diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx index 333188b3e..92baa3d51 100644 --- a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx @@ -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), []); diff --git a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx index db0bedf32..f01fefcd8 100644 --- a/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx +++ b/frontend/app/components/shared/Filters/FilterItem/FilterItem.tsx @@ -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 (
@@ -48,14 +68,31 @@ function FilterItem(props: Props) { )} {/* Filter values */} - - { canShowValues && () } - + { !isSubFilter && ( + <> + + { canShowValues && () } + + )} + + {/* SubFilters */} + {isSubFilter && ( +
+ {filter.subFilters.map((subFilter, subFilterIndex) => ( + onUpdateSubFilter(f, subFilterIndex)} + onRemoveFilter={props.onRemoveFilter} + /> + ))} +
+ )}
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 ( +
+
{filter.label}
+ + + { canShowValues && () } +
+ ) +} diff --git a/frontend/app/components/shared/Filters/SubFilterItem/index.ts b/frontend/app/components/shared/Filters/SubFilterItem/index.ts new file mode 100644 index 000000000..0877700cc --- /dev/null +++ b/frontend/app/components/shared/Filters/SubFilterItem/index.ts @@ -0,0 +1 @@ +export { default } from './SubFilterItem'; \ No newline at end of file diff --git a/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx b/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx index 809eb739f..19a3b7ceb 100644 --- a/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx +++ b/frontend/app/components/shared/FunnelSearch/FunnelSearch.tsx @@ -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) => { diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx index 264786fff..17904c1ba 100644 --- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx +++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx @@ -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) => { diff --git a/frontend/app/duck/filters.js b/frontend/app/duck/filters.js index 132996797..16c16aa5e 100644 --- a/frontend/app/duck/filters.js +++ b/frontend/app/duck/filters.js @@ -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, diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index ad4ea944c..00799e5d7 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -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)) { diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index 16f128975..655681796 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -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", } \ No newline at end of file diff --git a/frontend/app/types/filter/index.js b/frontend/app/types/filter/index.js index 957e1dfb8..386ea96a0 100644 --- a/frontend/app/types/filter/index.js +++ b/frontend/app/types/filter/index.js @@ -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), -]; \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index d4cb905a1..99726b4a6 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -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; } \ No newline at end of file