change(ui): search query params improvements

This commit is contained in:
Shekar Siri 2024-11-26 11:17:12 +01:00
parent 251d727375
commit fa65f8be41
4 changed files with 200 additions and 120 deletions

View file

@ -11,39 +11,41 @@ import { useStore } from 'App/mstore';
import { debounce } from 'App/utils';
import useSessionSearchQueryHandler from 'App/hooks/useSessionSearchQueryHandler';
let debounceFetch: any = () => {
};
let debounceFetch: () => void;
function SessionSearch() {
const { tagWatchStore, aiFiltersStore, searchStore, customFieldStore, projectsStore } = useStore();
const appliedFilter = searchStore.instance;
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 hasEvents = appliedFilter.filters.some((i: any) => i.isEvent);
const hasFilters = appliedFilter.filters.some((i: any) => !i.isEvent);
const saveRequestPayloads = projectsStore.instance?.saveRequestPayloads ?? false;
useSessionSearchQueryHandler({
appliedFilter,
loading: metaLoading,
onBeforeLoad: async () => {
const tags = await tagWatchStore.getTags();
if (tags) {
addOptionsToFilter(
FilterKey.TAGGED_ELEMENT,
tags.map((tag) => ({
label: tag.name,
value: tag.tagId.toString()
}))
);
searchStore.refreshFilterOptions();
try {
const tags = await tagWatchStore.getTags();
if (tags) {
addOptionsToFilter(
FilterKey.TAGGED_ELEMENT,
tags.map((tag) => ({
label: tag.name,
value: tag.tagId.toString()
}))
);
searchStore.refreshFilterOptions();
}
} catch (error) {
console.error('Error during onBeforeLoad:', error);
}
}
});
useEffect(() => {
debounceFetch = debounce(() => searchStore.fetchSessions(), 500);
// void searchStore.fetchSessions(true)
}, []);
}, [searchStore]);
useEffect(() => {
debounceFetch();
@ -85,49 +87,47 @@ function SessionSearch() {
};
const showPanel = hasEvents || hasFilters || aiFiltersStore.isLoading;
return !metaLoading ? (
<>
{showPanel ? (
<div className="border bg-white rounded-lg mt-4">
<div className="p-5">
{aiFiltersStore.isLoading ? (
<div className={'font-semibold flex items-center gap-2 mb-2'}>
<AnimatedSVG name={ICONS.LOADER} size={18} />
<span>Translating your query into search steps...</span>
</div>
) : null}
{hasEvents || hasFilters ? (
<FilterList
filter={appliedFilter}
onUpdateFilter={onUpdateFilter}
onRemoveFilter={onRemoveFilter}
onChangeEventsOrder={onChangeEventsOrder}
onFilterMove={onFilterMove}
saveRequestPayloads={saveRequestPayloads}
/>
) : null}
</div>
{hasEvents || hasFilters ? (
<div className="border-t px-5 py-1 flex items-center -mx-2">
<div>
<FilterSelection filter={undefined} onFilterClick={onAddFilter}>
<Button variant="text-primary" className="mr-2" icon="plus">
ADD STEP
</Button>
</FilterSelection>
</div>
<div className="ml-auto flex items-center">
<SaveFilterButton />
</div>
</div>
) : null}
if (metaLoading) return null;
if (!showPanel) return null;
return (
<div className="border bg-white rounded-lg mt-4">
<div className="p-5">
{aiFiltersStore.isLoading ? (
<div className={'font-semibold flex items-center gap-2 mb-2'}>
<AnimatedSVG name={ICONS.LOADER} size={18} />
<span>Translating your query into search steps...</span>
</div>
) : null}
{hasEvents || hasFilters ? (
<FilterList
filter={appliedFilter}
onUpdateFilter={onUpdateFilter}
onRemoveFilter={onRemoveFilter}
onChangeEventsOrder={onChangeEventsOrder}
onFilterMove={onFilterMove}
saveRequestPayloads={saveRequestPayloads}
/>
) : null}
</div>
{hasEvents || hasFilters ? (
<div className="border-t px-5 py-1 flex items-center -mx-2">
<div>
<FilterSelection filter={undefined} onFilterClick={onAddFilter}>
<Button variant="text-primary" className="mr-2" icon="plus">
ADD STEP
</Button>
</FilterSelection>
</div>
<div className="ml-auto flex items-center">
<SaveFilterButton />
</div>
</div>
) : (
<></>
)}
</>
) : null;
) : null}
</div>
);
}
export default observer(SessionSearch);

View file

@ -6,46 +6,59 @@ import Search from '@/mstore/types/search';
import { getFilterFromJson } from 'Types/filter/newFilter';
interface Props {
onBeforeLoad?: () => Promise<any>;
appliedFilter: any;
onBeforeLoad?: () => Promise<void>;
appliedFilter: Record<string, any>;
loading: boolean;
}
const useSessionSearchQueryHandler = (props: Props) => {
const useSessionSearchQueryHandler = ({ onBeforeLoad, appliedFilter, loading }: Props) => {
const { searchStore } = useStore();
const [beforeHookLoaded, setBeforeHookLoaded] = useState(!props.onBeforeLoad);
const { appliedFilter, loading } = props;
const [beforeHookLoaded, setBeforeHookLoaded] = useState(!onBeforeLoad);
const history = useHistory();
// Apply filter from the query string when the component mounts
useEffect(() => {
const applyFilterFromQuery = async () => {
if (!loading && !searchStore.urlParsed) {
if (props.onBeforeLoad) {
await props.onBeforeLoad();
setBeforeHookLoaded(true);
}
try {
if (onBeforeLoad) {
await onBeforeLoad();
setBeforeHookLoaded(true);
}
const converter = JsonUrlConverter.urlParamsToJson(history.location.search);
const json: any = getFilterFromJson(converter.toJSON());
const filter = new Search(json);
searchStore.applyFilter(filter, true);
searchStore.setUrlParsed()
const converter = JsonUrlConverter.urlParamsToJson(history.location.search);
const json = getFilterFromJson(converter.toJSON());
const filter = new Search(json);
searchStore.applyFilter(filter, true);
searchStore.setUrlParsed();
} catch (error) {
console.error('Error applying filter from query:', error);
}
}
};
void applyFilterFromQuery();
}, [loading]);
}, [loading, onBeforeLoad, searchStore, history.location.search]);
// Update the URL whenever the appliedFilter changes
useEffect(() => {
const generateUrlQuery = () => {
const updateUrlWithFilter = () => {
if (!loading && beforeHookLoaded) {
const converter = JsonUrlConverter.jsonToUrlParams(appliedFilter);
history.replace({ search: converter });
const query = JsonUrlConverter.jsonToUrlParams(appliedFilter);
history.replace({ search: query });
}
};
generateUrlQuery();
}, [appliedFilter, loading, beforeHookLoaded]);
updateUrlWithFilter();
}, [appliedFilter, loading, beforeHookLoaded, history]);
// Ensure the URL syncs on remount if already parsed
useEffect(() => {
if (searchStore.urlParsed) {
const query = JsonUrlConverter.jsonToUrlParams(appliedFilter);
history.replace({ search: query });
}
}, [appliedFilter, searchStore.urlParsed, history]);
return null;
};

