diff --git a/resources/scripts/api/account/activity.ts b/resources/scripts/api/account/activity.ts index 7746c8a4..7d56061e 100644 --- a/resources/scripts/api/account/activity.ts +++ b/resources/scripts/api/account/activity.ts @@ -4,11 +4,12 @@ import { ActivityLog, Transformers } from '@definitions/user'; import { AxiosError } from 'axios'; import http, { PaginatedResult, QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; import { toPaginatedSet } from '@definitions/helpers'; +import useFilteredObject from '@/plugins/useFilteredObject'; export type ActivityLogFilters = QueryBuilderParams<'ip' | 'event', 'timestamp'>; const useActivityLogs = (filters?: ActivityLogFilters, config?: ConfigInterface<PaginatedResult<ActivityLog>, AxiosError>): responseInterface<PaginatedResult<ActivityLog>, AxiosError> => { - const key = useUserSWRContentKey([ 'account', 'activity', JSON.stringify(filters) ]); + const key = useUserSWRContentKey([ 'account', 'activity', JSON.stringify(useFilteredObject(filters || {})) ]); return useSWR<PaginatedResult<ActivityLog>>(key, async () => { const { data } = await http.get('/api/client/account/activity', { diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts index 2dc54a60..119f074a 100644 --- a/resources/scripts/api/http.ts +++ b/resources/scripts/api/http.ts @@ -88,7 +88,7 @@ export interface FractalPaginatedResponse extends FractalResponseList { total_pages: number; /* eslint-enable camelcase */ }; - } + }; } export interface PaginatedResult<T> { diff --git a/resources/scripts/helpers.ts b/resources/scripts/helpers.ts index 67d07942..fe6cc4f5 100644 --- a/resources/scripts/helpers.ts +++ b/resources/scripts/helpers.ts @@ -65,3 +65,13 @@ export function hashToPath (hash: string): string { export function formatIp (ip: string): string { return /([a-f0-9:]+:+)+[a-f0-9]+/.test(ip) ? `[${ip}]` : ip; } + +// eslint-disable-next-line @typescript-eslint/ban-types +export const isObject = (o: unknown): o is {} => typeof o === 'object' && o !== null; + +// eslint-disable-next-line @typescript-eslint/ban-types +export const isEmptyObject = (o: {}): boolean => + Object.keys(o).length === 0 && Object.getPrototypeOf(o) === Object.prototype; + +// eslint-disable-next-line @typescript-eslint/ban-types +export const getObjectKeys = <T extends {}> (o: T): Array<keyof T> => Object.keys(o) as Array<keyof T>; diff --git a/resources/scripts/plugins/useFilteredObject.ts b/resources/scripts/plugins/useFilteredObject.ts new file mode 100644 index 00000000..440e422f --- /dev/null +++ b/resources/scripts/plugins/useFilteredObject.ts @@ -0,0 +1,22 @@ +/** + * Similar to "withQueryBuilderParams" except this function filters out any null, + * undefined, or empty string key values. This allows the parameters to be used for + * caching without having to account for all of the different data combinations. + */ +import { isEmptyObject, isObject } from '@/helpers'; + +// eslint-disable-next-line @typescript-eslint/ban-types +export default <T extends {}>(data: T): T => { + const empty = [ undefined, null, '' ] as unknown[]; + + const removeEmptyValues = (input: T): T => + Object.entries(input) + .filter(([ _, value ]) => !empty.includes(value)) + .reduce((obj, [ k, v ]) => { + const parsed = isObject(v) ? removeEmptyValues(v as any) : v; + + return isObject(parsed) && isEmptyObject(parsed) ? obj : { ...obj, [k]: parsed }; + }, {} as T); + + return removeEmptyValues(data); +};