1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-23 01:16:27 +02:00

feat: recently used segments (#9881)

This commit is contained in:
Jaanus Sellin 2025-05-05 10:15:43 +03:00 committed by GitHub
parent 832e3f2e79
commit 33f23cc0c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 215 additions and 12 deletions

View File

@ -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} />
</>
);
};

View File

@ -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>
);
};

View File

@ -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);
});
});

View File

@ -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,
};
};

View File

@ -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,14 +32,16 @@ const useFeatureStrategyApi = () => {
);
const result = await makeRequest(req.caller, req.id);
if (
addEditStrategyEnabled &&
payload.constraints &&
payload.constraints.length > 0
) {
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,13 +75,15 @@ const useFeatureStrategyApi = () => {
);
await makeRequest(req.caller, req.id);
if (
addEditStrategyEnabled &&
payload.constraints &&
payload.constraints.length > 0
) {
if (addEditStrategyEnabled) {
if (payload.constraints && payload.constraints.length > 0) {
addToRecentlyUsedConstraints(payload.constraints);
}
if (payload.segments && payload.segments.length > 0) {
addToRecentlyUsedSegments(payload.segments);
}
}
};
const setStrategiesSortOrder = async (