mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: recently used segments (#9881)
This commit is contained in:
		
							parent
							
								
									832e3f2e79
								
							
						
					
					
						commit
						33f23cc0c1
					
				| @ -11,6 +11,7 @@ import { useSegmentLimits } from 'hooks/api/getters/useSegmentLimits/useSegmentL | ||||
| import { Box, styled, Typography } from '@mui/material'; | ||||
| import { HelpIcon } from 'component/common/HelpIcon/HelpIcon'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| import { RecentlyUsedSegments } from './RecentlyUsedSegments/RecentlyUsedSegments'; | ||||
| 
 | ||||
| interface IFeatureStrategySegmentProps { | ||||
|     segments: ISegment[]; | ||||
| @ -105,6 +106,7 @@ export const FeatureStrategySegment = ({ | ||||
|                 segments={selectedSegments} | ||||
|                 setSegments={setSelectedSegments} | ||||
|             /> | ||||
|             <RecentlyUsedSegments setSegments={setSelectedSegments} /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -0,0 +1,68 @@ | ||||
| import { styled, Typography } from '@mui/material'; | ||||
| import { useRecentlyUsedSegments } from './useRecentlyUsedSegments'; | ||||
| import type { ISegment } from 'interfaces/segment'; | ||||
| import { FeatureStrategySegmentChip } from '../FeatureStrategySegmentChip'; | ||||
| import { useSegments } from 'hooks/api/getters/useSegments/useSegments'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| 
 | ||||
| type RecentlyUsedSegmentsProps = { | ||||
|     setSegments?: React.Dispatch<React.SetStateAction<ISegment[]>>; | ||||
| }; | ||||
| 
 | ||||
| const StyledContainer = styled('div')(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(3), | ||||
| })); | ||||
| 
 | ||||
| const StyledHeader = styled(Typography)(({ theme }) => ({ | ||||
|     fontSize: theme.fontSizes.smallerBody, | ||||
|     color: theme.palette.text.secondary, | ||||
|     marginBottom: theme.spacing(1), | ||||
| })); | ||||
| 
 | ||||
| const StyledSegmentsContainer = styled('div')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     flexWrap: 'wrap', | ||||
|     gap: theme.spacing(1), | ||||
| })); | ||||
| 
 | ||||
