diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.test.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.test.tsx
new file mode 100644
index 000000000000..af3b4e904be5
--- /dev/null
+++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.test.tsx
@@ -0,0 +1,135 @@
+import React from 'react';
+import { render } from 'utils/testRenderer';
+import { screen } from '@testing-library/react';
+import { FeatureChange } from './FeatureChange';
+import {
+ ChangeRequestState,
+ IChangeRequestFeature,
+ IFeatureChange,
+} from 'component/changeRequest/changeRequest.types';
+
+describe('Schedule conflicts', () => {
+ const change = {
+ id: 15,
+ action: 'deleteStrategy' as const,
+ payload: {
+ id: 'b3ac8595-8ad3-419e-aa18-4d82f2b6bc4c',
+ name: 'flexibleRollout',
+ },
+ createdAt: new Date(),
+ createdBy: {
+ id: 1,
+ username: 'admin',
+ imageUrl: '',
+ },
+ scheduleConflicts: {
+ changeRequests: [
+ {
+ id: 73,
+ },
+ {
+ id: 80,
+ title: 'Adjust rollout percentage',
+ },
+ ],
+ },
+ };
+
+ const feature = (change: IFeatureChange): IChangeRequestFeature => ({
+ name: 'conflict-test',
+ changes: [change],
+ });
+
+ const changeRequest =
+ (feature: IChangeRequestFeature) => (state: ChangeRequestState) => ({
+ id: 1,
+ state,
+ title: '',
+ project: 'default',
+ environment: 'default',
+ minApprovals: 1,
+ createdBy: { id: 1, username: 'user1', imageUrl: '' },
+ createdAt: new Date(),
+ features: [feature],
+ segments: [],
+ approvals: [],
+ rejections: [],
+ comments: [],
+ });
+
+ it.each(['Draft', 'Scheduled', 'In review', 'Approved'])(
+ 'should show schedule conflicts (when they exist) for change request in the %s state',
+ async (changeRequestState) => {
+ const flag = feature(change);
+ render(
+ ,
+ );
+
+ const alert = await screen.findByRole('alert');
+
+ expect(
+ alert.textContent!.startsWith('Potential conflict'),
+ ).toBeTruthy();
+
+ const links = await screen.findAllByRole('link');
+
+ expect(links).toHaveLength(
+ change.scheduleConflicts.changeRequests.length,
+ );
+
+ const [link1, link2] = links;
+
+ expect(link1).toHaveTextContent('#73');
+ expect(link1).toHaveAccessibleDescription('Change request 73');
+ expect(link1).toHaveAttribute(
+ 'href',
+ `/projects/default/change-requests/73`,
+ );
+
+ expect(link2).toHaveTextContent('#80 (Adjust rollout percentage)');
+ expect(link2).toHaveAccessibleDescription('Change request 80');
+ expect(link2).toHaveAttribute(
+ 'href',
+ `/projects/default/change-requests/80`,
+ );
+ },
+ );
+
+ it.each(['Draft', 'Scheduled', 'In review', 'Approved'])(
+ 'should not show schedule conflicts when they do not exist for change request in the %s state',
+ async (changeRequestState) => {
+ const { scheduleConflicts, ...changeWithNoScheduleConflicts } =
+ change;
+
+ const flag = feature(changeWithNoScheduleConflicts);
+
+ render(
+ ,
+ );
+
+ const links = screen.queryByRole('link');
+
+ expect(links).toBe(null);
+
+ const alert = screen.queryByRole('alert');
+
+ expect(alert).toBe(null);
+ },
+ );
+});
diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx
index eab19b99afce..1cc1800d42c8 100644
--- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx
@@ -13,6 +13,7 @@ import { VariantPatch } from './VariantPatch/VariantPatch';
import { EnvironmentStrategyExecutionOrder } from './EnvironmentStrategyExecutionOrder/EnvironmentStrategyExecutionOrder';
import { ArchiveFeatureChange } from './ArchiveFeatureChange';
import { DependencyChange } from './DependencyChange';
+import { Link } from 'react-router-dom';
const StyledSingleChangeBox = styled(Box, {
shouldForwardProp: (prop: string) => !prop.startsWith('$'),
@@ -56,6 +57,15 @@ const StyledAlert = styled(Alert)(({ theme }) => ({
},
}));
+const InlineList = styled('ul')(({ theme }) => ({
+ display: 'inline',
+ padding: 0,
+ li: { display: 'inline' },
+ 'li + li::before': {
+ content: '", "',
+ },
+}));
+
export const FeatureChange: FC<{
actions: ReactNode;
index: number;
@@ -71,9 +81,12 @@ export const FeatureChange: FC<{
return (
+
+ Potential conflict! This change would
+ create conflicts with the following scheduled change
+ request(s):{' '}
+
+ {(
+ change.scheduleConflicts ?? {
+ changeRequests: [],
+ }
+ ).changeRequests.map(({ id, title }) => {
+ const text = title
+ ? `#${id} (${title})`
+ : `#${id}`;
+ return (
+
+
+ {text}
+
+
+ );
+ })}
+ .
+
+
+ }
+ />
+
({ padding: theme.spacing(3) })}>
{(change.action === 'addDependency' ||
change.action === 'deleteDependency') && (
diff --git a/frontend/src/component/changeRequest/changeRequest.types.ts b/frontend/src/component/changeRequest/changeRequest.types.ts
index e79a42fddb0c..0a077282e830 100644
--- a/frontend/src/component/changeRequest/changeRequest.types.ts
+++ b/frontend/src/component/changeRequest/changeRequest.types.ts
@@ -66,6 +66,9 @@ export interface IChangeRequestChangeBase {
conflict?: string;
createdBy?: Pick;
createdAt?: Date;
+ scheduleConflicts?: {
+ changeRequests: { id: number; title?: string }[];
+ };
}
export type ChangeRequestState =