Skip to content

Commit

Permalink
[frontend] Remove duplicate requests for pagination & filters (#1682)
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaumejparis authored Oct 18, 2024
1 parent c377ffa commit b0a5e1a
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,6 @@ const PaginationComponentV2 = <T extends object>({
const [options, setOptions] = useState<OptionPropertySchema[]>([]);

useEffect(() => {
// Retrieve input from uri
if (queryableHelpers.uriHelpers) {
queryableHelpers.uriHelpers.retrieveFromUri();
}

if (entityPrefix) {
useFilterableProperties(entityPrefix, availableFilterNames).then((propertySchemas: PropertySchemaDTO[]) => {
const newOptions = propertySchemas.filter((property) => property.schema_property_name !== MITRE_FILTER_KEY)
Expand Down
46 changes: 28 additions & 18 deletions openbas-front/src/components/common/queryable/uri/useUriState.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
import * as qs from 'qs';
import { useLocation, useNavigate } from 'react-router-dom';
import { useSearchParams } from 'react-router-dom';
import { useEffect, useState } from 'react';
import * as R from 'ramda';
import { z } from 'zod';
import { UriHelpers } from './UriHelpers';
import type { SearchPaginationInput } from '../../../../utils/api-types';
import { buildSearchPagination, SearchPaginationInputSchema } from '../QueryableUtils';

export const retrieveFromUri = (localStorageKey: string, searchParams: URLSearchParams): SearchPaginationInput | null => {
const encodedParams = searchParams.get('query') || '';
const params = atob(encodedParams);
const paramsJson = qs.parse(params, { allowEmptyArrays: true }) as unknown as SearchPaginationInput & { key: string };
if (!R.isEmpty(paramsJson) && paramsJson.key === localStorageKey) {
try {
const parse = SearchPaginationInputSchema.parse(paramsJson);
return buildSearchPagination(parse);
} catch (err) {
if (err instanceof z.ZodError) {
// eslint-disable-next-line no-console
console.log(`Validation error: the uri has not a valid format ${err.issues}`);
return null;
}
}
}
return null;
};

const useUriState = (localStorageKey: string, initSearchPaginationInput: SearchPaginationInput, onChange: (input: SearchPaginationInput) => void) => {
const location = useLocation();
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();

const [input, setInput] = useState<SearchPaginationInput>(initSearchPaginationInput);

const helpers: UriHelpers = {
retrieveFromUri: () => {
const encodedParams = location.search?.startsWith('?') ? location.search.substring(1) : '';
const params = atob(encodedParams);
const paramsJson = qs.parse(params) as unknown as SearchPaginationInput & { key: string };
if (!R.isEmpty(paramsJson) && paramsJson.key === localStorageKey) {
try {
const parse = SearchPaginationInputSchema.parse(paramsJson);
setInput(buildSearchPagination(parse));
} catch (err) {
if (err instanceof z.ZodError) {
// eslint-disable-next-line no-console
console.log(`Validation error: the uri has not a valid format ${err.issues}`);
}
}
const built = retrieveFromUri(localStorageKey, searchParams);
if (built) {
setInput(built);
}
},
updateUri: () => {
const params = qs.stringify({ key: localStorageKey, ...initSearchPaginationInput });
const params = qs.stringify({ ...initSearchPaginationInput, key: localStorageKey }, { allowEmptyArrays: true });
const encodedParams = btoa(params);
navigate(`?${encodedParams}`);
setSearchParams({
query: encodedParams,
});
},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { useLocalStorage } from 'usehooks-ts';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import * as R from 'ramda';
import useFiltersState from './filter/useFiltersState';
import type { FilterGroup, SearchPaginationInput, SortField } from '../../../utils/api-types';
import useTextSearchState from './textSearch/useTextSearchState';
import usPaginationState from './pagination/usPaginationState';
import { QueryableHelpers } from './QueryableHelpers';
import useSortState from './sort/useSortState';
import useUriState from './uri/useUriState';
import useUriState, { retrieveFromUri } from './uri/useUriState';
import { buildSearchPagination } from './QueryableUtils';

const buildUseQueryable = (
Expand Down Expand Up @@ -70,9 +72,23 @@ export const useQueryable = (initSearchPaginationInput: Partial<SearchPagination
};

export const useQueryableWithLocalStorage = (localStorageKey: string, initSearchPaginationInput: Partial<SearchPaginationInput>) => {
const [searchParams] = useSearchParams();
const finalSearchPaginationInput: SearchPaginationInput = buildSearchPagination(initSearchPaginationInput);
const searchPaginationInputFromUri = retrieveFromUri(localStorageKey, searchParams);

const [searchPaginationInput, setSearchPaginationInput] = useLocalStorage<SearchPaginationInput>(localStorageKey, finalSearchPaginationInput);
const [searchPaginationInputFromLocalStorage, setSearchPaginationInputFromLocalStorage] = useLocalStorage<SearchPaginationInput>(
localStorageKey,
searchPaginationInputFromUri ?? finalSearchPaginationInput,
);
// add a transitional state to avoid re render because of useLocalStorage hook
const [searchPaginationInput, setSearchPaginationInput] = useState(searchPaginationInputFromUri ?? searchPaginationInputFromLocalStorage);

return buildUseQueryable(localStorageKey, initSearchPaginationInput, searchPaginationInput, setSearchPaginationInput);
useEffect(() => {
// check deep changes between state from local storage and transitional state
if (!R.equals(searchPaginationInputFromLocalStorage, searchPaginationInput)) {
setSearchPaginationInput(searchPaginationInputFromLocalStorage);
}
}, [searchPaginationInputFromLocalStorage]);

return buildUseQueryable(localStorageKey, initSearchPaginationInput, searchPaginationInput, setSearchPaginationInputFromLocalStorage);
};
9 changes: 7 additions & 2 deletions openbas-front/src/utils/hooks/useDataLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const ERROR_2S_DELAY = 2000;
const ERROR_8S_DELAY = 8000;
const ERROR_30S_DELAY = 30000;

// pristine is used to avoid duplicate requests at the launch of the app
let pristine = true;
let sseClient;
let lastPingDate = new Date().getTime();
const listeners = new Map();
Expand All @@ -31,7 +33,10 @@ const useDataLoader = (loader = () => {}, refetchArg = []) => {
sseConnect();
}
}, EVENT_TRY_DELAY);
sseClient.addEventListener('open', () => [...listeners.keys()].forEach((load) => load()));
sseClient.addEventListener('open', () => {
pristine = false;
[...listeners.keys()].forEach((load) => load());
});
sseClient.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
if (data.listened) {
Expand Down Expand Up @@ -80,7 +85,7 @@ const useDataLoader = (loader = () => {}, refetchArg = []) => {
listeners.set(loader, '');
if (EventSource !== undefined && sseClient === undefined) {
sseClient = sseConnect();
} else {
} else if (!pristine) {
const load = async () => {
await loader();
};
Expand Down

0 comments on commit b0a5e1a

Please sign in to comment.