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:
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,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 (
|
||||
|
Loading…
Reference in New Issue
Block a user