From 74944ed778c893252d623cfb45cd80143d7198b5 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 10 Feb 2022 19:46:04 +0100 Subject: [PATCH] feat(ui) - assist filters --- .../LiveSessionList/LiveSessionList.tsx | 66 ++++++++++----- .../FilterSeries/FilterSeries.tsx | 1 - .../FilterAutoCompleteLocal.css | 81 +++++++++++++++++++ .../FilterAutoCompleteLocal.tsx | 77 ++++++++++++++++++ .../Filters/FilterAutoCompleteLocal/index.ts | 1 + .../Filters/FilterValue/FilterValue.tsx | 13 +++ .../LiveFilterModal/LiveFilterModal.tsx | 9 +-- .../shared/LiveSearchBar/LiveSearchBar.tsx | 4 +- .../LiveSessionSearch/LiveSessionSearch.tsx | 18 ++++- .../LiveSessionSearchField.tsx | 1 - .../shared/SavedSearch/SavedSearch.tsx | 2 +- frontend/app/duck/liveSearch.js | 14 +++- frontend/app/duck/search.js | 19 ++++- frontend/app/duck/sessions.js | 8 +- frontend/app/types/filter/filterType.ts | 2 + frontend/app/types/filter/newFilter.js | 47 ++++++++--- frontend/app/types/session/session.js | 1 + tracker/tracker/scripts/compile.js | 2 +- 18 files changed, 317 insertions(+), 49 deletions(-) create mode 100644 frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.css create mode 100644 frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.tsx create mode 100644 frontend/app/components/shared/Filters/FilterAutoCompleteLocal/index.ts diff --git a/frontend/app/components/BugFinder/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/BugFinder/LiveSessionList/LiveSessionList.tsx index b58279109..13617a8c2 100644 --- a/frontend/app/components/BugFinder/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/BugFinder/LiveSessionList/LiveSessionList.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { fetchList } from 'Duck/sessions'; +import { fetchLiveList } from 'Duck/sessions'; import { connect } from 'react-redux'; import { NoContent, Loader } from 'UI'; import { List, Map } from 'immutable'; @@ -7,26 +7,56 @@ import SessionItem from 'Shared/SessionItem'; import withPermissions from 'HOCs/withPermissions' import { KEYS } from 'Types/filter/customFilter'; import { applyFilter, addAttribute } from 'Duck/filters'; -import Filter from 'Types/filter'; +import { FilterCategory, FilterKey } from 'App/types/filter/filterType'; +import { addFilterByKeyAndValue } from 'Duck/liveSearch'; const AUTOREFRESH_INTERVAL = .5 * 60 * 1000 interface Props { loading: Boolean, - list?: List, - fetchList: (params) => void, + list: List, + fetchLiveList: () => Promise, applyFilter: () => void, - filters: Filter + filters: any, addAttribute: (obj) => void, + addFilterByKeyAndValue: (key: FilterKey, value: string) => void, } function LiveSessionList(props: Props) { - const { loading, list, filters } = props; + const { loading, filters, list } = props; var timeoutId; - const hasUserFilter = filters && filters.filters.map(i => i.key).includes(KEYS.USERID); + const hasUserFilter = filters.map(i => i.key).includes(KEYS.USERID); + const [sessions, setSessions] = React.useState(list); + + useEffect(() => { + if (filters.size === 0) { + props.addFilterByKeyAndValue(FilterKey.USERID, ''); + } + }, []); + + useEffect(() => { + const filteredSessions = filters.size > 0 ? props.list.filter(session => { + let hasValidFilter = true; + filters.forEach(filter => { + if (!hasValidFilter) return; + + const _values = filter.value.filter(i => i !== '' && i !== null && i !== undefined).map(i => i.toLowerCase()); + if (filter.key === FilterKey.USERID) { + const _userId = session.userId ? session.userId.toLowerCase() : ''; + hasValidFilter = _values.length > 0 ? (_values.includes(_userId) && hasValidFilter) || _values.some(i => _userId.includes(i)) : hasValidFilter; + } + if (filter.category === FilterCategory.METADATA) { + const _source = session.metadata[filter.key] ? session.metadata[filter.key].toLowerCase() : ''; + hasValidFilter = _values.length > 0 ? (_values.includes(_source) && hasValidFilter) || _values.some(i => _source.includes(i)) : hasValidFilter; + } + }) + return hasValidFilter; + }) : props.list; + setSessions(filteredSessions); + }, [filters, list]); useEffect(() => { - props.fetchList(filters.toJS()); + props.fetchLiveList(); timeout(); return () => { clearTimeout(timeoutId) @@ -35,17 +65,15 @@ function LiveSessionList(props: Props) { const onUserClick = (userId, userAnonymousId) => { if (userId) { - props.addAttribute({ label: 'User Id', key: KEYS.USERID, type: KEYS.USERID, operator: 'is', value: userId }) + props.addFilterByKeyAndValue(FilterKey.USERID, userId); } else { - props.addAttribute({ label: 'Anonymous ID', key: 'USERANONYMOUSID', type: "USERANONYMOUSID", operator: 'is', value: userAnonymousId }) + props.addFilterByKeyAndValue(FilterKey.USERANONYMOUSID, userAnonymousId); } - - props.applyFilter() } const timeout = () => { timeoutId = setTimeout(() => { - props.fetchList(filters.toJS()); + props.fetchLiveList(); timeout(); }, AUTOREFRESH_INTERVAL); } @@ -59,11 +87,12 @@ function LiveSessionList(props: Props) { See how to {'enable Assist'} if you haven't yet done so. } - image={} - show={ !loading && list && list.size === 0} + image={} + show={ !loading && sessions && sessions.size === 0} > - {list && list.map(session => ( + {sessions && sessions.map(session => ( ({ list: state.getIn(['sessions', 'liveSessions']), loading: state.getIn([ 'sessions', 'loading' ]), - filters: state.getIn([ 'filters', 'appliedFilter' ]), + filters: state.getIn([ 'liveSearch', 'instance', 'filters' ]), }), - { - fetchList, applyFilter, addAttribute } + { fetchLiveList, applyFilter, addAttribute, addFilterByKeyAndValue } )(LiveSessionList)); diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx index dd1ef1a31..0f5df220b 100644 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx @@ -42,7 +42,6 @@ function FilterSeries(props: Props) { } const onChangeEventsOrder = (e, { name, value }) => { - props.editSeriesFilter(seriesIndex, { eventsOrder: value }); } diff --git a/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.css b/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.css new file mode 100644 index 000000000..bb9f17313 --- /dev/null +++ b/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.css @@ -0,0 +1,81 @@ +.wrapper { + border: solid thin $gray-light !important; + border-radius: 3px; + border-radius: 3px; + display: flex; + align-items: center; + background-color: white; + width: 100%; + & input { + height: 24px; + font-size: 13px !important; + padding: 0 5px !important; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + border: solid thin transparent !important; + width: 100%; + } + + & .right { + height: 24px; + display: flex; + align-items: stretch; + padding: 0; + background-color: $gray-lightest; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + + & div { + /* background-color: red; */ + border-left: solid thin $gray-light !important; + width: 28px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + &:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + } + &:hover { + background-color: $gray-light; + } + } + } +} + +.menu { + border-radius: 0 0 3px 3px; + border: solid thin $gray-light !important; + box-shadow: 0 2px 2px 0 $gray-light; + /* padding: 20px; */ + background-color: white; + max-height: 350px; + overflow-y: auto; + position: absolute; + top: 28px; + left: 0; + width: 500px; + z-index: 99; +} + +.filterItem { + display: flex; + align-items: center; + padding: 8px 10px; + cursor: pointer; + border-radius: 3px; + /* transition: all 0.4s; */ + margin-bottom: 5px; + max-width: 100%; + & .label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &:hover { + background-color: $gray-lightest; + /* transition: all 0.2s; */ + } +} \ No newline at end of file diff --git a/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.tsx b/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.tsx new file mode 100644 index 000000000..24e940079 --- /dev/null +++ b/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/FilterAutoCompleteLocal.tsx @@ -0,0 +1,77 @@ +import React, { useState, useEffect } from 'react'; +import { Icon, Loader } from 'UI'; +import { debounce } from 'App/utils'; +import stl from './FilterAutoCompleteLocal.css'; +import cn from 'classnames'; + +interface Props { + showOrButton?: boolean; + showCloseButton?: boolean; + onRemoveValue?: () => void; + onAddValue?: () => void; + placeholder?: string; + onSelect: (e, item) => void; + value: any; + icon?: string; +} + +function FilterAutoCompleteLocal(props: Props) { + const { + showCloseButton = false, + placeholder = 'Type to search', + showOrButton = false, + onRemoveValue = () => null, + onAddValue = () => null, + value = '', + icon = null, + } = props; + const [showModal, setShowModal] = useState(true) + const [query, setQuery] = useState(value); + + const onInputChange = ({ target: { value } }) => { + setQuery(value); + } + + useEffect(() => { + setQuery(value); + }, [value]) + + const onBlur = (e) => { + setTimeout(() => { setShowModal(false) }, 200) + props.onSelect(e, { value: query }) + } + + const handleKeyDown = (e) => { + if (e.key === 'Enter') { + props.onSelect(e, { value: query }) + } + } + + return ( +
+
+ setShowModal(true)} + value={ query } + autoFocus={ true } + type="text" + placeholder={ placeholder } + onKeyDown={handleKeyDown} + /> +
+ { showCloseButton &&
} + { showOrButton &&
or
} +
+
+ + { !showOrButton &&
or
} +
+ ); +} + +export default FilterAutoCompleteLocal; \ No newline at end of file diff --git a/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/index.ts b/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/index.ts new file mode 100644 index 000000000..b44237db5 --- /dev/null +++ b/frontend/app/components/shared/Filters/FilterAutoCompleteLocal/index.ts @@ -0,0 +1 @@ +export { default } from './FilterAutoCompleteLocal'; \ No newline at end of file diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx index f5ffdcb39..80a1f705a 100644 --- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx +++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import FilterAutoComplete from '../FilterAutoComplete'; +import FilterAutoCompleteLocal from '../FilterAutoCompleteLocal'; import { FilterKey, FilterCategory, FilterType } from 'Types/filter/filterType'; import FilterValueDropdown from '../FilterValueDropdown'; import FilterDuration from '../FilterDuration'; @@ -63,6 +64,18 @@ function FilterValue(props: Props) { const renderValueFiled = (value, valueIndex) => { const showOrButton = valueIndex === lastIndex; switch(filter.type) { + case FilterType.STRING: + return ( + onRemoveValue(valueIndex)} + onSelect={(e, item) => onChange(e, item, valueIndex)} + icon={filter.icon} + /> + ) case FilterType.DROPDOWN: return ( - {filters && Object.keys(filters).filter(i => i === 'User' || i === 'Metadata').map((key) => ( + {filters && Object.keys(filters).map((key) => (
{key}
- {filters[key].filter((i: any) => i.key === FilterKey.USERID || i.key === FilterKey.USERANONYMOUSID || i.category === FilterCategory.METADATA).map((filter: any) => ( + {filters[key].map((filter: any) => (
onFilterClick(filter)}> {filter.label} @@ -89,7 +88,7 @@ function LiveFilterModal(props: Props) { } export default connect(state => ({ - filters: state.getIn([ 'search', 'filterList' ]), + filters: state.getIn([ 'search', 'filterListLive' ]), filterSearchList: state.getIn([ 'search', 'filterSearchList' ]), metaOptions: state.getIn([ 'customFields', 'list' ]), fetchingFilterSearchList: state.getIn([ 'search', 'fetchFilterSearch', 'loading' ]), diff --git a/frontend/app/components/shared/LiveSearchBar/LiveSearchBar.tsx b/frontend/app/components/shared/LiveSearchBar/LiveSearchBar.tsx index e1acab70d..f6d9122e3 100644 --- a/frontend/app/components/shared/LiveSearchBar/LiveSearchBar.tsx +++ b/frontend/app/components/shared/LiveSearchBar/LiveSearchBar.tsx @@ -13,10 +13,10 @@ const LiveSearchBar = (props: Props) => { const hasFilters = appliedFilter && appliedFilter.filters && appliedFilter.filters.size > 0; return (
-
+
-
+
i.isEvent).size > 0; const hasFilters = appliedFilter.filters.filter(i => !i.isEvent).size > 0; + const onAddFilter = (filter) => { + props.addFilter(filter); + } + const onUpdateFilter = (filterIndex, filter) => { const newFilters = appliedFilter.filters.map((_filter, i) => { if (i === filterIndex) { @@ -55,6 +60,17 @@ function LiveSessionSearch(props: Props) { onChangeEventsOrder={onChangeEventsOrder} />
+ +
+
+ + + +
+
) : <>; } diff --git a/frontend/app/components/shared/LiveSessionSearchField/LiveSessionSearchField.tsx b/frontend/app/components/shared/LiveSessionSearchField/LiveSessionSearchField.tsx index 3a6b569c6..e0266c7c1 100644 --- a/frontend/app/components/shared/LiveSessionSearchField/LiveSessionSearchField.tsx +++ b/frontend/app/components/shared/LiveSessionSearchField/LiveSessionSearchField.tsx @@ -23,7 +23,6 @@ function LiveSessionSearchField(props: Props) { } const onAddFilter = (filter) => { - console.log('onAddFilter', filter) props.addFilterByKeyAndValue(filter.key, filter.value) } diff --git a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx index ffada6726..1321563ae 100644 --- a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx +++ b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx @@ -33,7 +33,7 @@ function SavedSearch(props) { className="flex items-center" onClick={() => setShowMenu(true)} > - {`Search Saved (${list.size})`} + {`Saved Search (${list.size})`} { savedSearch.exists() && ( diff --git a/frontend/app/duck/liveSearch.js b/frontend/app/duck/liveSearch.js index a7f2e5814..c0de6a189 100644 --- a/frontend/app/duck/liveSearch.js +++ b/frontend/app/duck/liveSearch.js @@ -6,7 +6,7 @@ import Filter from 'Types/filter'; import SavedFilter from 'Types/filter/savedFilter'; import { fetchList as fetchSessionList } from './sessions'; import { filtersMap } from 'Types/filter/newFilter'; -import { filterMap, checkFilterValue } from './search'; +import { filterMap, checkFilterValue, hasFilterApplied } from './search'; const name = "liveSearch"; const idKey = "searchId"; @@ -73,8 +73,16 @@ export const clearSearch = () => (dispatch, getState) => { export const addFilter = (filter) => (dispatch, getState) => { filter.value = checkFilterValue(filter.value); const instance = getState().getIn([ 'liveSearch', 'instance']); - const filters = instance.filters.push(filter); - return dispatch(edit(instance.set('filters', filters))); + + if (hasFilterApplied(instance.filters, filter)) { + // const index = instance.filters.findIndex(f => f.key === filter.key); + // const oldFilter = instance.filters.get(index); + // oldFilter.value = oldFilter.value.concat(filter.value); + // return dispatch(edit(instance.setIn(['filters', index], oldFilter))); + } else { + const filters = instance.filters.push(filter); + return dispatch(edit(instance.set('filters', filters))); + } } export const addFilterByKeyAndValue = (key, value) => (dispatch, getState) => { diff --git a/frontend/app/duck/search.js b/frontend/app/duck/search.js index 7897a3596..1b6e84a3f 100644 --- a/frontend/app/duck/search.js +++ b/frontend/app/duck/search.js @@ -8,7 +8,7 @@ import { errors as errorsRoute, isRoute } from "App/routes"; import { fetchList as fetchSessionList } from './sessions'; import { fetchList as fetchErrorsList } from './errors'; import { FilterCategory, FilterKey } from '../types/filter/filterType'; -import { filtersMap, generateFilterOptions } from 'Types/filter/newFilter'; +import { filtersMap, generateFilterOptions, generateLiveFilterOptions } from 'Types/filter/newFilter'; const ERRORS_ROUTE = errorsRoute(); @@ -43,6 +43,7 @@ const updateInstance = (state, instance) => state.getIn([ "savedSearch", savedSe const initialState = Map({ filterList: generateFilterOptions(filtersMap), + filterListLive: generateLiveFilterOptions(filtersMap), list: List(), alertMetricId: null, instance: new Filter({ filters: [] }), @@ -54,7 +55,8 @@ const initialState = Map({ function reducer(state = initialState, action = {}) { switch (action.type) { case REFRESH_FILTER_OPTIONS: - return state.set('filterList', generateFilterOptions(filtersMap)); + return state.set('filterList', generateFilterOptions(filtersMap)) + .set('filterListLive', generateLiveFilterOptions(filtersMap)); case EDIT: return state.mergeIn(['instance'], action.instance); case APPLY: @@ -212,11 +214,20 @@ export const clearSearch = () => (dispatch, getState) => { }); } +export const hasFilterApplied = (filters, filter) => { + return !filter.isEvent && filters.some(f => f.key === filter.key); +} + export const addFilter = (filter) => (dispatch, getState) => { filter.value = checkFilterValue(filter.value); const instance = getState().getIn([ 'search', 'instance']); - const filters = instance.filters.push(filter); - return dispatch(edit(instance.set('filters', filters))); + + if (hasFilterApplied(instance.filters, filter)) { + + } else { + const filters = instance.filters.push(filter); + return dispatch(edit(instance.set('filters', filters))); + } } export const addFilterByKeyAndValue = (key, value) => (dispatch, getState) => { diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.js index 977af85bb..2ab1e5a5a 100644 --- a/frontend/app/duck/sessions.js +++ b/frontend/app/duck/sessions.js @@ -74,7 +74,6 @@ const reducer = (state = initialState, action = {}) => { case FETCH_ERROR_STACK.SUCCESS: return state.set('errorStack', List(action.data.trace).map(ErrorStack)).set('sourcemapUploaded', action.data.sourcemapUploaded) case FETCH_LIVE_LIST.SUCCESS: - // const { sessions, total } = action.data; const liveList = List(action.data).map(s => new Session({...s, live: true})); return state .set('liveSessions', liveList) @@ -284,6 +283,13 @@ export const fetchList = (params = {}, clear = false, live = false) => (dispatch }) } +// export const fetchLiveList = (id) => (dispatch, getState) => { +// return dispatch({ +// types: FETCH_LIVE_LIST.toArray(), +// call: client => client.get('/assist/sessions'), +// }) +// } + export function fetchErrorStackList(sessionId, errorId) { return { types: FETCH_ERROR_STACK.toArray(), diff --git a/frontend/app/types/filter/filterType.ts b/frontend/app/types/filter/filterType.ts index 2c29e5cde..16f128975 100644 --- a/frontend/app/types/filter/filterType.ts +++ b/frontend/app/types/filter/filterType.ts @@ -9,6 +9,7 @@ export enum FilterCategory { }; export enum FilterType { + STRING = "STRING", ISSUE = "ISSUE", BOOLEAN = "BOOLEAN", NUMBER = "NUMBER", @@ -17,6 +18,7 @@ export enum FilterType { COUNTRY = "COUNTRY", DROPDOWN = "DROPDOWN", MULTIPLE_DROPDOWN = "MULTIPLE_DROPDOWN", + AUTOCOMPLETE_LOCAL = "AUTOCOMPLETE_LOCAL", }; export enum FilterKey { diff --git a/frontend/app/types/filter/newFilter.js b/frontend/app/types/filter/newFilter.js index 6867cf610..6ae761f58 100644 --- a/frontend/app/types/filter/newFilter.js +++ b/frontend/app/types/filter/newFilter.js @@ -43,7 +43,7 @@ export const filtersMap = { [FilterKey.DURATION]: { key: FilterKey.DURATION, type: FilterType.DURATION, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'Duration', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is']), icon: 'filters/duration' }, [FilterKey.USER_COUNTRY]: { key: FilterKey.USER_COUNTRY, type: FilterType.MULTIPLE_DROPDOWN, category: FilterCategory.RECORDING_ATTRIBUTES, label: 'User Country', operator: 'is', operatorOptions: filterOptions.getOperatorsByKeys(['is', 'isAny', 'isNot']), icon: 'filters/country', options: countryOptions }, // [FilterKey.CONSOLE]: { key: FilterKey.CONSOLE, type: FilterType.MULTIPLE, category: FilterCategory.JAVASCRIPT, label: 'Console', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/console' }, - [FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' }, + [FilterKey.USERID]: { key: FilterKey.USERID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User Id', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid', isLive: true }, [FilterKey.USERANONYMOUSID]: { key: FilterKey.USERANONYMOUSID, type: FilterType.MULTIPLE, category: FilterCategory.USER, label: 'User AnonymousId', operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/userid' }, // PERFORMANCE @@ -56,6 +56,15 @@ export const filtersMap = { [FilterKey.ISSUE]: { key: FilterKey.ISSUE, type: FilterType.ISSUE, category: FilterCategory.JAVASCRIPT, label: 'Issue', operator: 'is', operatorOptions: filterOptions.baseOperators, icon: 'filters/click', options: ISSUE_OPTIONS }, } +/** + * Add a new filter to the filter list + * @param {*} category + * @param {*} key + * @param {*} type + * @param {*} operator + * @param {*} operatorOptions + * @param {*} icon + */ export const addElementToFiltersMap = ( category = FilterCategory.METADATA, key, @@ -64,14 +73,7 @@ export const addElementToFiltersMap = ( operatorOptions = filterOptions.stringOperators, icon = 'filters/metadata' ) => { - console.log('addElementToFiltersMap', category, key, type, operator, operatorOptions, icon) - filtersMap[key] = { key, type, category, label: capitalize(key), operator: operator, operatorOptions, icon } -} - - -export const getMetaDataFilter = (key) => { - const METADATA_FILTER = { key: key, type: FilterType.MULTIPLE, category: FilterCategory.METADATA, label: capitalize(key), operator: 'is', operatorOptions: filterOptions.stringOperators, icon: 'filters/metadata' } - return METADATA_FILTER; + filtersMap[key] = { key, type, category, label: capitalize(key), operator: operator, operatorOptions, icon, isLive: true } } export default Record({ @@ -113,11 +115,16 @@ export default Record({ ..._filter, key: _filter.key, type: _filter.type, // camelCased(filter.type.toLowerCase()), - value: value.length === 0 ? [""] : value, + value: value.length === 0 ? [""] : value, // make sure there an empty value } }, }) +/** + * Group filters by category + * @param {*} filtersMap + * @returns + */ export const generateFilterOptions = (filtersMap) => { const _options = {}; Object.keys(filtersMap).forEach(key => { @@ -129,4 +136,24 @@ export const generateFilterOptions = (filtersMap) => { } }); return _options; +} + +export const generateLiveFilterOptions = (filtersMap) => { + const _options = {}; + Object.keys(filtersMap).filter(i => filtersMap[i].isLive).forEach(key => { + const filter = filtersMap[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); + } else { + _options[filter.category] = [filter]; + } + }); + return _options; } \ No newline at end of file diff --git a/frontend/app/types/session/session.js b/frontend/app/types/session/session.js index ee47ecab3..44dce3ab0 100644 --- a/frontend/app/types/session/session.js +++ b/frontend/app/types/session/session.js @@ -142,6 +142,7 @@ export default Record({ firstResourceTime, issues: issuesList, sessionId: sessionId || sessionID, + userId: session.userId || session.userID, }; }, idKey: "sessionId", diff --git a/tracker/tracker/scripts/compile.js b/tracker/tracker/scripts/compile.js index 6e7bd43a1..ab0091dd5 100644 --- a/tracker/tracker/scripts/compile.js +++ b/tracker/tracker/scripts/compile.js @@ -1,6 +1,6 @@ import { promises as fs } from 'fs'; import replaceInFiles from 'replace-in-files'; -import packageConfig from '../package.json'; +import packageConfig from '../package.json' assert { type: 'json' }; async function main() { const webworker = await fs.readFile('build/webworker.js', 'utf8');