mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-22 01:16:07 +02:00
feat: paginated hook for applications list (#6315)
This commit is contained in:
parent
fb63f21d8a
commit
edbd71ac15
@ -13,12 +13,18 @@ import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
|||||||
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||||
import { ApplicationUsageCell } from './ApplicationUsageCell/ApplicationUsageCell';
|
import { ApplicationUsageCell } from './ApplicationUsageCell/ApplicationUsageCell';
|
||||||
import { ApplicationSchema } from '../../../openapi';
|
import { ApplicationSchema } from '../../../openapi';
|
||||||
import { NumberParam, StringParam, withDefault } from 'use-query-params';
|
import {
|
||||||
|
encodeQueryParams,
|
||||||
|
NumberParam,
|
||||||
|
StringParam,
|
||||||
|
withDefault,
|
||||||
|
} from 'use-query-params';
|
||||||
import { DEFAULT_PAGE_LIMIT } from 'hooks/api/getters/useProjectApplications/useProjectApplications';
|
import { DEFAULT_PAGE_LIMIT } from 'hooks/api/getters/useProjectApplications/useProjectApplications';
|
||||||
import { usePersistentTableState } from 'hooks/usePersistentTableState';
|
import { usePersistentTableState } from 'hooks/usePersistentTableState';
|
||||||
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
|
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
|
||||||
import { withTableState } from 'utils/withTableState';
|
import { withTableState } from 'utils/withTableState';
|
||||||
import useLoading from 'hooks/useLoading';
|
import useLoading from 'hooks/useLoading';
|
||||||
|
import mapValues from 'lodash.mapvalues';
|
||||||
|
|
||||||
const renderNoApplications = () => (
|
const renderNoApplications = () => (
|
||||||
<>
|
<>
|
||||||
@ -42,9 +48,6 @@ const renderNoApplications = () => (
|
|||||||
const columnHelper = createColumnHelper<ApplicationSchema>();
|
const columnHelper = createColumnHelper<ApplicationSchema>();
|
||||||
|
|
||||||
export const PaginatedApplicationList = () => {
|
export const PaginatedApplicationList = () => {
|
||||||
const { applications: data, loading } = useApplications();
|
|
||||||
const total = 1000;
|
|
||||||
|
|
||||||
const stateConfig = {
|
const stateConfig = {
|
||||||
offset: withDefault(NumberParam, 0),
|
offset: withDefault(NumberParam, 0),
|
||||||
limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT),
|
limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT),
|
||||||
@ -56,11 +59,21 @@ export const PaginatedApplicationList = () => {
|
|||||||
`applications-table`,
|
`applications-table`,
|
||||||
stateConfig,
|
stateConfig,
|
||||||
);
|
);
|
||||||
|
const {
|
||||||
|
applications: data,
|
||||||
|
total,
|
||||||
|
loading,
|
||||||
|
} = useApplications(
|
||||||
|
mapValues(encodeQueryParams(stateConfig, tableState), (value) =>
|
||||||
|
value ? `${value}` : undefined,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
columnHelper.accessor('icon', {
|
columnHelper.accessor('icon', {
|
||||||
id: 'Icon',
|
id: 'Icon',
|
||||||
|
header: () => '',
|
||||||
cell: ({
|
cell: ({
|
||||||
row: {
|
row: {
|
||||||
original: { icon },
|
original: { icon },
|
||||||
@ -74,6 +87,7 @@ export const PaginatedApplicationList = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
enableSorting: false,
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('appName', {
|
columnHelper.accessor('appName', {
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
@ -93,6 +107,7 @@ export const PaginatedApplicationList = () => {
|
|||||||
columnHelper.accessor('usage', {
|
columnHelper.accessor('usage', {
|
||||||
header: 'Project(environment)',
|
header: 'Project(environment)',
|
||||||
meta: { width: '50%' },
|
meta: { width: '50%' },
|
||||||
|
enableSorting: false,
|
||||||
cell: ({
|
cell: ({
|
||||||
row: { original },
|
row: { original },
|
||||||
}: {
|
}: {
|
||||||
|
@ -13,12 +13,11 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
|||||||
import styles from '../../strategies.module.scss';
|
import styles from '../../strategies.module.scss';
|
||||||
import { TogglesLinkList } from 'component/strategies/TogglesLinkList/TogglesLinkList';
|
import { TogglesLinkList } from 'component/strategies/TogglesLinkList/TogglesLinkList';
|
||||||
import { IStrategy, IStrategyParameter } from 'interfaces/strategy';
|
import { IStrategy, IStrategyParameter } from 'interfaces/strategy';
|
||||||
import { IApplication } from 'interfaces/application';
|
import { ApplicationSchema, FeatureSchema } from 'openapi';
|
||||||
import { FeatureSchema } from 'openapi';
|
|
||||||
|
|
||||||
interface IStrategyDetailsProps {
|
interface IStrategyDetailsProps {
|
||||||
strategy: IStrategy;
|
strategy: IStrategy;
|
||||||
applications: IApplication[];
|
applications: ApplicationSchema[];
|
||||||
toggles: FeatureSchema[];
|
toggles: FeatureSchema[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,40 +1,49 @@
|
|||||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
import useSWR, { mutate, SWRConfiguration } from 'swr';
|
||||||
import { useState, useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { formatApiPath } from 'utils/formatPath';
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
import { IApplication } from 'interfaces/application';
|
import { ApplicationsSchema, GetApplicationsParams } from '../../../../openapi';
|
||||||
|
import { useClearSWRCache } from '../../../useClearSWRCache';
|
||||||
|
|
||||||
const path = formatApiPath('api/admin/metrics/applications');
|
interface IUseApplicationsOutput extends ApplicationsSchema {
|
||||||
|
|
||||||
interface IUseApplicationsOutput {
|
|
||||||
applications: IApplication[];
|
|
||||||
refetchApplications: () => void;
|
refetchApplications: () => void;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error?: Error;
|
error?: Error;
|
||||||
APPLICATIONS_CACHE_KEY: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PREFIX_KEY = 'api/admin/metrics/applications?';
|
||||||
|
|
||||||
const useApplications = (
|
const useApplications = (
|
||||||
|
params: GetApplicationsParams = {},
|
||||||
options: SWRConfiguration = {},
|
options: SWRConfiguration = {},
|
||||||
): IUseApplicationsOutput => {
|
): 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 () => {
|
const fetcher = async () => {
|
||||||
return fetch(path, {
|
return fetch(formatApiPath(KEY), {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
.then(handleErrorResponses('Applications data'))
|
.then(handleErrorResponses('Applications data'))
|
||||||
.then((res) => res.json());
|
.then((res) => res.json());
|
||||||
};
|
};
|
||||||
|
|
||||||
const APPLICATIONS_CACHE_KEY = 'api/admin/metrics/applications';
|
const { data, error } = useSWR(KEY, fetcher, {
|
||||||
|
|
||||||
const { data, error } = useSWR(APPLICATIONS_CACHE_KEY, fetcher, {
|
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [loading, setLoading] = useState(!error && !data);
|
const [loading, setLoading] = useState(!error && !data);
|
||||||
|
|
||||||
const refetchApplications = () => {
|
const refetchApplications = () => {
|
||||||
mutate(APPLICATIONS_CACHE_KEY);
|
mutate(KEY);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -43,10 +52,10 @@ const useApplications = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
applications: data?.applications || [],
|
applications: data?.applications || [],
|
||||||
|
total: data?.total || 0,
|
||||||
error,
|
error,
|
||||||
loading,
|
loading,
|
||||||
refetchApplications,
|
refetchApplications,
|
||||||
APPLICATIONS_CACHE_KEY,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import useSWR, { SWRConfiguration, useSWRConfig } from 'swr';
|
import useSWR, { SWRConfiguration } from 'swr';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { formatApiPath } from 'utils/formatPath';
|
import { formatApiPath } from 'utils/formatPath';
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
import { SearchFeaturesParams, SearchFeaturesSchema } from 'openapi';
|
import { SearchFeaturesParams, SearchFeaturesSchema } from 'openapi';
|
||||||
|
import { useClearSWRCache } from 'hooks/useClearSWRCache';
|
||||||
|
|
||||||
type UseFeatureSearchOutput = {
|
type UseFeatureSearchOutput = {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@ -26,19 +27,6 @@ const fallbackData: SearchFeaturesSchema = {
|
|||||||
|
|
||||||
const PREFIX_KEY = 'api/admin/search/features?';
|
const PREFIX_KEY = 'api/admin/search/features?';
|
||||||
|
|
||||||
/**
|
|
||||||
With dynamic search and filter parameters we want to prevent cache from growing extensively.
|
|
||||||
We only keep the latest cache key `currentKey` and remove all other entries identified
|
|
||||||
by the `clearPrefix`
|
|
||||||
*/
|
|
||||||
const useClearSWRCache = (currentKey: string, clearPrefix: string) => {
|
|
||||||
const { cache } = useSWRConfig();
|
|
||||||
const keys = [...cache.keys()];
|
|
||||||
keys.filter((key) => key !== currentKey && key.startsWith(clearPrefix)).map(
|
|
||||||
(key) => cache.delete(key),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createFeatureSearch = () => {
|
const createFeatureSearch = () => {
|
||||||
const internalCache: InternalCache = {};
|
const internalCache: InternalCache = {};
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
GetProjectApplicationsParams,
|
GetProjectApplicationsParams,
|
||||||
ProjectApplicationsSchema,
|
ProjectApplicationsSchema,
|
||||||
} from 'openapi';
|
} from 'openapi';
|
||||||
|
import { useClearSWRCache } from 'hooks/useClearSWRCache';
|
||||||
|
|
||||||
type UseProjectApplicationsOutput = {
|
type UseProjectApplicationsOutput = {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@ -66,6 +67,7 @@ const getProjectApplicationsFetcher = (
|
|||||||
),
|
),
|
||||||
).toString();
|
).toString();
|
||||||
const KEY = `${getPrefixKey(projectId)}${urlSearchParams}`;
|
const KEY = `${getPrefixKey(projectId)}${urlSearchParams}`;
|
||||||
|
useClearSWRCache(KEY, getPrefixKey(projectId));
|
||||||
const fetcher = () => {
|
const fetcher = () => {
|
||||||
const path = formatApiPath(KEY);
|
const path = formatApiPath(KEY);
|
||||||
return fetch(path, {
|
return fetch(path, {
|
||||||
|
14
frontend/src/hooks/useClearSWRCache.ts
Normal file
14
frontend/src/hooks/useClearSWRCache.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { useSWRConfig } from 'swr';
|
||||||
|
|
||||||
|
/**
|
||||||
|
With dynamic search and filter parameters we want to prevent cache from growing extensively.
|
||||||
|
We only keep the latest cache key `currentKey` and remove all other entries identified
|
||||||
|
by the `clearPrefix`
|
||||||
|
*/
|
||||||
|
export const useClearSWRCache = (currentKey: string, clearPrefix: string) => {
|
||||||
|
const { cache } = useSWRConfig();
|
||||||
|
const keys = [...cache.keys()];
|
||||||
|
keys.filter((key) => key !== currentKey && key.startsWith(clearPrefix)).map(
|
||||||
|
(key) => cache.delete(key),
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user