From 8db708369df3d80fd5cd80b015fa440afa39475f Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Mon, 27 Oct 2025 14:46:51 +0200 Subject: [PATCH] feat: warning when empty segment (#10868) --- .../FeatureStrategySegmentList.test.tsx | 70 +++++++++++++++++++ .../FeatureStrategySegmentList.tsx | 23 +++++- .../MilestoneStrategySegmentList.test.tsx | 70 +++++++++++++++++++ .../MilestoneStrategySegmentList.tsx | 24 ++++++- 4 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.test.tsx create mode 100644 frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategySegmentList.test.tsx diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.test.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.test.tsx new file mode 100644 index 0000000000..f15b176fc9 --- /dev/null +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.test.tsx @@ -0,0 +1,70 @@ +import { screen } from '@testing-library/react'; +import { render } from 'utils/testRenderer'; +import { FeatureStrategySegmentList } from './FeatureStrategySegmentList.tsx'; +import type { ISegment } from 'interfaces/segment'; + +const createMockSegment = ( + id: number, + name: string, + constraints: ISegment['constraints'] = [], +): ISegment => ({ + id, + name, + description: `Description for ${name}`, + createdAt: '2023-01-01T00:00:00Z', + createdBy: 'test-user', + constraints, +}); + +describe('FeatureStrategySegmentList', () => { + test('should not show warning when segment has constraints', () => { + const segments: ISegment[] = [ + createMockSegment(1, 'Segment with constraints', [ + { + contextName: 'userId', + operator: 'IN', + values: ['user1', 'user2'], + }, + ]), + ]; + + render( + {}} + />, + ); + + expect(screen.getByText('Selected Segments')).toBeInTheDocument(); + expect( + screen.getByText('Segment with constraints'), + ).toBeInTheDocument(); + expect( + screen.queryByText(/You are adding an empty segment/i), + ).not.toBeInTheDocument(); + }); + + test('should show warning when segment has no constraints', () => { + const segments: ISegment[] = [ + createMockSegment(1, 'pre-access-demo-accounts', []), + ]; + + render( + {}} + />, + ); + + expect(screen.getByText('Selected Segments')).toBeInTheDocument(); + expect( + screen.getByText('pre-access-demo-accounts'), + ).toBeInTheDocument(); + expect( + screen.getByText(/You are adding an empty segment/i), + ).toBeInTheDocument(); + expect( + screen.getByText(/This will activate this feature for ALL USERS/i), + ).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx index 3e6c05940b..2b23d2d747 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx @@ -3,7 +3,7 @@ import { Fragment, useId, useState } from 'react'; import type { ISegment } from 'interfaces/segment'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { FeatureStrategySegmentChip } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentChip'; -import { styled } from '@mui/material'; +import { Alert, styled } from '@mui/material'; import { SegmentItem } from 'component/common/SegmentItem/SegmentItem'; interface IFeatureStrategySegmentListProps { @@ -38,6 +38,11 @@ const StyledPreviewContainer = styled('div')({ display: 'contents', }); +const StyledWarningAlert = styled(Alert)(({ theme }) => ({ + marginTop: theme.spacing(1.5), + marginBottom: theme.spacing(0.5), +})); + export const FeatureStrategySegmentList = ({ segments, setSegments, @@ -50,6 +55,11 @@ export const FeatureStrategySegmentList = ({ return null; } + const emptySegments = segments.filter( + (segment) => !segment.constraints || segment.constraints.length === 0, + ); + const hasEmptySegments = emptySegments.length > 0; + return ( <> } /> + + Warning! You are adding an empty + segment{emptySegments.length > 1 ? 's' : ''} ( + {emptySegments.map((s) => s.name).join(', ')}). This + will activate this feature for ALL USERS. + + } + /> {segments.map((segment, i) => ( diff --git a/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategySegmentList.test.tsx b/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategySegmentList.test.tsx new file mode 100644 index 0000000000..ee706d1f48 --- /dev/null +++ b/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategySegmentList.test.tsx @@ -0,0 +1,70 @@ +import { screen } from '@testing-library/react'; +import { render } from 'utils/testRenderer'; +import { MilestoneStrategySegmentList } from './MilestoneStrategySegmentList.tsx'; +import type { ISegment } from 'interfaces/segment'; + +const createMockSegment = ( + id: number, + name: string, + constraints: ISegment['constraints'] = [], +): ISegment => ({ + id, + name, + description: `Description for ${name}`, + createdAt: '2023-01-01T00:00:00Z', + createdBy: 'test-user', + constraints, +}); + +describe('MilestoneStrategySegmentList', () => { + test('should not show warning when segment has constraints', () => { + const segments: ISegment[] = [ + createMockSegment(1, 'Segment with constraints', [ + { + contextName: 'userId', + operator: 'IN', + values: ['user1', 'user2'], + }, + ]), + ]; + + render( + {}} + />, + ); + + expect(screen.getByText('Selected Segments')).toBeInTheDocument(); + expect( + screen.getByText('Segment with constraints'), + ).toBeInTheDocument(); + expect( + screen.queryByText(/You are adding an empty segment/i), + ).not.toBeInTheDocument(); + }); + + test('should show warning when segment has no constraints', () => { + const segments: ISegment[] = [ + createMockSegment(1, 'pre-access-demo-accounts', []), + ]; + + render( + {}} + />, + ); + + expect(screen.getByText('Selected Segments')).toBeInTheDocument(); + expect( + screen.getByText('pre-access-demo-accounts'), + ).toBeInTheDocument(); + expect( + screen.getByText(/You are adding an empty segment/i), + ).toBeInTheDocument(); + expect( + screen.getByText(/This will activate this feature for ALL USERS/i), + ).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategySegmentList.tsx b/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategySegmentList.tsx index 418f3baa63..691d228434 100644 --- a/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategySegmentList.tsx +++ b/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneStrategy/MilestoneStrategySegmentList.tsx @@ -1,8 +1,9 @@ +import type React from 'react'; import { Fragment, useState } from 'react'; import type { ISegment } from 'interfaces/segment'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { FeatureStrategySegmentChip } from 'component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentChip'; -import { styled } from '@mui/material'; +import { Alert, styled } from '@mui/material'; import { SegmentItem } from 'component/common/SegmentItem/SegmentItem'; const StyledList = styled('div')(({ theme }) => ({ @@ -28,6 +29,11 @@ const StyledAnd = styled('p')(({ theme }) => ({ backgroundColor: theme.palette.background.elevation2, })); +const StyledWarningAlert = styled(Alert)(({ theme }) => ({ + marginTop: theme.spacing(1.5), + marginBottom: theme.spacing(1.5), +})); + type IMilestoneStrategySegmentListProps = { segments: ISegment[]; setSegments: React.Dispatch>; @@ -44,6 +50,11 @@ export const MilestoneStrategySegmentList = ({ return null; } + const emptySegments = segments.filter( + (segment) => !segment.constraints || segment.constraints.length === 0, + ); + const hasEmptySegments = emptySegments.length > 0; + return ( <> } /> + + Warning! You are adding an empty + segment{emptySegments.length > 1 ? 's' : ''} ( + {emptySegments.map((s) => s.name).join(', ')}). This + will activate this feature for ALL USERS. + + } + /> {segments.map((segment, i) => (