1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

refactor: paginated data hook (#6333)

This commit is contained in:
Mateusz Kwasniewski 2024-02-26 11:24:41 +01:00 committed by GitHub
parent 1633722877
commit d1e93228a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 114 additions and 139 deletions

View File

@ -22,8 +22,8 @@ import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
import { ProjectApplicationSchema } from '../../../openapi';
import mapValues from 'lodash.mapvalues';
import {
useProjectApplications,
DEFAULT_PAGE_LIMIT,
useProjectApplications,
} from 'hooks/api/getters/useProjectApplications/useProjectApplications';
import { StringArrayCell } from 'component/common/Table/cells/StringArrayCell';
import { SdkCell } from './SdkCell';
@ -51,12 +51,11 @@ export const ProjectApplications = () => {
applications = [],
total,
loading,
refetch: refetchApplications,
} = useProjectApplications(
projectId,
mapValues(encodeQueryParams(stateConfig, tableState), (value) =>
value ? `${value}` : undefined,
),
projectId,
);
const setSearchValue = (query = '') => {

View File

@ -1,62 +1,10 @@
import useSWR, { mutate, SWRConfiguration } from 'swr';
import { useEffect, useState } from 'react';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
import { ApplicationsSchema, GetApplicationsParams } from '../../../../openapi';
import { useClearSWRCache } from '../../../useClearSWRCache';
import { ApplicationsSchema } from '../../../../openapi';
import { createPaginatedHook } from '../usePaginatedData/usePaginatedData';
interface IUseApplicationsOutput extends ApplicationsSchema {
refetchApplications: () => void;
loading: boolean;
error?: Error;
}
const PREFIX_KEY = 'api/admin/metrics/applications?';
const useApplications = (
params: GetApplicationsParams = {},
options: SWRConfiguration = {},
): IUseApplicationsOutput => {
const urlSearchParams = new URLSearchParams(
Array.from(
Object.entries(params)
.filter(([_, value]) => !!value)
.map(([key, value]) => [key, value.toString()]),
),
).toString();
const KEY = `${PREFIX_KEY}${urlSearchParams}`;
useClearSWRCache(KEY, PREFIX_KEY);
const fetcher = async () => {
return fetch(formatApiPath(KEY), {
method: 'GET',
})
.then(handleErrorResponses('Applications data'))
.then((res) => res.json());
};
const { data, error } = useSWR(KEY, fetcher, {
...options,
});
const [loading, setLoading] = useState(!error && !data);
const refetchApplications = () => {
mutate(KEY);
};
useEffect(() => {
setLoading(!error && !data);
}, [data, error]);
return {
applications: data?.applications || [],
total: data?.total || 0,
error,
loading,
refetchApplications,
};
};
const prefixKey = 'api/admin/metrics/applications?';
const useApplications = createPaginatedHook<ApplicationsSchema>(
{ applications: [], total: 0 },
prefixKey,
);
export default useApplications;

View File

@ -0,0 +1,40 @@
import { testServerRoute, testServerSetup } from 'utils/testServer';
import { render } from 'utils/testRenderer';
import { screen } from '@testing-library/react';
import { createPaginatedHook } from './usePaginatedData';
import { FC } from 'react';
import { http, HttpResponse } from 'msw';
const server = testServerSetup();
const usePaginatedData = createPaginatedHook<{ total: number; items: string }>(
{ total: 0, items: 'default' },
'/api/project/my-project?',
);
const TestComponent: FC<{ query: string }> = ({ query }) => {
const { items, total } = usePaginatedData({ query });
return (
<span>
{items} ({total})
</span>
);
};
test('Pass query params to server and return total', async () => {
testServerRoute(server, '/api/admin/ui-config', {});
server.use(
http.get('/api/project/my-project', ({ request }) => {
const url = new URL(request.url);
return HttpResponse.json({
items: `result${url.searchParams.get('query')}`,
total: 10,
});
}),
);
render(<TestComponent query='value' />);
await screen.findByText('default (0)');
const element = await screen.findByText('resultvalue (10)');
});

View File

@ -0,0 +1,54 @@
import useSWR, { SWRConfiguration } from 'swr';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
import { useClearSWRCache } from '../../../useClearSWRCache';
type GenericSearchOutput<T> = {
loading: boolean;
initialLoad: boolean;
error: string;
total: number;
} & T;
export function createPaginatedHook<T extends { total?: number }>(
customFallbackData: T,
defaultPrefixKey = '',
) {
return (
params: Record<string, any> = {},
dynamicPrefixKey: string = '',
options: SWRConfiguration = {},
): GenericSearchOutput<T> => {
const urlSearchParams = new URLSearchParams(
Array.from(
Object.entries(params)
.filter(([_, value]) => !!value)
.map(([key, value]) => [key, value.toString()]),
),
).toString();
const prefix = dynamicPrefixKey || defaultPrefixKey;
const KEY = `${prefix}${urlSearchParams}`;
useClearSWRCache(KEY, prefix);
const fetcher = async () => {
return fetch(formatApiPath(KEY), {
method: 'GET',
})
.then(handleErrorResponses('Paginated data'))
.then((res) => res.json());
};
const { data, error, isLoading } = useSWR(KEY, fetcher, {
...options,
});
const returnData = data || customFallbackData;
return {
...returnData,
total: data?.total || 0,
error,
loading: isLoading,
};
};
}

View File

@ -1,84 +1,18 @@
import useSWR, { SWRConfiguration } from 'swr';
import { useCallback } from 'react';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
import {
GetProjectApplicationsParams,
ProjectApplicationsSchema,
} from 'openapi';
import { useClearSWRCache } from 'hooks/useClearSWRCache';
import { ProjectApplicationsSchema } from 'openapi';
import { createPaginatedHook } from '../usePaginatedData/usePaginatedData';
type UseProjectApplicationsOutput = {
loading: boolean;
error: string;
refetch: () => void;
} & ProjectApplicationsSchema;
const fallbackData: ProjectApplicationsSchema = {
applications: [],
total: 0,
};
export const DEFAULT_PAGE_LIMIT = 25;
const getPrefixKey = (projectId: string) => {
return `api/admin/projects/${projectId}/applications?`;
};
const useParameterizedProjectApplications =
createPaginatedHook<ProjectApplicationsSchema>({
applications: [],
total: 0,
});
const createProjectApplications = () => {
return (
projectId: string,
params: GetProjectApplicationsParams,
options: SWRConfiguration = {},
): UseProjectApplicationsOutput => {
const { KEY, fetcher } = getProjectApplicationsFetcher(
projectId,
params,
);
const { data, error, mutate, isLoading } =
useSWR<ProjectApplicationsSchema>(KEY, fetcher, options);
const refetch = useCallback(() => {
mutate();
}, [mutate]);
const returnData = data || fallbackData;
return {
...returnData,
loading: isLoading,
error,
refetch,
};
};
};
export const DEFAULT_PAGE_LIMIT = 25;
export const useProjectApplications = createProjectApplications();
const getProjectApplicationsFetcher = (
export const useProjectApplications = (
params: Record<string, any>,
projectId: string,
params: GetProjectApplicationsParams,
) => {
const urlSearchParams = new URLSearchParams(
Array.from(
Object.entries(params)
.filter(([_, value]) => !!value)
.map(([key, value]) => [key, value.toString()]), // TODO: parsing non-string parameters
),
).toString();
const KEY = `${getPrefixKey(projectId)}${urlSearchParams}`;
useClearSWRCache(KEY, getPrefixKey(projectId));
const fetcher = () => {
const path = formatApiPath(KEY);
return fetch(path, {
method: 'GET',
})
.then(handleErrorResponses('Feature search'))
.then((res) => res.json());
};
return {
fetcher,
KEY,
};
};
) => useParameterizedProjectApplications(params, getPrefixKey(projectId));