diff --git a/frontend/src/hooks/api/getters/useFeatureSearch/useFeatureSearch.test.tsx b/frontend/src/hooks/api/getters/useFeatureSearch/useFeatureSearch.test.tsx
index 62a4bc0c7d..7a168377df 100644
--- a/frontend/src/hooks/api/getters/useFeatureSearch/useFeatureSearch.test.tsx
+++ b/frontend/src/hooks/api/getters/useFeatureSearch/useFeatureSearch.test.tsx
@@ -43,7 +43,7 @@ describe('useFeatureSearch', () => {
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', {
features: [{ name: 'Feature1' }],
total: 1,
@@ -61,7 +61,7 @@ describe('useFeatureSearch', () => {
render();
await screen.findByText(/Features:/);
await screen.findByText(
- 'Cache: api/admin/search/features?project=project2',
+ 'Cache: api/admin/search/features?project=project1api/admin/search/features?project=project2',
);
});
});
diff --git a/frontend/src/hooks/api/getters/useFeatureSearch/useFeatureSearch.ts b/frontend/src/hooks/api/getters/useFeatureSearch/useFeatureSearch.ts
index eee25a3358..2fc97150fd 100644
--- a/frontend/src/hooks/api/getters/useFeatureSearch/useFeatureSearch.ts
+++ b/frontend/src/hooks/api/getters/useFeatureSearch/useFeatureSearch.ts
@@ -25,6 +25,7 @@ const fallbackData: SearchFeaturesSchema = {
total: 0,
};
+const SWR_CACHE_SIZE = 10;
const PREFIX_KEY = 'api/admin/search/features?';
const createFeatureSearch = () => {
@@ -57,7 +58,7 @@ const createFeatureSearch = () => {
): UseFeatureSearchOutput => {
const { KEY, fetcher } = getFeatureSearchFetcher(params);
const cacheId = params.project || '';
- useClearSWRCache(KEY, PREFIX_KEY);
+ useClearSWRCache(KEY, PREFIX_KEY, SWR_CACHE_SIZE);
useEffect(() => {
initCache(params.project || '');
@@ -97,8 +98,6 @@ const createFeatureSearch = () => {
export const DEFAULT_PAGE_LIMIT = 25;
-export const useFeatureSearch = createFeatureSearch();
-
const getFeatureSearchFetcher = (params: SearchFeaturesParams) => {
const urlSearchParams = new URLSearchParams(
Array.from(
@@ -122,3 +121,5 @@ const getFeatureSearchFetcher = (params: SearchFeaturesParams) => {
KEY,
};
};
+
+export const useFeatureSearch = createFeatureSearch();
diff --git a/frontend/src/hooks/useClearSWRCache.test.ts b/frontend/src/hooks/useClearSWRCache.test.ts
new file mode 100644
index 0000000000..1a3498a01e
--- /dev/null
+++ b/frontend/src/hooks/useClearSWRCache.test.ts
@@ -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);
+ });
+});
diff --git a/frontend/src/hooks/useClearSWRCache.ts b/frontend/src/hooks/useClearSWRCache.ts
index 359ac102c0..32db4c6ce7 100644
--- a/frontend/src/hooks/useClearSWRCache.ts
+++ b/frontend/src/hooks/useClearSWRCache.ts
@@ -1,14 +1,33 @@
import { useSWRConfig } from 'swr';
+type Cache = ReturnType['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.
We only keep the latest cache key `currentKey` and remove all other entries identified
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 keys = [...cache.keys()];
- keys.filter((key) => key !== currentKey && key.startsWith(clearPrefix)).map(
- (key) => cache.delete(key),
- );
+ clearCacheEntries(cache, currentKey, clearPrefix, SWR_CACHE_SIZE);
};