mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-14 00:19:16 +01:00
feat: introduce large cache for swr (#7470)
Previously, clearing the SWR cache cleared all entries. Now you can configure the cache size. 1. This makes the search more fluid. Previously, if you went back and forth on pages, you were always sent to the loading state. 2. This also solves the issue where the command bar search cleared the cache for all other searches. 3. Additionally, it addresses the problem where the global search cleared the cache for project search.
This commit is contained in:
parent
95359ecff8
commit
82a53fa9b3
@ -43,7 +43,7 @@ describe('useFeatureSearch', () => {
|
|||||||
await screen.findByText(/Total:/);
|
await screen.findByText(/Total:/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should keep only latest cache entry', async () => {
|
test('should keep at least latest cache entry', async () => {
|
||||||
testServerRoute(server, '/api/admin/search/features?project=project1', {
|
testServerRoute(server, '/api/admin/search/features?project=project1', {
|
||||||
features: [{ name: 'Feature1' }],
|
features: [{ name: 'Feature1' }],
|
||||||
total: 1,
|
total: 1,
|
||||||
@ -61,7 +61,7 @@ describe('useFeatureSearch', () => {
|
|||||||
render(<TestComponent params={{ project: 'project2' }} />);
|
render(<TestComponent params={{ project: 'project2' }} />);
|
||||||
await screen.findByText(/Features:/);
|
await screen.findByText(/Features:/);
|
||||||
await screen.findByText(
|
await screen.findByText(
|
||||||
'Cache: api/admin/search/features?project=project2',
|
'Cache: api/admin/search/features?project=project1api/admin/search/features?project=project2',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -25,6 +25,7 @@ const fallbackData: SearchFeaturesSchema = {
|
|||||||
total: 0,
|
total: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SWR_CACHE_SIZE = 10;
|
||||||
const PREFIX_KEY = 'api/admin/search/features?';
|
const PREFIX_KEY = 'api/admin/search/features?';
|
||||||
|
|
||||||
const createFeatureSearch = () => {
|
const createFeatureSearch = () => {
|
||||||
@ -57,7 +58,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);
|
useClearSWRCache(KEY, PREFIX_KEY, SWR_CACHE_SIZE);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initCache(params.project || '');
|
initCache(params.project || '');
|
||||||
@ -97,8 +98,6 @@ const createFeatureSearch = () => {
|
|||||||
|
|
||||||
export const DEFAULT_PAGE_LIMIT = 25;
|
export const DEFAULT_PAGE_LIMIT = 25;
|
||||||
|
|
||||||
export const useFeatureSearch = createFeatureSearch();
|
|
||||||
|
|
||||||
const getFeatureSearchFetcher = (params: SearchFeaturesParams) => {
|
const getFeatureSearchFetcher = (params: SearchFeaturesParams) => {
|
||||||
const urlSearchParams = new URLSearchParams(
|
const urlSearchParams = new URLSearchParams(
|
||||||
Array.from(
|
Array.from(
|
||||||
@ -122,3 +121,5 @@ const getFeatureSearchFetcher = (params: SearchFeaturesParams) => {
|
|||||||
KEY,
|
KEY,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useFeatureSearch = createFeatureSearch();
|
||||||
|
53
frontend/src/hooks/useClearSWRCache.test.ts
Normal file
53
frontend/src/hooks/useClearSWRCache.test.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { clearCacheEntries } from './useClearSWRCache';
|
||||||
|
|
||||||
|
describe('manageCacheEntries', () => {
|
||||||
|
it('should clear old cache entries and keep the current one when SWR_CACHE_SIZE is not provided', () => {
|
||||||
|
const cacheMock = new Map();
|
||||||
|
cacheMock.set('prefix-1', {});
|
||||||
|
cacheMock.set('prefix-2', {});
|
||||||
|
cacheMock.set('prefix-3', {});
|
||||||
|
|
||||||
|
clearCacheEntries(cacheMock, 'prefix-3', 'prefix-');
|
||||||
|
|
||||||
|
expect(cacheMock.has('prefix-1')).toBe(false);
|
||||||
|
expect(cacheMock.has('prefix-2')).toBe(false);
|
||||||
|
expect(cacheMock.has('prefix-3')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep the SWR_CACHE_SIZE entries and delete the rest', () => {
|
||||||
|
const cacheMock = new Map();
|
||||||
|
cacheMock.set('prefix-1', {});
|
||||||
|
cacheMock.set('prefix-2', {});
|
||||||
|
cacheMock.set('prefix-3', {});
|
||||||
|
cacheMock.set('prefix-4', {});
|
||||||
|
|
||||||
|
clearCacheEntries(cacheMock, 'prefix-4', 'prefix-', 2);
|
||||||
|
|
||||||
|
expect(cacheMock.has('prefix-4')).toBe(true);
|
||||||
|
expect([...cacheMock.keys()].length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle case when SWR_CACHE_SIZE is larger than number of entries', () => {
|
||||||
|
const cacheMock = new Map();
|
||||||
|
cacheMock.set('prefix-1', {});
|
||||||
|
cacheMock.set('prefix-2', {});
|
||||||
|
|
||||||
|
clearCacheEntries(cacheMock, 'prefix-2', 'prefix-', 5);
|
||||||
|
|
||||||
|
expect(cacheMock.has('prefix-1')).toBe(true);
|
||||||
|
expect(cacheMock.has('prefix-2')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not delete entries that do not match the prefix', () => {
|
||||||
|
const cacheMock = new Map();
|
||||||
|
cacheMock.set('prefix-1', {});
|
||||||
|
cacheMock.set('other-2', {});
|
||||||
|
cacheMock.set('prefix-3', {});
|
||||||
|
|
||||||
|
clearCacheEntries(cacheMock, 'prefix-3', 'prefix-', 2);
|
||||||
|
|
||||||
|
expect(cacheMock.has('prefix-1')).toBe(true);
|
||||||
|
expect(cacheMock.has('other-2')).toBe(true);
|
||||||
|
expect(cacheMock.has('prefix-3')).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
@ -1,14 +1,33 @@
|
|||||||
import { useSWRConfig } from 'swr';
|
import { useSWRConfig } from 'swr';
|
||||||
|
|
||||||
|
type Cache = ReturnType<typeof useSWRConfig>['cache'];
|
||||||
|
|
||||||
|
export const clearCacheEntries = (
|
||||||
|
cache: Cache,
|
||||||
|
currentKey: string,
|
||||||
|
clearPrefix: string,
|
||||||
|
SWR_CACHE_SIZE = 1,
|
||||||
|
) => {
|
||||||
|
const keys = [...cache.keys()];
|
||||||
|
|
||||||
|
const filteredKeys = keys.filter(
|
||||||
|
(key) => key.startsWith(clearPrefix) && key !== currentKey,
|
||||||
|
);
|
||||||
|
const keysToDelete = filteredKeys.slice(SWR_CACHE_SIZE - 1);
|
||||||
|
|
||||||
|
keysToDelete.forEach((key) => cache.delete(key));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
With dynamic search and filter parameters we want to prevent cache from growing extensively.
|
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
|
We only keep the latest cache key `currentKey` and remove all other entries identified
|
||||||
by the `clearPrefix`
|
by the `clearPrefix`
|
||||||
*/
|
*/
|
||||||
export const useClearSWRCache = (currentKey: string, clearPrefix: string) => {
|
export const useClearSWRCache = (
|
||||||
|
currentKey: string,
|
||||||
|
clearPrefix: string,
|
||||||
|
SWR_CACHE_SIZE = 1,
|
||||||
|
) => {
|
||||||
const { cache } = useSWRConfig();
|
const { cache } = useSWRConfig();
|
||||||
const keys = [...cache.keys()];
|
clearCacheEntries(cache, currentKey, clearPrefix, SWR_CACHE_SIZE);
|
||||||
keys.filter((key) => key !== currentKey && key.startsWith(clearPrefix)).map(
|
|
||||||
(key) => cache.delete(key),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user