From cb987ac78b6c0d02d5205783b2e984797e48d5e3 Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Tue, 29 Apr 2025 15:33:06 +0300 Subject: [PATCH] feat: now updating/editing strategy will store constraints in recents (#9861) --- ...FeatureStrategyConstraintAccordionList.tsx | 2 +- .../RecentlyUsedConstraints.tsx | 1 - .../useRecentlyUsedConstraints.test.tsx | 82 ++++++++++++++----- .../useRecentlyUsedConstraints.ts | 19 +++-- .../useFeatureStrategyApi.ts | 26 +++++- 5 files changed, 99 insertions(+), 31 deletions(-) rename frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/{ => RecentlyUsedConstraints}/RecentlyUsedConstraints.tsx (98%) rename frontend/src/component/feature/FeatureStrategy/{ => FeatureStrategyConstraints}/RecentlyUsedConstraints/useRecentlyUsedConstraints.test.tsx (72%) rename frontend/src/component/feature/FeatureStrategy/{ => FeatureStrategyConstraints}/RecentlyUsedConstraints/useRecentlyUsedConstraints.ts (70%) diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraintAccordionList/FeatureStrategyConstraintAccordionList.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraintAccordionList/FeatureStrategyConstraintAccordionList.tsx index 3880a3e8ca..e4cab5ab91 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraintAccordionList/FeatureStrategyConstraintAccordionList.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/FeatureStrategyConstraintAccordionList/FeatureStrategyConstraintAccordionList.tsx @@ -15,7 +15,7 @@ import { EditableConstraintsList } from 'component/common/NewConstraintAccordion import { Limit } from 'component/common/Limit/Limit'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { useUiFlag } from 'hooks/useUiFlag'; -import { RecentlyUsedConstraints } from '../RecentlyUsedConstraints'; +import { RecentlyUsedConstraints } from '../RecentlyUsedConstraints/RecentlyUsedConstraints'; interface IConstraintAccordionListProps { constraints: IConstraint[]; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/RecentlyUsedConstraints.tsx similarity index 98% rename from frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints.tsx rename to frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/RecentlyUsedConstraints.tsx index 20db866c1e..39091ddd54 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/RecentlyUsedConstraints.tsx @@ -26,7 +26,6 @@ const StyledConstraintsContainer = styled('div')(({ theme }) => ({ export const RecentlyUsedConstraints = ({ temporary, }: IRecentlyUsedConstraintsProps) => { - // Mock constraint for now const mockConstraints: IConstraint[] = [ { contextName: 'userId', diff --git a/frontend/src/component/feature/FeatureStrategy/RecentlyUsedConstraints/useRecentlyUsedConstraints.test.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/useRecentlyUsedConstraints.test.tsx similarity index 72% rename from frontend/src/component/feature/FeatureStrategy/RecentlyUsedConstraints/useRecentlyUsedConstraints.test.tsx rename to frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/useRecentlyUsedConstraints.test.tsx index f357d9ddbf..48f24e9633 100644 --- a/frontend/src/component/feature/FeatureStrategy/RecentlyUsedConstraints/useRecentlyUsedConstraints.test.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/useRecentlyUsedConstraints.test.tsx @@ -91,9 +91,7 @@ describe('useRecentlyUsedConstraints', () => { }); it('should initialize with empty array when no items in localStorage', () => { - const { result } = renderHook(() => - useRecentlyUsedConstraints('test-key'), - ); + const { result } = renderHook(() => useRecentlyUsedConstraints()); expect(result.current.items).toEqual([]); }); @@ -101,16 +99,14 @@ describe('useRecentlyUsedConstraints', () => { it('should initialize with initial items if provided', () => { const initialItems = [createTestConstraint('userId')]; const { result } = renderHook(() => - useRecentlyUsedConstraints('test-key', initialItems), + useRecentlyUsedConstraints(initialItems), ); expect(result.current.items).toEqual(initialItems); }); it('should add new items to the beginning of the list', () => { - const { result } = renderHook(() => - useRecentlyUsedConstraints('test-key'), - ); + const { result } = renderHook(() => useRecentlyUsedConstraints()); act(() => { result.current.addItem(createTestConstraint('userId')); @@ -124,10 +120,27 @@ describe('useRecentlyUsedConstraints', () => { expect(result.current.items[1].contextName).toBe('userId'); }); + it('should handle array of constraints when adding items', () => { + const { result } = renderHook(() => useRecentlyUsedConstraints()); + + const constraints = [ + createTestConstraint('userId'), + createTestConstraint('email'), + createTestConstraint('appName'), + ]; + + act(() => { + result.current.addItem(constraints); + }); + + expect(result.current.items.length).toBe(3); + expect(result.current.items[0].contextName).toBe('appName'); + expect(result.current.items[1].contextName).toBe('email'); + expect(result.current.items[2].contextName).toBe('userId'); + }); + it('should limit stored items to maximum of 3', () => { - const { result } = renderHook(() => - useRecentlyUsedConstraints('test-key'), - ); + const { result } = renderHook(() => useRecentlyUsedConstraints()); act(() => { result.current.addItem(createTestConstraint('userId')); @@ -142,10 +155,25 @@ describe('useRecentlyUsedConstraints', () => { expect(result.current.items[2].contextName).toBe('email'); }); + it('should also limit to max of 3 items when adding an array of constraints', () => { + const { result } = renderHook(() => useRecentlyUsedConstraints()); + + const constraints = [ + createTestConstraint('userId'), + createTestConstraint('email'), + createTestConstraint('appName'), + createTestConstraint('countryId'), + ]; + + act(() => { + result.current.addItem(constraints); + }); + + expect(result.current.items.length).toBe(3); + }); + it('should not add duplicate constraints', () => { - const { result } = renderHook(() => - useRecentlyUsedConstraints('test-key'), - ); + const { result } = renderHook(() => useRecentlyUsedConstraints()); const constraint1 = createTestConstraint('userId', IN, [ 'user1', @@ -173,9 +201,7 @@ describe('useRecentlyUsedConstraints', () => { }); it('should not add duplicate constraints with values in different order', () => { - const { result } = renderHook(() => - useRecentlyUsedConstraints('test-key'), - ); + const { result } = renderHook(() => useRecentlyUsedConstraints()); const constraint1 = { contextName: 'userId', @@ -210,17 +236,33 @@ describe('useRecentlyUsedConstraints', () => { ]); }); + it('should handle duplicates in an array of constraints', () => { + const { result } = renderHook(() => useRecentlyUsedConstraints()); + + const constraints = [ + createTestConstraint('userId', IN, ['user1', 'user2']), + createTestConstraint('email'), + createTestConstraint('userId', IN, ['user1', 'user2']), // Duplicate + ]; + + act(() => { + result.current.addItem(constraints); + }); + + expect(result.current.items.length).toBe(2); + expect(result.current.items[0].contextName).toBe('userId'); + expect(result.current.items[1].contextName).toBe('email'); + }); + it('should persist items to localStorage', () => { - const { result } = renderHook(() => - useRecentlyUsedConstraints('test-key'), - ); + const { result } = renderHook(() => useRecentlyUsedConstraints()); act(() => { result.current.addItem(createTestConstraint('userId')); }); const { result: newResult } = renderHook(() => - useRecentlyUsedConstraints('test-key'), + useRecentlyUsedConstraints(), ); expect(newResult.current.items[0].contextName).toBe('userId'); diff --git a/frontend/src/component/feature/FeatureStrategy/RecentlyUsedConstraints/useRecentlyUsedConstraints.ts b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/useRecentlyUsedConstraints.ts similarity index 70% rename from frontend/src/component/feature/FeatureStrategy/RecentlyUsedConstraints/useRecentlyUsedConstraints.ts rename to frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/useRecentlyUsedConstraints.ts index 6d43b17f2c..f92a453aba 100644 --- a/frontend/src/component/feature/FeatureStrategy/RecentlyUsedConstraints/useRecentlyUsedConstraints.ts +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/useRecentlyUsedConstraints.ts @@ -5,7 +5,6 @@ export const areConstraintsEqual = ( a: IConstraint, b: IConstraint, ): boolean => { - // Sort the values arrays if they exist const sortedValues = (values?: string[]) => values ? [...values].sort() : undefined; @@ -31,21 +30,25 @@ export const areConstraintsEqual = ( }; export const useRecentlyUsedConstraints = ( - key: string, initialItems: IConstraint[] = [], ) => { const [items, setItems] = useLocalStorageState( - `recently-used-constraints-${key}`, + 'recently-used-constraints', initialItems, ); - const addItem = (newItem: IConstraint) => { + const addItem = (newItem: IConstraint | IConstraint[]) => { setItems((prevItems) => { - const filteredItems = prevItems.filter( - (item) => !areConstraintsEqual(item, newItem), - ); + const itemsToAdd = Array.isArray(newItem) ? newItem : [newItem]; - const updatedItems = [newItem, ...filteredItems]; + let updatedItems = [...prevItems]; + + itemsToAdd.forEach((item) => { + updatedItems = updatedItems.filter( + (existingItem) => !areConstraintsEqual(existingItem, item), + ); + updatedItems = [item, ...updatedItems]; + }); return updatedItems.slice(0, 3); }); }; diff --git a/frontend/src/hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi.ts b/frontend/src/hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi.ts index bcc344782c..ed8affa633 100644 --- a/frontend/src/hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi.ts +++ b/frontend/src/hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi.ts @@ -4,12 +4,18 @@ import type { IFeatureStrategySortOrder, } from 'interfaces/strategy'; import useAPI from '../useApi/useApi'; +import { useRecentlyUsedConstraints } from 'component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/useRecentlyUsedConstraints'; +import { useUiFlag } from 'hooks/useUiFlag'; const useFeatureStrategyApi = () => { const { makeRequest, createRequest, errors, loading } = useAPI({ propagateErrors: true, }); + const { addItem: addToRecentlyUsedConstraints } = + useRecentlyUsedConstraints(); + const addEditStrategyEnabled = useUiFlag('addEditStrategy'); + const addStrategyToFeature = async ( projectId: string, featureId: string, @@ -22,7 +28,17 @@ const useFeatureStrategyApi = () => { { method: 'POST', body: JSON.stringify(payload) }, 'addStrategyToFeature', ); - return (await makeRequest(req.caller, req.id)).json(); + const result = await makeRequest(req.caller, req.id); + + if ( + addEditStrategyEnabled && + payload.constraints && + payload.constraints.length > 0 + ) { + addToRecentlyUsedConstraints(payload.constraints); + } + + return result.json(); }; const deleteStrategyFromFeature = async ( @@ -54,6 +70,14 @@ const useFeatureStrategyApi = () => { 'updateStrategyOnFeature', ); await makeRequest(req.caller, req.id); + + if ( + addEditStrategyEnabled && + payload.constraints && + payload.constraints.length > 0 + ) { + addToRecentlyUsedConstraints(payload.constraints); + } }; const setStrategiesSortOrder = async (