change(ui): duck/search wip

This commit is contained in:
Shekar Siri 2024-09-20 14:29:05 +05:30
parent 70293cd8de
commit b91868ebe1
16 changed files with 145 additions and 85 deletions

View file

@ -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)));

View file

@ -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<Props> = ({
}
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);

View file

@ -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;

View file

@ -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]: <Pointer size={18} />,
@ -65,10 +67,10 @@ const IconMap = {
[FilterKey.DURATION]: <Clock2 size={18} />,
[FilterKey.TAGGED_ELEMENT]: <SquareMousePointer size={18} />,
[FilterKey.METADATA]: <ContactRound size={18} />,
[FilterKey.UTM_SOURCE]: <CornerDownRight size={18}/>,
[FilterKey.UTM_MEDIUM]: <Layers size={18}/>,
[FilterKey.UTM_CAMPAIGN]: <Megaphone size={18}/>,
[FilterKey.FEATURE_FLAG]: <Flag size={18}/>,
[FilterKey.UTM_SOURCE]: <CornerDownRight size={18} />,
[FilterKey.UTM_MEDIUM]: <Layers size={18} />,
[FilterKey.UTM_CAMPAIGN]: <Megaphone size={18} />,
[FilterKey.FEATURE_FLAG]: <Flag size={18} />
};
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<string, any>) => {
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 <Icon name={filter.icon} size={16} />
}
return IconMap[filter.key];
} else return <Icon name={filter.icon} size={16} />;
};
return (
<div
className={stl.wrapper}
style={{ width: '480px', maxHeight: '380px', overflowY: 'auto', borderRadius:'.5rem', }}
style={{ width: '480px', maxHeight: '380px', overflowY: 'auto', borderRadius: '.5rem' }}
>
<div
className={searchQuery && !isResultEmpty ? 'mb-6' : ''}
@ -276,16 +280,17 @@ function FilterModal(props: Props) {
export default connect((state: any, props: any) => {
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));

View file

@ -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}

View file

@ -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;

View file

@ -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]);

View file

@ -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
});

View file

@ -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));

View file

@ -210,7 +210,7 @@ function SessionList(props: Props) {
className="mt-4"
icon="arrow-repeat"
iconSize={20}
onClick={() => searchStore.fetchSessions(true)}
onClick={() => searchStore.fetchSessions()}
>
Refresh
</Button>

View file

@ -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;
}

View file

@ -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<Search>) {
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<Search>) => {
Object.assign(this.instance!, search);
updateFilter = (index: number, search: Partial<IFilter>) => {
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<void> {
// TODO
}
async fetchSessions() {
await sessionStore.fetchSessions(this.instance.toSearch());
};
}
export default SearchStore;

View file

@ -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) {

View file

@ -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<ISearch>) {
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;

View file

@ -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<any> {
const r = await this.client.get('/PROJECT_ID/events/search', params);
const j = await r.json();
return j.data;
}
}

View file

@ -78,6 +78,6 @@ export const services = [
issueReportsService,
customFieldService,
integrationsService,
searchService
searchService,
projectsService,
];