diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx
index 0305e47080..939648f280 100644
--- a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx
@@ -2,7 +2,10 @@ import { Alert, Box, Button, styled, Typography } from '@mui/material';
import { FC, useContext, useState } from 'react';
import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest';
import { ChangeRequestHeader } from './ChangeRequestHeader/ChangeRequestHeader';
-import { ChangeRequestTimeline } from './ChangeRequestTimeline/ChangeRequestTimeline';
+import {
+ ChangeRequestTimeline,
+ ISuggestChangeTimelineProps,
+} from './ChangeRequestTimeline/ChangeRequestTimeline';
import { ChangeRequest } from '../ChangeRequest/ChangeRequest';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
@@ -285,20 +288,23 @@ export const ChangeRequestOverview: FC = () => {
? changeRequest.schedule.scheduledAt
: undefined;
+ const timelineProps: ISuggestChangeTimelineProps =
+ changeRequest.state === 'Scheduled'
+ ? {
+ state: 'Scheduled',
+ schedule: changeRequest.schedule,
+ }
+ : {
+ state: changeRequest.state,
+ schedule: undefined,
+ };
+
return (
<>
-
+
diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.test.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.test.tsx
index 97d971dd73..94600d3199 100644
--- a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.test.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.test.tsx
@@ -1,6 +1,10 @@
import { screen } from '@testing-library/react';
import { render } from 'utils/testRenderer';
-import { ChangeRequestTimeline, determineColor } from './ChangeRequestTimeline';
+import {
+ ChangeRequestTimeline,
+ determineColor,
+ getScheduleProps,
+} from './ChangeRequestTimeline';
import { ChangeRequestState } from '../../changeRequest.types';
test('cancelled timeline shows all states', () => {
@@ -44,7 +48,10 @@ test('scheduled timeline shows all states', () => {
render(
,
);
@@ -101,27 +108,64 @@ test('returns success for stages other than Rejected in Rejected state', () => {
),
).toBe('success');
});
-test('returns warning for Scheduled stage in Scheduled state', () => {
- expect(
- determineColor(
- 'Scheduled',
- irrelevantIndex,
- 'Scheduled',
- irrelevantIndex,
- ),
- ).toBe('warning');
-});
-test('returns error for Scheduled stage in Scheduled state with failure reason', () => {
- expect(
- determineColor(
- 'Scheduled',
- irrelevantIndex,
- 'Scheduled',
- irrelevantIndex,
- 'conflicts',
- ),
- ).toBe('error');
+describe('changeRequestScheduleProps', () => {
+ test('returns correct props for a pending schedule', () => {
+ const schedule = {
+ scheduledAt: new Date().toISOString(),
+ status: 'pending' as const,
+ };
+
+ const time = 'some time string';
+
+ const { title, subtitle, color, reason } = getScheduleProps(
+ schedule,
+ time,
+ );
+ expect(title).toBe('Scheduled');
+ expect(subtitle).toBe(`for ${time}`);
+ expect(color).toBe('warning');
+ expect(reason).toBeNull();
+ });
+
+ test('returns correct props for a failed schedule', () => {
+ const schedule = {
+ scheduledAt: new Date().toISOString(),
+ status: 'failed' as const,
+ reason: 'reason',
+ failureReason: 'failure reason',
+ };
+
+ const time = 'some time string';
+
+ const { title, subtitle, color, reason } = getScheduleProps(
+ schedule,
+ time,
+ );
+ expect(title).toBe('Schedule failed');
+ expect(subtitle).toBe(`at ${time}`);
+ expect(color).toBe('error');
+ expect(reason).toBeTruthy();
+ });
+
+ test('returns correct props for a suspended schedule', () => {
+ const schedule = {
+ scheduledAt: new Date().toISOString(),
+ status: 'suspended' as const,
+ reason: 'reason',
+ };
+
+ const time = 'some time string';
+
+ const { title, subtitle, color, reason } = getScheduleProps(
+ schedule,
+ time,
+ );
+ expect(title).toBe('Schedule suspended');
+ expect(subtitle).toBe(`was ${time}`);
+ expect(color).toBe('grey');
+ expect(reason).toBeTruthy();
+ });
});
test('returns success for stages at or before activeIndex', () => {
diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.tsx
index 9c0b4c9e52..d346bc6921 100644
--- a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestTimeline/ChangeRequestTimeline.tsx
@@ -6,18 +6,24 @@ import TimelineSeparator from '@mui/lab/TimelineSeparator';
import TimelineDot from '@mui/lab/TimelineDot';
import TimelineConnector from '@mui/lab/TimelineConnector';
import TimelineContent from '@mui/lab/TimelineContent';
-import { ChangeRequestState } from '../../changeRequest.types';
-import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
+import {
+ ChangeRequestSchedule,
+ ChangeRequestState,
+} from '../../changeRequest.types';
import { HtmlTooltip } from '../../../common/HtmlTooltip/HtmlTooltip';
import { Error as ErrorIcon } from '@mui/icons-material';
import { useLocationSettings } from 'hooks/useLocationSettings';
import { formatDateYMDHMS } from 'utils/formatDate';
-interface ISuggestChangeTimelineProps {
- state: ChangeRequestState;
- scheduledAt?: string;
- failureReason?: string;
-}
+export type ISuggestChangeTimelineProps =
+ | {
+ state: Exclude;
+ schedule?: undefined;
+ }
+ | {
+ state: 'Scheduled';
+ schedule: ChangeRequestSchedule;
+ };
const StyledPaper = styled(Paper)(({ theme }) => ({
marginTop: theme.spacing(2),
@@ -62,7 +68,6 @@ export const determineColor = (
changeRequestStateIndex: number,
displayStage: ChangeRequestState,
displayStageIndex: number,
- failureReason?: string,
) => {
if (changeRequestState === 'Cancelled') return 'grey';
@@ -70,19 +75,9 @@ export const determineColor = (
return displayStage === 'Rejected' ? 'error' : 'success';
if (
changeRequestStateIndex !== -1 &&
- changeRequestStateIndex > displayStageIndex
+ changeRequestStateIndex >= displayStageIndex
)
return 'success';
- if (
- changeRequestStateIndex !== -1 &&
- changeRequestStateIndex === displayStageIndex
- ) {
- return changeRequestState === 'Scheduled'
- ? failureReason
- ? 'error'
- : 'warning'
- : 'success';
- }
if (changeRequestStateIndex + 1 === displayStageIndex) return 'primary';
return 'grey';
@@ -90,8 +85,7 @@ export const determineColor = (
export const ChangeRequestTimeline: FC = ({
state,
- scheduledAt,
- failureReason,
+ schedule,
}) => {
let data: ChangeRequestState[];
switch (state) {
@@ -106,28 +100,20 @@ export const ChangeRequestTimeline: FC = ({
}
const activeIndex = data.findIndex((item) => item === state);
- const { locationSettings } = useLocationSettings();
-
return (
{data.map((title, index) => {
- const subtitle =
- scheduledAt &&
- state === 'Scheduled' &&
- state === title
- ? formatDateYMDHMS(
- new Date(scheduledAt),
- locationSettings?.locale,
- )
- : undefined;
+ if (schedule && title === 'Scheduled') {
+ return createTimelineScheduleItem(schedule);
+ }
+
const color = determineColor(
state,
activeIndex,
title,
index,
- failureReason,
);
let timelineDotProps = {};
@@ -142,8 +128,6 @@ export const ChangeRequestTimeline: FC = ({
return createTimelineItem(
color,
title,
- subtitle,
- failureReason,
index < data.length - 1,
timelineDotProps,
);
@@ -157,8 +141,6 @@ export const ChangeRequestTimeline: FC = ({
const createTimelineItem = (
color: 'primary' | 'success' | 'grey' | 'error' | 'warning',
title: string,
- subtitle: string | undefined,
- failureReason: string | undefined,
shouldConnectToNextItem: boolean,
timelineDotProps: { [key: string]: string | undefined } = {},
) => (
@@ -167,33 +149,78 @@ const createTimelineItem = (
{shouldConnectToNextItem && }
-
- {title}
-
- {`(for ${subtitle})`}
-
-
-
- }
- />
-
- }
- />
-
+ {title}
);
+
+export const getScheduleProps = (
+ schedule: ChangeRequestSchedule,
+ formattedTime: string,
+) => {
+ switch (schedule.status) {
+ case 'suspended':
+ return {
+ title: 'Schedule suspended',
+ subtitle: `was ${formattedTime}`,
+ color: 'grey' as const,
+ reason: (
+
+
+
+ ),
+ };
+ case 'failed':
+ return {
+ title: 'Schedule failed',
+ subtitle: `at ${formattedTime}`,
+ color: 'error' as const,
+ reason: (
+
+
+
+ ),
+ };
+ default:
+ return {
+ title: 'Scheduled',
+ subtitle: `for ${formattedTime}`,
+ color: 'warning' as const,
+ reason: null,
+ };
+ }
+};
+
+const createTimelineScheduleItem = (schedule: ChangeRequestSchedule) => {
+ const { locationSettings } = useLocationSettings();
+
+ const time = formatDateYMDHMS(
+ new Date(schedule.scheduledAt),
+ locationSettings?.locale,
+ );
+
+ const { title, subtitle, color, reason } = getScheduleProps(schedule, time);
+
+ return (
+
+
+
+
+
+
+ {title}
+
+ {`(${subtitle})`}
+ {reason}
+
+
+
+ );
+};