| export const RecentlyUsedSegments = ({ | ||||
|     setSegments, | ||||
| }: RecentlyUsedSegmentsProps) => { | ||||
|     const { items: recentlyUsedSegmentIds } = useRecentlyUsedSegments(); | ||||
|     const { segments: allSegments } = useSegments(); | ||||
|     const addEditStrategyEnabled = useUiFlag('addEditStrategy'); | ||||
| 
 | ||||
|     if ( | ||||
|         !addEditStrategyEnabled || | ||||
|         recentlyUsedSegmentIds.length === 0 || | ||||
|         !setSegments || | ||||
|         !allSegments | ||||
|     ) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     const segmentObjects = recentlyUsedSegmentIds | ||||
|         .map((id) => allSegments.find((segment) => segment.id === id)) | ||||
|         .filter((segment) => segment !== undefined) as ISegment[]; | ||||
| 
 | ||||
|     if (segmentObjects.length === 0) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledContainer> | ||||
|             <StyledHeader>Recently used segments</StyledHeader> | ||||
|             <StyledSegmentsContainer> | ||||
|                 {segmentObjects.map((segment) => ( | ||||
|                     <FeatureStrategySegmentChip | ||||
|                         key={segment.id} | ||||
|                         segment={segment} | ||||
|                         setSegments={setSegments} | ||||
|                         preview={undefined} | ||||
|                         setPreview={() => {}} | ||||
|                     /> | ||||
|                 ))} | ||||
|             </StyledSegmentsContainer> | ||||
|         </StyledContainer> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,99 @@ | ||||
| import { useRecentlyUsedSegments } from './useRecentlyUsedSegments'; | ||||
| import { renderHook, act } from '@testing-library/react'; | ||||
| 
 | ||||
| describe('useRecentlyUsedSegments', () => { | ||||
|     beforeEach(() => { | ||||
|         window.localStorage.clear(); | ||||
|     }); | ||||
| 
 | ||||
|     it('should initialize with empty array when no items in localStorage', () => { | ||||
|         const { result } = renderHook(() => useRecentlyUsedSegments()); | ||||
| 
 | ||||
|         expect(result.current.items).toEqual([]); | ||||
|     }); | ||||
| 
 | ||||
|     it('should initialize with initial items if provided', () => { | ||||
|         const initialItems = [1]; | ||||
|         const { result } = renderHook(() => | ||||
|             useRecentlyUsedSegments(initialItems), | ||||
|         ); | ||||
| 
 | ||||
|         expect(result.current.items).toEqual(initialItems); | ||||
|     }); | ||||
| 
 | ||||
|     it('should add new items to the beginning of the list', () => { | ||||
|         const { result } = renderHook(() => useRecentlyUsedSegments()); | ||||
| 
 | ||||
|         act(() => { | ||||
|             result.current.addItem(1); | ||||
|         }); | ||||
|         expect(result.current.items[0]).toBe(1); | ||||
| 
 | ||||
|         act(() => { | ||||
|             result.current.addItem(2); | ||||
|         }); | ||||
|         expect(result.current.items[0]).toBe(2); | ||||
|         expect(result.current.items[1]).toBe(1); | ||||
|     }); | ||||
| 
 | ||||
|     it('should handle array of segment IDs', () => { | ||||
|         const { result } = renderHook(() => useRecentlyUsedSegments()); | ||||
| 
 | ||||
|         act(() => { | ||||
|             result.current.addItem([1, 2, 3]); | ||||
|         }); | ||||
| 
 | ||||
|         expect(result.current.items.length).toBe(3); | ||||
|         expect(result.current.items[0]).toBe(3); | ||||
|         expect(result.current.items[1]).toBe(2); | ||||
|         expect(result.current.items[2]).toBe(1); | ||||
|     }); | ||||
| 
 | ||||
|     it('should limit stored items to maximum of 3', () => { | ||||
|         const { result } = renderHook(() => useRecentlyUsedSegments()); | ||||
| 
 | ||||
|         act(() => { | ||||
|             result.current.addItem(1); | ||||
|             result.current.addItem(2); | ||||
|             result.current.addItem(3); | ||||
|             result.current.addItem(4); | ||||
|         }); | ||||
| 
 | ||||
|         expect(result.current.items.length).toBe(3); | ||||
|         expect(result.current.items[0]).toBe(4); | ||||
|         expect(result.current.items[1]).toBe(3); | ||||
|         expect(result.current.items[2]).toBe(2); | ||||
|     }); | ||||
| 
 | ||||
|     it('should not add duplicate segment IDs', () => { | ||||
|         const { result } = renderHook(() => useRecentlyUsedSegments()); | ||||
| 
 | ||||
|         act(() => { | ||||
|             result.current.addItem(1); | ||||
|             result.current.addItem(2); | ||||
|         }); | ||||
|         expect(result.current.items.length).toBe(2); | ||||
| 
 | ||||
|         act(() => { | ||||
|             result.current.addItem(1); | ||||
|         }); | ||||
| 
 | ||||
|         expect(result.current.items.length).toBe(2); | ||||
|         expect(result.current.items[0]).toBe(1); | ||||
|         expect(result.current.items[1]).toBe(2); | ||||
|     }); | ||||
| 
 | ||||
|     it('should persist items to localStorage', () => { | ||||
|         const { result } = renderHook(() => useRecentlyUsedSegments()); | ||||
| 
 | ||||
|         act(() => { | ||||
|             result.current.addItem(1); | ||||
|         }); | ||||
| 
 | ||||
|         const { result: newResult } = renderHook(() => | ||||
|             useRecentlyUsedSegments(), | ||||
|         ); | ||||
| 
 | ||||
|         expect(newResult.current.items[0]).toBe(1); | ||||
|     }); | ||||
| }); | ||||
| @ -0,0 +1,28 @@ | ||||
| import { useLocalStorageState } from 'hooks/useLocalStorageState'; | ||||
| 
 | ||||
| export const useRecentlyUsedSegments = (initialItems: number[] = []) => { | ||||
|     const [items, setItems] = useLocalStorageState<number[]>( | ||||
|         'recently-used-segments', | ||||
|         initialItems, | ||||
|     ); | ||||
| 
 | ||||
|     const addItem = (newItem: number | number[]) => { | ||||
|         setItems((prevItems) => { | ||||
|             const itemsToAdd = Array.isArray(newItem) ? newItem : [newItem]; | ||||
| 
 | ||||
|             let updatedItems = [...prevItems]; | ||||
|             itemsToAdd.forEach((id) => { | ||||
|                 updatedItems = updatedItems.filter( | ||||
|                     (existingId) => existingId !== id, | ||||
|                 ); | ||||
|                 updatedItems = [id, ...updatedItems]; | ||||
|             }); | ||||
|             return updatedItems.slice(0, 3); | ||||
|         }); | ||||
|     }; | ||||
| 
 | ||||
|     return { | ||||
|         items, | ||||
|         addItem, | ||||
|     }; | ||||
| }; | ||||
| @ -5,6 +5,7 @@ import type { | ||||
| } from 'interfaces/strategy'; | ||||
| import useAPI from '../useApi/useApi'; | ||||
| import { useRecentlyUsedConstraints } from 'component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/useRecentlyUsedConstraints'; | ||||
| import { useRecentlyUsedSegments } from 'component/feature/FeatureStrategy/FeatureStrategySegment/RecentlyUsedSegments/useRecentlyUsedSegments'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| 
 | ||||
| const useFeatureStrategyApi = () => { | ||||
| @ -14,6 +15,7 @@ const useFeatureStrategyApi = () => { | ||||
| 
 | ||||
|     const { addItem: addToRecentlyUsedConstraints } = | ||||
|         useRecentlyUsedConstraints(); | ||||
|     const { addItem: addToRecentlyUsedSegments } = useRecentlyUsedSegments(); | ||||
|     const addEditStrategyEnabled = useUiFlag('addEditStrategy'); | ||||
| 
 | ||||
|     const addStrategyToFeature = async ( | ||||
| @ -30,12 +32,14 @@ const useFeatureStrategyApi = () => { | ||||
|         ); | ||||
|         const result = await makeRequest(req.caller, req.id); | ||||
| 
 | ||||
|         if ( | ||||
|             addEditStrategyEnabled && | ||||
|             payload.constraints && | ||||
|             payload.constraints.length > 0 | ||||
|         ) { | ||||
|             addToRecentlyUsedConstraints(payload.constraints); | ||||
|         if (addEditStrategyEnabled) { | ||||
|             if (payload.constraints && payload.constraints.length > 0) { | ||||
|                 addToRecentlyUsedConstraints(payload.constraints); | ||||
|             } | ||||
| 
 | ||||
|             if (payload.segments && payload.segments.length > 0) { | ||||
|                 addToRecentlyUsedSegments(payload.segments); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return result.json(); | ||||
| @ -71,12 +75,14 @@ const useFeatureStrategyApi = () => { | ||||
|         ); | ||||
|         await makeRequest(req.caller, req.id); | ||||
| 
 | ||||
|         if ( | ||||
|             addEditStrategyEnabled && | ||||
|             payload.constraints && | ||||
|             payload.constraints.length > 0 | ||||
|         ) { | ||||
|             addToRecentlyUsedConstraints(payload.constraints); | ||||
|         if (addEditStrategyEnabled) { | ||||
|             if (payload.constraints && payload.constraints.length > 0) { | ||||
|                 addToRecentlyUsedConstraints(payload.constraints); | ||||
|             } | ||||
| 
 | ||||
|             if (payload.segments && payload.segments.length > 0) { | ||||
|                 addToRecentlyUsedSegments(payload.segments); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user