mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: do not cache old search and filter results (#5600)
This commit is contained in:
		
							parent
							
								
									e02c252636
								
							
						
					
					
						commit
						e88beff2b2
					
				@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					import React, { FC } from 'react';
 | 
				
			||||||
 | 
					import { render, screen } from '@testing-library/react';
 | 
				
			||||||
 | 
					import '@testing-library/jest-dom';
 | 
				
			||||||
 | 
					import { testServerRoute, testServerSetup } from 'utils/testServer';
 | 
				
			||||||
 | 
					import { useFeatureSearch } from './useFeatureSearch';
 | 
				
			||||||
 | 
					import { useSWRConfig } from 'swr';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const server = testServerSetup();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TestComponent: FC<{ params: { project: string } }> = ({ params }) => {
 | 
				
			||||||
 | 
					    const { loading, error, features, total } = useFeatureSearch(params);
 | 
				
			||||||
 | 
					    const { cache } = useSWRConfig();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (loading) {
 | 
				
			||||||
 | 
					        return <div>Loading...</div>;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (error) {
 | 
				
			||||||
 | 
					        return <div>Error: {error}</div>;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					            <div>Features: {features.map((f) => f.name).join(', ')}</div>
 | 
				
			||||||
 | 
					            <div>Total: {total}</div>
 | 
				
			||||||
 | 
					            <div>Cache: {[...cache.keys()]}</div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('useFeatureSearch', () => {
 | 
				
			||||||
 | 
					    test('should return features after loading', async () => {
 | 
				
			||||||
 | 
					        testServerRoute(server, '/api/admin/search/features?project=project1', {
 | 
				
			||||||
 | 
					            features: [{ name: 'Feature1' }, { name: 'Feature2' }],
 | 
				
			||||||
 | 
					            total: 2,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        render(<TestComponent params={{ project: 'project1' }} />);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await screen.findByText(/Loading.../);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await screen.findByText(/Features:/);
 | 
				
			||||||
 | 
					        await screen.findByText(/Total:/);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    test('should keep only latest cache entry', async () => {
 | 
				
			||||||
 | 
					        testServerRoute(server, '/api/admin/search/features?project=project1', {
 | 
				
			||||||
 | 
					            features: [{ name: 'Feature1' }],
 | 
				
			||||||
 | 
					            total: 1,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        render(<TestComponent params={{ project: 'project1' }} />);
 | 
				
			||||||
 | 
					        await screen.findByText(/Features:/);
 | 
				
			||||||
 | 
					        await screen.findByText(
 | 
				
			||||||
 | 
					            'Cache: api/admin/search/features?project=project1',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        testServerRoute(server, '/api/admin/search/features?project=project2', {
 | 
				
			||||||
 | 
					            features: [{ name: 'Feature2' }],
 | 
				
			||||||
 | 
					            total: 1,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        render(<TestComponent params={{ project: 'project2' }} />);
 | 
				
			||||||
 | 
					        await screen.findByText(/Features:/);
 | 
				
			||||||
 | 
					        await screen.findByText(
 | 
				
			||||||
 | 
					            'Cache: api/admin/search/features?project=project2',
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import useSWR, { SWRConfiguration } from 'swr';
 | 
					import useSWR, { SWRConfiguration, useSWRConfig } 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';
 | 
				
			||||||
@ -24,6 +24,21 @@ const fallbackData: SearchFeaturesSchema = {
 | 
				
			|||||||
    total: 0,
 | 
					    total: 0,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -54,6 +69,7 @@ const createFeatureSearch = () => {
 | 
				
			|||||||
    ): UseFeatureSearchOutput => {
 | 
					    ): UseFeatureSearchOutput => {
 | 
				
			||||||
        const { KEY, fetcher } = getFeatureSearchFetcher(params);
 | 
					        const { KEY, fetcher } = getFeatureSearchFetcher(params);
 | 
				
			||||||
        const cacheId = params.project || '';
 | 
					        const cacheId = params.project || '';
 | 
				
			||||||
 | 
					        useClearSWRCache(KEY, PREFIX_KEY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        useEffect(() => {
 | 
					        useEffect(() => {
 | 
				
			||||||
            initCache(params.project || '');
 | 
					            initCache(params.project || '');
 | 
				
			||||||
@ -103,7 +119,7 @@ const getFeatureSearchFetcher = (params: SearchFeaturesParams) => {
 | 
				
			|||||||
                .map(([key, value]) => [key, value.toString()]), // TODO: parsing non-string parameters
 | 
					                .map(([key, value]) => [key, value.toString()]), // TODO: parsing non-string parameters
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
    ).toString();
 | 
					    ).toString();
 | 
				
			||||||
    const KEY = `api/admin/search/features?${urlSearchParams}`;
 | 
					    const KEY = `${PREFIX_KEY}${urlSearchParams}`;
 | 
				
			||||||
    const fetcher = () => {
 | 
					    const fetcher = () => {
 | 
				
			||||||
        const path = formatApiPath(KEY);
 | 
					        const path = formatApiPath(KEY);
 | 
				
			||||||
        return fetch(path, {
 | 
					        return fetch(path, {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user