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 { Box, styled, Typography } from '@mui/material';
|
||||||
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import { RecentlyUsedSegments } from './RecentlyUsedSegments/RecentlyUsedSegments';
|
||||||
|
|
||||||
interface IFeatureStrategySegmentProps {
|
interface IFeatureStrategySegmentProps {
|
||||||
segments: ISegment[];
|
segments: ISegment[];
|
||||||
@ -105,6 +106,7 @@ export const FeatureStrategySegment = ({
|
|||||||
segments={selectedSegments}
|
segments={selectedSegments}
|
||||||
setSegments={setSelectedSegments}
|
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';
|
} from 'interfaces/strategy';
|
||||||
import useAPI from '../useApi/useApi';
|
import useAPI from '../useApi/useApi';
|
||||||
import { useRecentlyUsedConstraints } from 'component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/useRecentlyUsedConstraints';
|
import { useRecentlyUsedConstraints } from 'component/feature/FeatureStrategy/FeatureStrategyConstraints/RecentlyUsedConstraints/useRecentlyUsedConstraints';
|
||||||
|
import { useRecentlyUsedSegments } from 'component/feature/FeatureStrategy/FeatureStrategySegment/RecentlyUsedSegments/useRecentlyUsedSegments';
|
||||||
import { useUiFlag } from 'hooks/useUiFlag';
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
|
||||||
const useFeatureStrategyApi = () => {
|
const useFeatureStrategyApi = () => {
|
||||||
@ -14,6 +15,7 @@ const useFeatureStrategyApi = () => {
|
|||||||
|
|
||||||
const { addItem: addToRecentlyUsedConstraints } =
|
const { addItem: addToRecentlyUsedConstraints } =
|
||||||
useRecentlyUsedConstraints();
|
useRecentlyUsedConstraints();
|
||||||
|
const { addItem: addToRecentlyUsedSegments } = useRecentlyUsedSegments();
|
||||||
const addEditStrategyEnabled = useUiFlag('addEditStrategy');
|
const addEditStrategyEnabled = useUiFlag('addEditStrategy');
|
||||||
|
|
||||||
const addStrategyToFeature = async (
|
const addStrategyToFeature = async (
|
||||||
@ -30,12 +32,14 @@ const useFeatureStrategyApi = () => {
|
|||||||
);
|
);
|
||||||
const result = await makeRequest(req.caller, req.id);
|
const result = await makeRequest(req.caller, req.id);
|
||||||
|
|
||||||
if (
|
if (addEditStrategyEnabled) {
|
||||||
addEditStrategyEnabled &&
|
if (payload.constraints && payload.constraints.length > 0) {
|
||||||
payload.constraints &&
|
addToRecentlyUsedConstraints(payload.constraints);
|
||||||
payload.constraints.length > 0
|
}
|
||||||
) {
|
|
||||||
addToRecentlyUsedConstraints(payload.constraints);
|
if (payload.segments && payload.segments.length > 0) {
|
||||||
|
addToRecentlyUsedSegments(payload.segments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.json();
|
return result.json();
|
||||||
@ -71,12 +75,14 @@ const useFeatureStrategyApi = () => {
|
|||||||
);
|
);
|
||||||
await makeRequest(req.caller, req.id);
|
await makeRequest(req.caller, req.id);
|
||||||
|
|
||||||
if (
|
if (addEditStrategyEnabled) {
|
||||||
addEditStrategyEnabled &&
|
if (payload.constraints && payload.constraints.length > 0) {
|
||||||
payload.constraints &&
|
addToRecentlyUsedConstraints(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