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} + + + + ); +};