change(ui): search query params improvements
This commit is contained in:
parent
251d727375
commit
fa65f8be41
4 changed files with 200 additions and 120 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue