From b91868ebe16c62a3787e21b818084beb78817cab Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 20 Sep 2024 14:29:05 +0530 Subject: [PATCH] change(ui): duck/search wip --- frontend/app/components/Overview/Overview.tsx | 3 +- .../FilterAutoComplete/FilterAutoComplete.tsx | 6 +- .../shared/Filters/FilterList/FilterList.tsx | 1 - .../Filters/FilterModal/FilterModal.tsx | 57 ++++++++------- .../Filters/FilterValue/FilterValue.tsx | 2 +- .../shared/MainSearchBar/MainSearchBar.tsx | 2 +- .../shared/SavedSearch/SavedSearch.tsx | 2 +- .../shared/SessionSearch/SessionSearch.tsx | 26 ++----- .../SessionHeader/SessionHeader.tsx | 8 ++- .../components/SessionList/SessionList.tsx | 2 +- frontend/app/mstore/index.tsx | 3 +- frontend/app/mstore/searchStore.ts | 71 ++++++++++++++----- frontend/app/mstore/sessionStore.ts | 7 +- frontend/app/mstore/types/search.ts | 24 +++++-- frontend/app/services/SearchService.ts | 14 +++- frontend/app/services/index.ts | 2 +- 16 files changed, 145 insertions(+), 85 deletions(-) diff --git a/frontend/app/components/Overview/Overview.tsx b/frontend/app/components/Overview/Overview.tsx index 8d4d61fd1..21c38c7bb 100644 --- a/frontend/app/components/Overview/Overview.tsx +++ b/frontend/app/components/Overview/Overview.tsx @@ -12,6 +12,7 @@ import { Switch, Route } from 'react-router'; import { sessions, fflags, withSiteId, newFFlag, fflag, notes, fflagRead, bookmarks } from 'App/routes'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import FlagView from 'Components/FFlags/FlagView/FlagView'; +import { observer } from 'mobx-react-lite'; // @ts-ignore interface IProps extends RouteComponentProps { @@ -54,4 +55,4 @@ function Overview({ match: { params } }: IProps) { ); } -export default withPageTitle('Sessions - OpenReplay')(withRouter(Overview)); +export default withPageTitle('Sessions - OpenReplay')(withRouter(observer(Overview))); diff --git a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx index 90e0f7d64..a3b3335cd 100644 --- a/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx +++ b/frontend/app/components/shared/Filters/FilterAutoComplete/FilterAutoComplete.tsx @@ -8,6 +8,7 @@ import Select from 'react-select'; import cn from 'classnames'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; +import { searchService} from 'App/services'; const dropdownStyles = { option: (provided: any, state: any) => ({ @@ -196,8 +197,9 @@ const FilterAutoComplete: React.FC = ({ } try { - const response = await new APIClient()[method.toLowerCase()](endpoint, { ..._params, q: inputValue }); - const data = await response.json(); + // const response = await new APIClient()[method.toLowerCase()](endpoint, { ..._params, q: inputValue }); + const data = await searchService.fetchAutoCompleteValues({ ..._params, q: inputValue }) + // const data = await response.json(); const _options = data.data.map((i: any) => ({ value: i.value, label: i.value })) || []; setOptions(_options); callback(_options); diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx index e14e23e66..0a0ff0572 100644 --- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx +++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx @@ -37,7 +37,6 @@ function FilterList(props: Props) { } = props; const filters = filter.filters; - console.log('filters', filters) const hasEvents = filters.filter((i: any) => i.isEvent).length > 0; const hasFilters = filters.filter((i: any) => !i.isEvent).length > 0; diff --git a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx index 9126c8e96..8e27aeb05 100644 --- a/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx +++ b/frontend/app/components/shared/Filters/FilterModal/FilterModal.tsx @@ -25,7 +25,7 @@ import { Timer, VenetianMask, Workflow, - Flag, + Flag } from 'lucide-react'; import React from 'react'; import { connect } from 'react-redux'; @@ -34,6 +34,8 @@ import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import { FilterKey } from 'Types/filter/filterType'; import stl from './FilterModal.module.css'; +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore'; const IconMap = { [FilterKey.CLICK]: , @@ -65,10 +67,10 @@ const IconMap = { [FilterKey.DURATION]: , [FilterKey.TAGGED_ELEMENT]: , [FilterKey.METADATA]: , - [FilterKey.UTM_SOURCE]: , - [FilterKey.UTM_MEDIUM]: , - [FilterKey.UTM_CAMPAIGN]: , - [FilterKey.FEATURE_FLAG]: , + [FilterKey.UTM_SOURCE]: , + [FilterKey.UTM_MEDIUM]: , + [FilterKey.UTM_CAMPAIGN]: , + [FilterKey.FEATURE_FLAG]: }; function filterJson( @@ -102,7 +104,7 @@ export const getMatchingEntries = ( if (lowerCaseQuery.length === 0) return { matchingCategories: Object.keys(filters), - matchingFilters: filters, + matchingFilters: filters }; Object.keys(filters).forEach((name) => { @@ -123,7 +125,8 @@ export const getMatchingEntries = ( }; interface Props { - filters: any; + // filters: any; + isLive: boolean; conditionalFilters: any; mobileConditionalFilters: any; onFilterClick?: (filter: any) => void; @@ -139,9 +142,7 @@ interface Props { function FilterModal(props: Props) { const { - filters, - conditionalFilters, - mobileConditionalFilters, + isLive, onFilterClick = () => null, filterSearchList, isMainSearch = false, @@ -150,8 +151,12 @@ function FilterModal(props: Props) { excludeFilterKeys = [], allowedFilterKeys = [], isConditional, - isMobile, + isMobile } = props; + const { searchStore } = useStore(); + const filters = isLive ? searchStore.filterListLive : searchStore.filterList; + const conditionalFilters = searchStore.filterListConditional; + const mobileConditionalFilters = searchStore.filterListMobileConditional; const showSearchList = isMainSearch && searchQuery.length > 0; const onFilterSearchClick = (filter: any) => { @@ -161,8 +166,8 @@ function FilterModal(props: Props) { }; const filterJsonObj = isConditional - ? isMobile ? mobileConditionalFilters : conditionalFilters - : filters; + ? isMobile ? mobileConditionalFilters : conditionalFilters + : filters; const { matchingCategories, matchingFilters } = getMatchingEntries( searchQuery, filterJson(filterJsonObj, excludeFilterKeys, allowedFilterKeys) @@ -175,19 +180,18 @@ function FilterModal(props: Props) { const getNewIcon = (filter: Record) => { if (filter.icon?.includes('metadata')) { - return IconMap[FilterKey.METADATA] + return IconMap[FilterKey.METADATA]; } // @ts-ignore if (IconMap[filter.key]) { // @ts-ignore - return IconMap[filter.key] - } - else return - } + return IconMap[filter.key]; + } else return ; + }; return (
{ return { - filters: props.isLive - ? state.getIn(['search', 'filterListLive']) - : state.getIn(['search', 'filterList']), - conditionalFilters: state.getIn(['search', 'filterListConditional']), - mobileConditionalFilters: state.getIn(['search', 'filterListMobileConditional']), + // filters: props.isLive + // ? state.getIn(['search', 'filterListLive']) + // : state.getIn(['search', 'filterList']), + isLive: props.isLive, + // conditionalFilters: state.getIn(['search', 'filterListConditional']), + // mobileConditionalFilters: state.getIn(['search', 'filterListMobileConditional']), filterSearchList: props.isLive ? state.getIn(['liveSearch', 'filterSearchList']) : state.getIn(['search', 'filterSearchList']), fetchingFilterSearchList: props.isLive ? state.getIn(['liveSearch', 'fetchFilterSearch', 'loading']) - : state.getIn(['search', 'fetchFilterSearch', 'loading']), + : state.getIn(['search', 'fetchFilterSearch', 'loading']) }; -})(FilterModal); +})(observer(FilterModal)); diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx index 4bf549c9a..f88c03a5b 100644 --- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx +++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx @@ -165,7 +165,7 @@ function FilterValue(props: Props) { onAddValue={onAddValue} onRemoveValue={() => onRemoveValue(valueIndex)} method={'GET'} - endpoint="/events/search" + endpoint="/PROJECT_ID/events/search" params={getParms(filter.key)} headerText={''} placeholder={filter.placeholder} diff --git a/frontend/app/components/shared/MainSearchBar/MainSearchBar.tsx b/frontend/app/components/shared/MainSearchBar/MainSearchBar.tsx index 7e7750c6b..edf3bbb89 100644 --- a/frontend/app/components/shared/MainSearchBar/MainSearchBar.tsx +++ b/frontend/app/components/shared/MainSearchBar/MainSearchBar.tsx @@ -18,7 +18,7 @@ const MainSearchBar = (props: Props) => { const savedSearch = searchStore.savedSearch; const projectId = projectsStore.siteId; const currSite = React.useRef(projectId); - const hasFilters = appliedFilter && appliedFilter.filters && appliedFilter.filters.size > 0; + const hasFilters = appliedFilter && appliedFilter.filters && appliedFilter.filters.length > 0; const hasSavedSearch = savedSearch && savedSearch.exists(); const hasSearch = hasFilters || hasSavedSearch; diff --git a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx index e7c08d40a..ead805996 100644 --- a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx +++ b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx @@ -21,7 +21,7 @@ function SavedSearch(props: Props) { useEffect(() => { if (list.size === 0 && fetchedMeta) { - searchStore.fetchList(); // TODO check this call + searchStore.fetchSavedSearchList(); // TODO check this call } }, [fetchedMeta]); diff --git a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx index 2da94b887..553c73f27 100644 --- a/frontend/app/components/shared/SessionSearch/SessionSearch.tsx +++ b/frontend/app/components/shared/SessionSearch/SessionSearch.tsx @@ -15,8 +15,7 @@ let debounceFetch: any = () => { }; interface Props { - saveRequestPayloads: boolean; - metaLoading?: boolean; + } function SessionSearch(props: Props) { @@ -25,7 +24,7 @@ function SessionSearch(props: Props) { const metaLoading = customFieldStore.isLoading; const hasEvents = appliedFilter.filters.filter((i: any) => i.isEvent).length > 0; const hasFilters = appliedFilter.filters.filter((i: any) => !i.isEvent).length > 0; - const saveRequestPayloads = projectsStore.instance?.saveRequestPayloads ?? false + const saveRequestPayloads = projectsStore.instance?.saveRequestPayloads ?? false; useSessionSearchQueryHandler({ appliedFilter, @@ -55,24 +54,13 @@ function SessionSearch(props: Props) { }; const onUpdateFilter = (filterIndex: any, filter: any) => { - const newFilters = appliedFilter.filters.map((_filter: any, i: any) => { - if (i === filterIndex) { - return filter; - } else { - return _filter; - } - }); - - searchStore.updateFilter({ - ...appliedFilter, - filters: newFilters - }); + searchStore.updateFilter(filterIndex, filter); debounceFetch(); }; const onFilterMove = (newFilters: any) => { - searchStore.updateFilter({ + searchStore.updateFilter(0, { ...appliedFilter, filters: newFilters }); @@ -85,15 +73,13 @@ function SessionSearch(props: Props) { return i !== filterIndex; }); - searchStore.updateFilter({ - filters: newFilters - }); + searchStore.removeFilter(filterIndex); debounceFetch(); }; const onChangeEventsOrder = (e: any, { value }: any) => { - searchStore.updateFilter({ + searchStore.edit({ eventsOrder: value }); diff --git a/frontend/app/components/shared/SessionsTabOverview/components/SessionHeader/SessionHeader.tsx b/frontend/app/components/shared/SessionsTabOverview/components/SessionHeader/SessionHeader.tsx index 8e1ca4de4..aa30dfb41 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/SessionHeader/SessionHeader.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/SessionHeader/SessionHeader.tsx @@ -6,7 +6,8 @@ import NoteTags from '../Notes/NoteTags'; import { connect } from 'react-redux'; import SessionSort from '../SessionSort'; import { Space } from 'antd'; -import { useStore } from 'App/mstore'; +import { sessionStore, useStore } from 'App/mstore'; +import { observer } from 'mobx-react-lite'; interface Props { isEnterprise: boolean; @@ -32,7 +33,8 @@ function SessionHeader(props: Props) { const onDateChange = (e: any) => { const dateValues = e.toJSON(); - searchStore.applyFilter(dateValues); + searchStore.edit(dateValues); + searchStore.fetchSessions(); }; return ( @@ -67,4 +69,4 @@ export default connect( listCount: state.getIn(['sessions', 'total']), isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee' }) -)(SessionHeader); +)(observer(SessionHeader)); diff --git a/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx b/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx index 66c54d244..2ff305f8b 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx @@ -210,7 +210,7 @@ function SessionList(props: Props) { className="mt-4" icon="arrow-repeat" iconSize={20} - onClick={() => searchStore.fetchSessions(true)} + onClick={() => searchStore.fetchSessions()} > Refresh diff --git a/frontend/app/mstore/index.tsx b/frontend/app/mstore/index.tsx index 99fd9415b..25b20a87a 100644 --- a/frontend/app/mstore/index.tsx +++ b/frontend/app/mstore/index.tsx @@ -33,6 +33,7 @@ import ProjectsStore from './projectsStore'; export const projectStore = new ProjectsStore(); export const sessionStore = new SessionStore(); +export const searchStore = new SearchStore(); export class RootStore { dashboardStore: DashboardStore; @@ -92,7 +93,7 @@ export class RootStore { this.uiPlayerStore = new UiPlayerStore(); this.issueReportingStore = new IssueReportingStore(); this.customFieldStore = new CustomFieldStore(); - this.searchStore = new SearchStore(); + this.searchStore = searchStore; this.integrationsStore = new IntegrationsStore(); this.projectsStore = projectStore; } diff --git a/frontend/app/mstore/searchStore.ts b/frontend/app/mstore/searchStore.ts index 212c6039d..7ed896cb9 100644 --- a/frontend/app/mstore/searchStore.ts +++ b/frontend/app/mstore/searchStore.ts @@ -8,11 +8,12 @@ import { mobileConditionalFiltersMap } from 'Types/filter/newFilter'; import { List } from 'immutable'; -import { makeAutoObservable, action } from 'mobx'; +import { makeAutoObservable, action, observable } from 'mobx'; import { searchService } from 'App/services'; import Search from 'App/mstore/types/search'; -import Filter, { checkFilterValue } from 'App/mstore/types/filter'; -import FilterItem from 'MOBX/types/filterItem'; +import Filter, { checkFilterValue, IFilter } from 'App/mstore/types/filter'; +import FilterItem from 'App/mstore/types/filterItem'; +import { sessionStore } from 'App/mstore'; const PER_PAGE = 10; @@ -56,12 +57,15 @@ class SearchStore { latestList = List(); alertMetricId: number | null = null; instance = new Search(); + instanceLive = new Search(); savedSearch = new Search(); filterSearchList: any = {}; currentPage = 1; pageSize = PER_PAGE; activeTab = { name: 'All', type: 'all' }; scrollY = 0; + sessions = List(); + total: number = 0; constructor() { makeAutoObservable(this); @@ -82,8 +86,13 @@ class SearchStore { this.list = List(response.map((item: any) => new Search(item))); } - edit(instance: any) { - this.instance = instance; + async fetchSavedSearchList() { + const response = await searchService.fetchSavedSearch(); + this.list = List(response.map((item: any) => new Search(item))); + } + + edit(instance: Partial) { + this.instance = new Search(Object.assign(this.instance.toData(), instance)); this.currentPage = 1; } @@ -101,17 +110,6 @@ class SearchStore { this.apply(filter, false); } - fetchSessions(force = false) { - const filter = this.instance.toData(); - if (this.activeTab === 'bookmark' || this.activeTab === 'vault') { - filter.bookmarked = true; - } - filter.filters = filter.filters.map(filterMap); - filter.limit = this.pageSize; - filter.page = this.currentPage; - // Further logic based on force, dispatching actions, etc. - } - fetchFilterSearch(params: any) { searchService.fetchFilterSearch(params).then((response: any) => { this.filterSearchList = response.reduce((acc: any, item: any) => { @@ -165,6 +163,8 @@ class SearchStore { endDate: instance.endDate, filters: [] })); + + this.fetchSessions(); } checkForLatestSessions() { @@ -200,6 +200,13 @@ class SearchStore { oldFilter.merge(updatedFilter); } else { this.instance.filters.push(filter); + this.instance = new Search({ + ...this.instance.toData() + }); + } + + if (filter.value && filter.value[0] && filter.value[0] !== '') { + this.fetchSessions(); } } @@ -222,17 +229,43 @@ class SearchStore { // TODO } - updateFilter = (index: number, search: Partial) => { - Object.assign(this.instance!, search); + updateFilter = (index: number, search: Partial) => { + const newFilters = this.instance.filters.map((_filter: any, i: any) => { + if (i === index) { + return search; + } else { + return _filter; + } + }); + + this.instance = new Search({ + ...this.instance.toData(), + filters: newFilters + }); + }; + + removeFilter = (index: number) => { + const newFilters = this.instance.filters.filter((_filter: any, i: any) => { + return i !== index; + }); + + this.instance = new Search({ + ...this.instance.toData(), + filters: newFilters + }); }; setScrollPosition = (y: number) => { - // TODO + this.scrollY = y; }; async fetchAutoplaySessions(page: number): Promise { // TODO } + + async fetchSessions() { + await sessionStore.fetchSessions(this.instance.toSearch()); + }; } export default SearchStore; diff --git a/frontend/app/mstore/sessionStore.ts b/frontend/app/mstore/sessionStore.ts index 7dd0ce692..836486310 100644 --- a/frontend/app/mstore/sessionStore.ts +++ b/frontend/app/mstore/sessionStore.ts @@ -1,4 +1,4 @@ -import { makeAutoObservable, runInAction } from 'mobx'; +import { action, makeAutoObservable, observable, runInAction } from 'mobx'; import { sessionService } from 'App/services'; import { Note } from 'App/services/NotesService'; import Session from 'Types/session'; @@ -12,6 +12,9 @@ import { setSessionFilter, } from 'App/utils'; import { loadFile } from 'App/player/web/network/loadFiles'; +import { LAST_7_DAYS } from 'Types/app/period'; +import { Record } from 'immutable'; +import { filterMap } from 'App/mstore/searchStore'; class UserFilter { endDate: number = new Date().getTime(); @@ -437,7 +440,7 @@ export default class SessionStore { // Set Session Path setSessionPath(path = {}) { - this.sessionPath = path; + // this.sessionPath = path; } updateLastPlayedSession(sessionId: string) { diff --git a/frontend/app/mstore/types/search.ts b/frontend/app/mstore/types/search.ts index 6229f653d..8448301ff 100644 --- a/frontend/app/mstore/types/search.ts +++ b/frontend/app/mstore/types/search.ts @@ -1,6 +1,7 @@ import { DATE_RANGE_VALUES, CUSTOM_RANGE, getDateRangeFromValue } from 'App/dateRange'; -import Filter, { IFilter } from 'App/mstore/types/filter'; -import FilterItem from 'MOBX/types/filterItem'; +import Filter, { checkFilterValue, IFilter } from 'App/mstore/types/filter'; +import FilterItem from 'App/mstore/types/filterItem'; +import { action, makeAutoObservable, observable } from 'mobx'; // @ts-ignore const rangeValue = DATE_RANGE_VALUES.LAST_24_HOURS; @@ -66,6 +67,10 @@ export default class Search { eventsOrder: string; constructor(initialData?: Partial) { + makeAutoObservable(this, { + filters: observable, + addFilter: action + }); Object.assign(this, { name: '', searchId: undefined, @@ -121,8 +126,19 @@ export default class Search { toData() { const js: any = { ...this }; - js.filters = js.filters.map((filter: any) => { - return filter; + // js.filters = this.filters.map((filter: any) => { + // return new FilterItem().fromJson(filter).toJson(); + // }); + + delete js.createdAt; + delete js.key; + return js; + } + + toSearch() { + const js: any = { ...this }; + js.filters = this.filters.map((filter: any) => { + return new FilterItem(filter).toJson(); }); delete js.createdAt; diff --git a/frontend/app/services/SearchService.ts b/frontend/app/services/SearchService.ts index 56fe30fc6..7102f63cf 100644 --- a/frontend/app/services/SearchService.ts +++ b/frontend/app/services/SearchService.ts @@ -1,6 +1,12 @@ import BaseService from 'App/services/BaseService'; export default class SearchService extends BaseService { + async fetchSessions(params: any) { + const r = await this.client.post('/PROJECT_ID/sessions/search', params); + const j = await r.json(); + return j.data; + } + async fetchSavedSearchList() { const r = await this.client.get('/PROJECT_ID/saved_search'); const j = await r.json(); @@ -26,7 +32,7 @@ export default class SearchService extends BaseService { } async fetchSavedSearch() { - const r = await this.client.get('/PROJECT_ID/search'); + const r = await this.client.get('/PROJECT_ID/saved_search'); const j = await r.json(); return j.data; } @@ -36,4 +42,10 @@ export default class SearchService extends BaseService { const j = await r.json(); return j.data; } + + async fetchAutoCompleteValues(params: {}): Promise { + const r = await this.client.get('/PROJECT_ID/events/search', params); + const j = await r.json(); + return j.data; + } } diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts index 126316fca..f947d22f1 100644 --- a/frontend/app/services/index.ts +++ b/frontend/app/services/index.ts @@ -78,6 +78,6 @@ export const services = [ issueReportsService, customFieldService, integrationsService, - searchService + searchService, projectsService, ];