View file

@ -1,7 +1,8 @@
import { DATE_RANGE_VALUES, CUSTOM_RANGE, getDateRangeFromValue } from 'App/dateRange';
import Filter, { checkFilterValue, IFilter } from 'App/mstore/types/filter';
import { CUSTOM_RANGE, DATE_RANGE_VALUES, getDateRangeFromValue } from 'App/dateRange';
import Filter, { IFilter } from 'App/mstore/types/filter';
import FilterItem from 'App/mstore/types/filterItem';
import { action, makeAutoObservable, observable } from 'mobx';
import { makeAutoObservable, observable } from 'mobx';
import { LAST_24_HOURS, LAST_30_DAYS, LAST_7_DAYS } from 'Types/app/period';
// @ts-ignore
const rangeValue = DATE_RANGE_VALUES.LAST_24_HOURS;
@ -69,7 +70,7 @@ export default class Search {
constructor(initialData?: Partial<ISearch>) {
makeAutoObservable(this, {
filters: observable,
filters: observable
});
Object.assign(this, {
name: '',
@ -142,11 +143,48 @@ export default class Search {
return new FilterItem(filter).toJson();
});
const { startDate, endDate } = this.getDateRange(js.rangeName, js.startDate, js.endDate);
js.startDate = startDate;
js.endDate = endDate;
delete js.createdAt;
delete js.key;
return js;
}
private getDateRange(rangeName: string, customStartDate: number, customEndDate: number): {
startDate: number;
endDate: number
} {
let endDate = new Date().getTime();
let startDate: number;
switch (rangeName) {
case LAST_7_DAYS:
startDate = endDate - 7 * 24 * 60 * 60 * 1000;
break;
case LAST_30_DAYS:
startDate = endDate - 30 * 24 * 60 * 60 * 1000;
break;
case CUSTOM_RANGE:
if (!customStartDate || !customEndDate) {
throw new Error('Start date and end date must be provided for CUSTOM_RANGE.');
}
startDate = customStartDate;
endDate = customEndDate;
break;
case LAST_24_HOURS:
default:
startDate = endDate - 24 * 60 * 60 * 1000;
}
return {
startDate,
endDate
};
}
fromJS({ eventsOrder, filters, events, custom, ...filterData }: any) {
let startDate, endDate;
const rValue = filterData.rangeValue || rangeValue;
@ -176,3 +214,4 @@ export default class Search {
});
}
}

View file

@ -1,5 +1,4 @@
import Period, { CUSTOM_RANGE } from 'Types/app/period';
import { filtersMap } from 'Types/filter/newFilter';
class Filter {
@ -25,24 +24,32 @@ class Filter {
}
}
class InputJson {
const DEFAULT_SORT = 'startTs';
const DEFAULT_ORDER = 'desc';
const DEFAULT_EVENTS_ORDER = 'then';
export class InputJson {
filters: Filter[];
rangeValue: string;
startDate: number;
endDate: number;
startDate?: number;
endDate?: number;
sort: string;
order: string;
eventsOrder: string;
constructor(filters: Filter[], rangeValue: string, startDate: number, endDate: number, sort: string, order: string, eventsOrder: string) {
constructor(
filters: Filter[],
rangeValue: string,
sort: string,
order: string,
eventsOrder: string,
startDate?: string | number,
endDate?: string | number
) {
this.filters = filters;
// .map((f: any) => {
// const subFilters = f.filters ? f.filters.map((sf: any) => new Filter(sf.key, sf.operator, sf.value, sf.filters)) : undefined;
// return new Filter(f.key, f.operator, f.value, subFilters);
// });
this.rangeValue = rangeValue;
this.startDate = startDate;
this.endDate = endDate;
this.startDate = startDate ? +startDate : undefined;
this.endDate = endDate ? +endDate : undefined;
this.sort = sort;
this.order = order;
this.eventsOrder = eventsOrder;
@ -50,17 +57,28 @@ class InputJson {
toJSON() {
return {
filters: this.filters.map(f => f.toJSON()),
filters: this.filters.map((f) => f.toJSON()),
rangeValue: this.rangeValue,
startDate: this.startDate,
endDate: this.endDate,
startDate: this.startDate ?? null,
endDate: this.endDate ?? null,
sort: this.sort,
order: this.order,
eventsOrder: this.eventsOrder
};
}
}
fromJSON(json: Record<string, any>): InputJson {
return new InputJson(
json.filters.map((f: any) => new Filter(f.key, f.operator, f.value, f.filters)),
json.rangeValue,
json.sort,
json.order,
json.eventsOrder,
json.startDate,
json.endDate
);
}
}
export class JsonUrlConverter {
static keyMap = {
@ -76,35 +94,46 @@ export class JsonUrlConverter {
filters: 'f'
};
static getDateRangeValues(rangeValue: string, startDate: number | undefined, endDate: number | undefined): [number, number] {
if (rangeValue === 'CUSTOM_RANGE') {
return [startDate!, endDate!];
static getDateRangeValues(
rangeValue: string,
startDate: string | null,
endDate: string | null
): [string, string] {
if (rangeValue === CUSTOM_RANGE) {
return [startDate || '', endDate || ''];
}
const period = Period({ rangeName: rangeValue });
const period: any = Period({ rangeName: rangeValue });
return [period.start, period.end];
}
static jsonToUrlParams(json: InputJson): string {
static jsonToUrlParams(json: Record<string, any>): string {
const params = new URLSearchParams();
const addFilterParams = (filter: Filter, prefix: string) => {
params.append(`${prefix}${this.keyMap.key}`, filter.key);
params.append(`${prefix}${this.keyMap.operator}`, filter.operator);
if (filter.value) {
filter.value.forEach((v, i) => params.append(`${prefix}${this.keyMap.value}[${i}]`, v || ''));
}
if (filter.filters) {
filter.filters.forEach((f, i) => addFilterParams(f, `${prefix}${this.keyMap.filters}[${i}].`));
}
filter.value?.forEach((v, i) =>
params.append(`${prefix}${this.keyMap.value}[${i}]`, v || '')
);
filter.filters?.forEach((f, i) =>
addFilterParams(f, `${prefix}${this.keyMap.filters}[${i}].`)
);
};
json.filters.forEach((filter, index) => addFilterParams(filter, `${this.keyMap.filters}[${index}].`));
const rangeValues = this.getDateRangeValues(json.rangeValue, json.startDate, json.endDate);
json.filters.forEach((filter: any, index: number) =>
addFilterParams(filter, `${this.keyMap.filters}[${index}].`)
);
params.append(this.keyMap.rangeValue, json.rangeValue);
params.append(this.keyMap.startDate, rangeValues[0].toString());
params.append(this.keyMap.endDate, rangeValues[1].toString());
if (json.rangeValue === CUSTOM_RANGE) {
const rangeValues = this.getDateRangeValues(
json.rangeValue,
json.startDate?.toString() || null,
json.endDate?.toString() || null
);
params.append(this.keyMap.startDate, rangeValues[0]);
params.append(this.keyMap.endDate, rangeValues[1]);
}
params.append(this.keyMap.sort, json.sort);
params.append(this.keyMap.order, json.order);
params.append(this.keyMap.eventsOrder, json.eventsOrder);
@ -130,7 +159,7 @@ export class JsonUrlConverter {
filters.push(getFilterParams(`${prefix}${this.keyMap.filters}[${index}].`));
index++;
}
return new Filter(key, operator, value.length ? value : '', filters.length ? filters : []);
return new Filter(key, operator, value.length ? value : [], filters.length ? filters : []);
};
const filters: Filter[] = [];
@ -142,21 +171,20 @@ export class JsonUrlConverter {
const rangeValue = params.get(this.keyMap.rangeValue) || 'LAST_24_HOURS';
const rangeValues = this.getDateRangeValues(rangeValue, params.get(this.keyMap.startDate), params.get(this.keyMap.endDate));
const startDate = rangeValues[0];
const endDate = rangeValues[1];
return new InputJson(
filters,
rangeValue,
startDate,
endDate,
params.get(this.keyMap.sort) || 'startTs',
params.get(this.keyMap.order) || 'desc',
params.get(this.keyMap.eventsOrder) || 'then'
params.get(this.keyMap.sort) || DEFAULT_SORT,
params.get(this.keyMap.order) || DEFAULT_ORDER,
params.get(this.keyMap.eventsOrder) || DEFAULT_EVENTS_ORDER,
rangeValues[0],
rangeValues[1]
);
}
}
// Example usage
// const urlParams = '?f[0].k=click&f[0].op=on&f[0].v[0]=Refresh&f[1].k=fetch&f[1].op=is&f[1].v[0]=&f[1].f[0].k=fetchUrl&f[1].f[0].op=is&f[1].f[0].v[0]=/g/collect&f[1].f[1].k=fetchStatusCode&f[1].f[1].op=>=&f[1].f[1].v[0]=400&f[1].f[2].k=fetchMethod&f[1].f[2].op=is&f[1].f[2].v[0]=&f[1].f[3].k=fetchDuration&f[1].f[3].op==&f[1].f[3].v[0]=&f[1].f[4].k=fetchRequestBody&f[1].f[4].op=is&f[1].f[4].v[0]=&f[1].f[5].k=fetchResponseBody&f[1].f[5].op=is&f[1].f[5].v[0]=&rv=LAST_24_HOURS&sd=1731343412555&ed=1731429812555&s=startTs&o=desc&st=false&eo=then';
// const parsedJson = JsonUrlConverter.urlParamsToJson(urlParams);