feat(ui) - subfilters - wip
This commit is contained in:
parent
2e33398efa
commit
1a55f9a897
11 changed files with 123 additions and 79 deletions
|
|
@ -47,15 +47,17 @@ function FilterAutoComplete(props: Props) {
|
|||
const requestValues = (q) => {
|
||||
setLoading(true);
|
||||
|
||||
return new APIClient()[method?.toLowerCase()](endpoint, { ...params, q })
|
||||
.then(response => response.json())
|
||||
.then(({ errors, data }) => {
|
||||
if (errors) {
|
||||
// this.setError();
|
||||
} else {
|
||||
setOptions(data);
|
||||
}
|
||||
}).finally(() => setLoading(false));
|
||||
return new APIClient()[method?.toLocaleLowerCase()](endpoint, { ...params, q })
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw new Error(response.statusText);
|
||||
})
|
||||
.then(({ data }) => {
|
||||
setOptions(data);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
|
||||
const debouncedRequestValues = React.useCallback(debounce(requestValues, 300), []);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import FilterSelection from '../FilterSelection';
|
|||
import FilterValue from '../FilterValue';
|
||||
import { Icon } from 'UI';
|
||||
import FilterSource from '../FilterSource';
|
||||
import { FilterType } from 'App/types/filter/filterType';
|
||||
import SubFilterItem from '../SubFilterItem';
|
||||
|
||||
interface Props {
|
||||
filterIndex: number;
|
||||
|
|
@ -15,9 +17,14 @@ interface Props {
|
|||
function FilterItem(props: Props) {
|
||||
const { isFilter = false, filterIndex, filter } = props;
|
||||
const canShowValues = !(filter.operator === "isAny" || filter.operator === "onAny" || filter.operator === "isUndefined");
|
||||
const isSubFilter = filter.type === FilterType.SUB_FILTERS;
|
||||
|
||||
const replaceFilter = (filter) => {
|
||||
props.onUpdate({ ...filter, value: [""]});
|
||||
props.onUpdate({
|
||||
...filter,
|
||||
value: [""],
|
||||
subFilters: filter.subFilters ? filter.subFilters.map(i => ({ ...i, value: [""] })) : []
|
||||
});
|
||||
};
|
||||
|
||||
const onOperatorChange = (e, { name, value }) => {
|
||||
|
|
@ -28,6 +35,19 @@ function FilterItem(props: Props) {
|
|||
props.onUpdate({ ...filter, sourceOperator: value })
|
||||
}
|
||||
|
||||
const onUpdateSubFilter = (subFilter, subFilterIndex) => {
|
||||
props.onUpdate({
|
||||
...filter,
|
||||
subFilters: filter.subFilters.map((i, index) => {
|
||||
if (index === subFilterIndex) {
|
||||
return subFilter;
|
||||
}
|
||||
return i;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex items-center hover:bg-active-blue -mx-5 px-5 py-2">
|
||||
<div className="flex items-start w-full">
|
||||
|
|
@ -48,14 +68,31 @@ function FilterItem(props: Props) {
|
|||
)}
|
||||
|
||||
{/* Filter values */}
|
||||
<FilterOperator
|
||||
options={filter.operatorOptions}
|
||||
onChange={onOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
value={filter.operator}
|
||||
/>
|
||||
{ canShowValues && (<FilterValue filter={filter} onUpdate={props.onUpdate} />) }
|
||||
|
||||
{ !isSubFilter && (
|
||||
<>
|
||||
<FilterOperator
|
||||
options={filter.operatorOptions}
|
||||
onChange={onOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
value={filter.operator}
|
||||
/>
|
||||
{ canShowValues && (<FilterValue filter={filter} onUpdate={props.onUpdate} />) }
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* SubFilters */}
|
||||
{isSubFilter && (
|
||||
<div className="grid grid-col ml-3 w-full">
|
||||
{filter.subFilters.map((subFilter, subFilterIndex) => (
|
||||
<SubFilterItem
|
||||
filterIndex={subFilterIndex}
|
||||
filter={subFilter}
|
||||
onUpdate={(f) => onUpdateSubFilter(f, subFilterIndex)}
|
||||
onRemoveFilter={props.onRemoveFilter}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 self-start mt-1 ml-auto px-2">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
import { filter } from 'App/components/BugFinder/ManageFilters/savedFilterList.css'
|
||||
import React from 'react'
|
||||
import FilterOperator from '../FilterOperator';
|
||||
import FilterValue from '../FilterValue';
|
||||
|
||||
interface Props {
|
||||
filterIndex: number;
|
||||
filter: any; // event/filter
|
||||
onUpdate: (filter) => void;
|
||||
onRemoveFilter: () => void;
|
||||
isFilter?: boolean;
|
||||
}
|
||||
export default function SubFilterItem(props: Props) {
|
||||
const { isFilter = false, filterIndex, filter } = props;
|
||||
const canShowValues = !(filter.operator === "isAny" || filter.operator === "onAny" || filter.operator === "isUndefined");
|
||||
|
||||
const onOperatorChange = (e, { name, value }) => {
|
||||
props.onUpdate({ ...filter, operator: value })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center hover:bg-active-blue pb-4">
|
||||
<div className="flex-shrink-0 py-1">{filter.label}</div>
|
||||
<FilterOperator
|
||||
options={filter.operatorOptions}
|
||||
onChange={onOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
value={filter.operator}
|
||||
/>
|
||||
|
||||
{ canShowValues && (<FilterValue filter={filter} onUpdate={props.onUpdate} />) }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './SubFilterItem';
|
||||
|
|
@ -18,12 +18,6 @@ function FunnelSearch(props: Props) {
|
|||
|
||||
const onAddFilter = (filter) => {
|
||||
props.addFilter(filter);
|
||||
// filter.value = [""]
|
||||
// const newFilters = appliedFilter.filters.concat(filter);
|
||||
// props.edit({
|
||||
// ...appliedFilter.filter,
|
||||
// filters: newFilters,
|
||||
// });
|
||||
}
|
||||
|
||||
const onUpdateFilter = (filterIndex, filter) => {
|
||||
|
|
|
|||
|
|
@ -19,12 +19,6 @@ function SessionSearch(props: Props) {
|
|||
|
||||
const onAddFilter = (filter) => {
|
||||
props.addFilter(filter);
|
||||
// filter.value = [""]
|
||||
// const newFilters = appliedFilter.filters.concat(filter);
|
||||
// props.edit({
|
||||
// ...appliedFilter.filter,
|
||||
// filters: newFilters,
|
||||
// });
|
||||
}
|
||||
|
||||
const onUpdateFilter = (filterIndex, filter) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { fromJS, List, Map, Set } from 'immutable';
|
||||
import { List, Map, Set } from 'immutable';
|
||||
import { errors as errorsRoute, isRoute } from "App/routes";
|
||||
import Filter from 'Types/filter';
|
||||
import SavedFilter from 'Types/filter/savedFilter';
|
||||
|
|
@ -8,15 +8,6 @@ import withRequestState, { RequestTypes } from './requestStateCreator';
|
|||
import { fetchList as fetchSessionList } from './sessions';
|
||||
import { fetchList as fetchErrorsList } from './errors';
|
||||
import { fetchListType, fetchType, saveType, editType, initType, removeType } from './funcTools/crud/types';
|
||||
import logger from 'App/logger';
|
||||
|
||||
import { newFiltersList } from 'Types/filter'
|
||||
import NewFilter, { filtersMap } from 'Types/filter/newFilter';
|
||||
|
||||
|
||||
// for (var i = 0; i < newFiltersList.length; i++) {
|
||||
// filterOptions[newFiltersList[i].category] = newFiltersList.filter(filter => filter.category === newFiltersList[i].category)
|
||||
// }
|
||||
|
||||
const ERRORS_ROUTE = errorsRoute();
|
||||
|
||||
|
|
@ -44,11 +35,8 @@ const ADD_ATTRIBUTE = 'filters/ADD_ATTRIBUTE';
|
|||
const EDIT_ATTRIBUTE = 'filters/EDIT_ATTRIBUTE';
|
||||
const REMOVE_ATTRIBUTE = 'filters/REMOVE_ATTRIBUTE';
|
||||
const SET_ACTIVE_FLOW = 'filters/SET_ACTIVE_FLOW';
|
||||
|
||||
const UPDATE_VALUE = 'filters/UPDATE_VALUE';
|
||||
|
||||
const REFRESH_FILTER_OPTIONS = 'filters/REFRESH_FILTER_OPTIONS';
|
||||
|
||||
const initialState = Map({
|
||||
instance: Filter(),
|
||||
activeFilter: null,
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ export const applySavedSearch = (filter) => (dispatch, getState) => {
|
|||
|
||||
export const fetchSessions = (filter) => (dispatch, getState) => {
|
||||
const _filter = filter ? filter : getState().getIn([ 'search', 'instance']);
|
||||
return dispatch(applyFilter(_filter));
|
||||
// return dispatch(applyFilter(_filter)); // TODO uncomment this line
|
||||
};
|
||||
|
||||
export const updateSeries = (index, series) => ({
|
||||
|
|
@ -233,6 +233,10 @@ export const hasFilterApplied = (filters, filter) => {
|
|||
|
||||
export const addFilter = (filter) => (dispatch, getState) => {
|
||||
filter.value = checkFilterValue(filter.value);
|
||||
filter.subFilters = filter.subFilters ? filter.subFilters.map(subFilter => ({
|
||||
...subFilter,
|
||||
value: checkFilterValue(subFilter.value),
|
||||
})) : null;
|
||||
const instance = getState().getIn([ 'search', 'instance']);
|
||||
|
||||
if (hasFilterApplied(instance.filters, filter)) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export enum FilterType {
|
|||
NUMBER = "NUMBER",
|
||||
DURATION = "DURATION",
|
||||
MULTIPLE = "MULTIPLE",
|
||||
SUB_FILTERS = "SUB_FILTERS",
|
||||
COUNTRY = "COUNTRY",
|
||||
DROPDOWN = "DROPDOWN",
|
||||
MULTIPLE_DROPDOWN = "MULTIPLE_DROPDOWN",
|
||||
|
|
@ -61,4 +62,8 @@ export enum FilterKey {
|
|||
AVG_CPU_LOAD = "AVG_CPU_LOAD",
|
||||
AVG_MEMORY_USAGE = "AVG_MEMORY_USAGE",
|
||||
FETCH_FAILED = "FETCH_FAILED",
|
||||
FETCH = "FETCH",
|
||||
FETCH_URL = "FETCH_URL",
|
||||
FETCH_STATUS = "FETCH_STATUS",
|
||||
FETCH_METHOD = "FETCH_METHOD",
|
||||
}
|
||||
|
|
@ -236,22 +236,4 @@ export const operatorOptions = (filter) => {
|
|||
case KEYS.CLICK_RAGE:
|
||||
return [{ key: 'onAnything', text: 'on anything', value: 'true' }]
|
||||
}
|
||||
}
|
||||
|
||||
const NewFilterType = (key, category, label, icon, isEvent = false) => {
|
||||
return {
|
||||
key: key,
|
||||
category: category,
|
||||
label: label,
|
||||
icon: icon,
|
||||
isEvent: isEvent,
|
||||
operators: operatorOptions({ key }),
|
||||
value: [""]
|
||||
}
|
||||
}
|
||||
|
||||
export const newFiltersList = [
|
||||
NewFilterType(TYPES.CLICK, 'Gear', 'Click', 'filters/click', true),
|
||||
NewFilterType(TYPES.CLICK, 'Gear', 'Input', 'filters/click', true),
|
||||
NewFilterType(TYPES.CONSOLE, 'Other', 'Console', 'filters/click', true),
|
||||
];
|
||||
}
|
||||
|
|
@ -48,6 +48,11 @@ export const filtersMap = {
|
|||
[FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' },
|
||||
|
||||
// PERFORMANCE
|
||||
[FilterKey.FETCH]: { key: FilterKey.FETCH, type: FilterType.SUB_FILTERS, category: FilterCategory.PERFORMANCE, label: 'Fetch Request', subFilters: [
|
||||
{ key: FilterKey.FETCH_URL, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with URL', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_STATUS, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with status code', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
{ key: FilterKey.FETCH_METHOD, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'with method', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/fetch' },
|
||||
], icon: 'filters/fetch-failed', isEvent: true },
|
||||
[FilterKey.DOM_COMPLETE]: { key: FilterKey.DOM_COMPLETE, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'DOM Complete', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/dom-complete', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
[FilterKey.LARGEST_CONTENTFUL_PAINT_TIME]: { key: FilterKey.LARGEST_CONTENTFUL_PAINT_TIME, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Largest Contentful Paint', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/lcpt', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
[FilterKey.TTFB]: { key: FilterKey.TTFB, type: FilterType.MULTIPLE, category: FilterCategory.PERFORMANCE, label: 'Time to First Byte', operator: 'isAny', operatorOptions: filterOptions.stringOperators, source: [], icon: 'filters/ttfb', isEvent: true, hasSource: true, sourceOperator: '=', sourceType: FilterType.NUMBER, sourceOperatorOptions: filterOptions.customOperators },
|
||||
|
|
@ -121,17 +126,19 @@ export default Record({
|
|||
isEvent: false,
|
||||
index: 0,
|
||||
options: [],
|
||||
|
||||
subFilters: [],
|
||||
}, {
|
||||
keyKey: "_key",
|
||||
fromJS: ({ value, key, type, ...filter }) => {
|
||||
// const _filter = filtersMap[key] || filtersMap[type] || {};
|
||||
const _filter = filtersMap[type];
|
||||
return {
|
||||
...filter,
|
||||
..._filter,
|
||||
key: _filter.key,
|
||||
type: _filter.type, // camelCased(filter.type.toLowerCase()),
|
||||
value: value.length === 0 ? [""] : value, // make sure there an empty value
|
||||
value: value.length === 0 ? [""] : value,
|
||||
// subFilters: filter.subFilters.map(this),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -142,33 +149,29 @@ export default Record({
|
|||
* @returns
|
||||
*/
|
||||
export const generateFilterOptions = (map) => {
|
||||
const _options = {};
|
||||
const filterSection = {};
|
||||
Object.keys(map).forEach(key => {
|
||||
const filter = map[key];
|
||||
if (_options.hasOwnProperty(filter.category)) {
|
||||
_options[filter.category].push(filter);
|
||||
if (filterSection.hasOwnProperty(filter.category)) {
|
||||
filterSection[filter.category].push(filter);
|
||||
} else {
|
||||
_options[filter.category] = [filter];
|
||||
filterSection[filter.category] = [filter];
|
||||
}
|
||||
});
|
||||
return _options;
|
||||
return filterSection;
|
||||
}
|
||||
|
||||
export const generateLiveFilterOptions = (map) => {
|
||||
const _options = {};
|
||||
const filterSection = {};
|
||||
|
||||
Object.keys(map).filter(i => map[i].isLive).forEach(key => {
|
||||
const filter = map[key];
|
||||
filter.operator = 'contains';
|
||||
// filter.type = FilterType.STRING;
|
||||
// filter.type = FilterType.AUTOCOMPLETE_LOCAL;
|
||||
// filter.options = countryOptions;
|
||||
// filter.operatorOptions = [{ key: 'contains', text: 'contains', value: 'contains' }]
|
||||
if (_options.hasOwnProperty(filter.category)) {
|
||||
_options[filter.category].push(filter);
|
||||
if (filterSection.hasOwnProperty(filter.category)) {
|
||||
filterSection[filter.category].push(filter);
|
||||
} else {
|
||||
_options[filter.category] = [filter];
|
||||
filterSection[filter.category] = [filter];
|
||||
}
|
||||
});
|
||||
return _options;
|
||||
return filterSection;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue