mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-06 01:15:28 +02:00
refactor: paginated data hook (#6333)
This commit is contained in:
parent
1633722877
commit
d1e93228a3
@ -22,8 +22,8 @@ import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
|||||||
import { ProjectApplicationSchema } from '../../../openapi';
|
import { ProjectApplicationSchema } from '../../../openapi';
|
||||||
import mapValues from 'lodash.mapvalues';
|
import mapValues from 'lodash.mapvalues';
|
||||||
import {
|
import {
|
||||||
useProjectApplications,
|
|
||||||
DEFAULT_PAGE_LIMIT,
|
DEFAULT_PAGE_LIMIT,
|
||||||
|
useProjectApplications,
|
||||||
} from 'hooks/api/getters/useProjectApplications/useProjectApplications';
|
} from 'hooks/api/getters/useProjectApplications/useProjectApplications';
|
||||||
import { StringArrayCell } from 'component/common/Table/cells/StringArrayCell';
|
import { StringArrayCell } from 'component/common/Table/cells/StringArrayCell';
|
||||||
import { SdkCell } from './SdkCell';
|
import { SdkCell } from './SdkCell';
|
||||||
@ -51,12 +51,11 @@ export const ProjectApplications = () => {
|
|||||||
applications = [],
|
applications = [],
|
||||||
total,
|
total,
|
||||||
loading,
|
loading,
|
||||||
refetch: refetchApplications,
|
|
||||||
} = useProjectApplications(
|
} = useProjectApplications(
|
||||||
projectId,
|
|
||||||
mapValues(encodeQueryParams(stateConfig, tableState), (value) =>
|
mapValues(encodeQueryParams(stateConfig, tableState), (value) =>
|
||||||
value ? `${value}` : undefined,
|
value ? `${value}` : undefined,
|
||||||
),
|
),
|
||||||
|
projectId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const setSearchValue = (query = '') => {
|
const setSearchValue = (query = '') => {
|
||||||
|
@ -1,62 +1,10 @@
|
|||||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
import { ApplicationsSchema } from '../../../../openapi';
|
||||||
import { useEffect, useState } from 'react';
|
import { createPaginatedHook } from '../usePaginatedData/usePaginatedData';
|
||||||
import { formatApiPath } from 'utils/formatPath';
|
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
|
||||||
import { ApplicationsSchema, GetApplicationsParams } from '../../../../openapi';
|
|
||||||
import { useClearSWRCache } from '../../../useClearSWRCache';
|
|
||||||
|
|
||||||
interface IUseApplicationsOutput extends ApplicationsSchema {
|
const prefixKey = 'api/admin/metrics/applications?';
|
||||||
refetchApplications: () => void;
|
const useApplications = createPaginatedHook<ApplicationsSchema>(
|
||||||
loading: boolean;
|
{ applications: [], total: 0 },
|
||||||
error?: Error;
|
prefixKey,
|
||||||
}
|
);
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useApplications;
|
export default useApplications;
|
||||||
|
@ -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)');
|
||||||
|
});
|
@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
@ -1,84 +1,18 @@
|
|||||||
import useSWR, { SWRConfiguration } from 'swr';
|
import { ProjectApplicationsSchema } from 'openapi';
|
||||||
import { useCallback } from 'react';
|
import { createPaginatedHook } from '../usePaginatedData/usePaginatedData';
|
||||||
import { formatApiPath } from 'utils/formatPath';
|
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
|
||||||
import {
|
|
||||||
GetProjectApplicationsParams,
|
|
||||||
ProjectApplicationsSchema,
|
|
||||||
} from 'openapi';
|
|
||||||
import { useClearSWRCache } from 'hooks/useClearSWRCache';
|
|
||||||
|
|
||||||
type UseProjectApplicationsOutput = {
|
export const DEFAULT_PAGE_LIMIT = 25;
|
||||||
loading: boolean;
|
|
||||||
error: string;
|
|
||||||
refetch: () => void;
|
|
||||||
} & ProjectApplicationsSchema;
|
|
||||||
|
|
||||||
const fallbackData: ProjectApplicationsSchema = {
|
|
||||||
applications: [],
|
|
||||||
total: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPrefixKey = (projectId: string) => {
|
const getPrefixKey = (projectId: string) => {
|
||||||
return `api/admin/projects/${projectId}/applications?`;
|
return `api/admin/projects/${projectId}/applications?`;
|
||||||
};
|
};
|
||||||
|
const useParameterizedProjectApplications =
|
||||||
|
createPaginatedHook<ProjectApplicationsSchema>({
|
||||||
|
applications: [],
|
||||||
|
total: 0,
|
||||||
|
});
|
||||||
|
|
||||||
const createProjectApplications = () => {
|
export const useProjectApplications = (
|
||||||
return (
|
params: Record<string, any>,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
params: GetProjectApplicationsParams,
|
) => useParameterizedProjectApplications(params, getPrefixKey(projectId));
|
||||||
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 = (
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
Loading…
Reference in New Issue
Block a user