mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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 { ApplicationUsageCell } from './ApplicationUsageCell/ApplicationUsageCell';
 | 
			
		||||
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 { usePersistentTableState } from 'hooks/usePersistentTableState';
 | 
			
		||||
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
 | 
			
		||||
import { withTableState } from 'utils/withTableState';
 | 
			
		||||
import useLoading from 'hooks/useLoading';
 | 
			
		||||
import mapValues from 'lodash.mapvalues';
 | 
			
		||||
 | 
			
		||||
const renderNoApplications = () => (
 | 
			
		||||
    <>
 | 
			
		||||
@ -42,9 +48,6 @@ const renderNoApplications = () => (
 | 
			
		||||
const columnHelper = createColumnHelper<ApplicationSchema>();
 | 
			
		||||
 | 
			
		||||
export const PaginatedApplicationList = () => {
 | 
			
		||||
    const { applications: data, loading } = useApplications();
 | 
			
		||||
    const total = 1000;
 | 
			
		||||
 | 
			
		||||
    const stateConfig = {
 | 
			
		||||
        offset: withDefault(NumberParam, 0),
 | 
			
		||||
        limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT),
 | 
			
		||||
@ -56,11 +59,21 @@ export const PaginatedApplicationList = () => {
 | 
			
		||||
        `applications-table`,
 | 
			
		||||
        stateConfig,
 | 
			
		||||
    );
 | 
			
		||||
    const {
 | 
			
		||||
        applications: data,
 | 
			
		||||
        total,
 | 
			
		||||
        loading,
 | 
			
		||||
    } = useApplications(
 | 
			
		||||
        mapValues(encodeQueryParams(stateConfig, tableState), (value) =>
 | 
			
		||||
            value ? `${value}` : undefined,
 | 
			
		||||
        ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const columns = useMemo(
 | 
			
		||||
        () => [
 | 
			
		||||
            columnHelper.accessor('icon', {
 | 
			
		||||
                id: 'Icon',
 | 
			
		||||
                header: () => '',
 | 
			
		||||
                cell: ({
 | 
			
		||||
                    row: {
 | 
			
		||||
                        original: { icon },
 | 
			
		||||
@ -74,6 +87,7 @@ export const PaginatedApplicationList = () => {
 | 
			
		||||
                        }
 | 
			
		||||
                    />
 | 
			
		||||
                ),
 | 
			
		||||
                enableSorting: false,
 | 
			
		||||
            }),
 | 
			
		||||
            columnHelper.accessor('appName', {
 | 
			
		||||
                header: 'Name',
 | 
			
		||||
@ -93,6 +107,7 @@ export const PaginatedApplicationList = () => {
 | 
			
		||||
            columnHelper.accessor('usage', {
 | 
			
		||||
                header: 'Project(environment)',
 | 
			
		||||
                meta: { width: '50%' },
 | 
			
		||||
                enableSorting: false,
 | 
			
		||||
                cell: ({
 | 
			
		||||
                    row: { original },
 | 
			
		||||
                }: {
 | 
			
		||||
 | 
			
		||||
@ -13,12 +13,11 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
 | 
			
		||||
import styles from '../../strategies.module.scss';
 | 
			
		||||
import { TogglesLinkList } from 'component/strategies/TogglesLinkList/TogglesLinkList';
 | 
			
		||||
import { IStrategy, IStrategyParameter } from 'interfaces/strategy';
 | 
			
		||||
import { IApplication } from 'interfaces/application';
 | 
			
		||||
import { FeatureSchema } from 'openapi';
 | 
			
		||||
import { ApplicationSchema, FeatureSchema } from 'openapi';
 | 
			
		||||
 | 
			
		||||
interface IStrategyDetailsProps {
 | 
			
		||||
    strategy: IStrategy;
 | 
			
		||||
    applications: IApplication[];
 | 
			
		||||
    applications: ApplicationSchema[];
 | 
			
		||||
    toggles: FeatureSchema[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,40 +1,49 @@
 | 
			
		||||
import useSWR, { mutate, SWRConfiguration } from 'swr';
 | 
			
		||||
import { useState, useEffect } from 'react';
 | 
			
		||||
import { useEffect, useState } from 'react';
 | 
			
		||||
import { formatApiPath } from 'utils/formatPath';
 | 
			
		||||
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 {
 | 
			
		||||
    applications: IApplication[];
 | 
			
		||||
interface IUseApplicationsOutput extends ApplicationsSchema {
 | 
			
		||||
    refetchApplications: () => void;
 | 
			
		||||
    loading: boolean;
 | 
			
		||||
    error?: Error;
 | 
			
		||||
    APPLICATIONS_CACHE_KEY: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(path, {
 | 
			
		||||
        return fetch(formatApiPath(KEY), {
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
        })
 | 
			
		||||
            .then(handleErrorResponses('Applications data'))
 | 
			
		||||
            .then((res) => res.json());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const APPLICATIONS_CACHE_KEY = 'api/admin/metrics/applications';
 | 
			
		||||
 | 
			
		||||
    const { data, error } = useSWR(APPLICATIONS_CACHE_KEY, fetcher, {
 | 
			
		||||
    const { data, error } = useSWR(KEY, fetcher, {
 | 
			
		||||
        ...options,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const [loading, setLoading] = useState(!error && !data);
 | 
			
		||||
 | 
			
		||||
    const refetchApplications = () => {
 | 
			
		||||
        mutate(APPLICATIONS_CACHE_KEY);
 | 
			
		||||
        mutate(KEY);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
@ -43,10 +52,10 @@ const useApplications = (
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        applications: data?.applications || [],
 | 
			
		||||
        total: data?.total || 0,
 | 
			
		||||
        error,
 | 
			
		||||
        loading,
 | 
			
		||||
        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 { formatApiPath } from 'utils/formatPath';
 | 
			
		||||
import handleErrorResponses from '../httpErrorResponseHandler';
 | 
			
		||||
import { SearchFeaturesParams, SearchFeaturesSchema } from 'openapi';
 | 
			
		||||
import { useClearSWRCache } from 'hooks/useClearSWRCache';
 | 
			
		||||
 | 
			
		||||
type UseFeatureSearchOutput = {
 | 
			
		||||
    loading: boolean;
 | 
			
		||||
@ -26,19 +27,6 @@ const fallbackData: SearchFeaturesSchema = {
 | 
			
		||||
 | 
			
		||||
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 internalCache: InternalCache = {};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import {
 | 
			
		||||
    GetProjectApplicationsParams,
 | 
			
		||||
    ProjectApplicationsSchema,
 | 
			
		||||
} from 'openapi';
 | 
			
		||||
import { useClearSWRCache } from 'hooks/useClearSWRCache';
 | 
			
		||||
 | 
			
		||||
type UseProjectApplicationsOutput = {
 | 
			
		||||
    loading: boolean;
 | 
			
		||||
@ -66,6 +67,7 @@ const getProjectApplicationsFetcher = (
 | 
			
		||||
        ),
 | 
			
		||||
    ).toString();
 | 
			
		||||
    const KEY = `${getPrefixKey(projectId)}${urlSearchParams}`;
 | 
			
		||||
    useClearSWRCache(KEY, getPrefixKey(projectId));
 | 
			
		||||
    const fetcher = () => {
 | 
			
		||||
        const path = formatApiPath(KEY);
 | 
			
		||||
        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