mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
Feat: change request scheduled timeline (#5346)
Adds the scheduled state to the timeline Closes # [1-1632](https://linear.app/unleash/issue/1-1632/update-the-progress-bar-for-scheduled-changes) ![Screenshot 2023-11-16 at 10 51 03](https://github.com/Unleash/unleash/assets/104830839/6267299e-d5c3-4cbf-9ab2-25da53f2d526) --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
parent
f8a9d7f355
commit
0dc5f306cc
@ -238,7 +238,10 @@ export const ChangeRequestOverview: FC = () => {
|
|||||||
<ChangeRequestHeader changeRequest={changeRequest} />
|
<ChangeRequestHeader changeRequest={changeRequest} />
|
||||||
<ChangeRequestBody>
|
<ChangeRequestBody>
|
||||||
<StyledAsideBox>
|
<StyledAsideBox>
|
||||||
<ChangeRequestTimeline state={changeRequest.state} />
|
<ChangeRequestTimeline
|
||||||
|
state={changeRequest.state}
|
||||||
|
scheduledAt={changeRequest.schedule?.scheduledAt}
|
||||||
|
/>
|
||||||
<ChangeRequestReviewers changeRequest={changeRequest} />
|
<ChangeRequestReviewers changeRequest={changeRequest} />
|
||||||
</StyledAsideBox>
|
</StyledAsideBox>
|
||||||
<StyledPaper elevation={0}>
|
<StyledPaper elevation={0}>
|
||||||
|
@ -41,6 +41,21 @@ test('rejected timeline shows all states', () => {
|
|||||||
expect(screen.queryByText('Applied')).not.toBeInTheDocument();
|
expect(screen.queryByText('Applied')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('scheduled timeline shows all states', () => {
|
||||||
|
render(
|
||||||
|
<ChangeRequestTimeline
|
||||||
|
state={'Scheduled'}
|
||||||
|
scheduledAt={new Date().toISOString()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Draft')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('In review')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Approved')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Scheduled')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Applied')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
const irrelevantIndex = -99; // Using a number that's unlikely to be a valid index
|
const irrelevantIndex = -99; // Using a number that's unlikely to be a valid index
|
||||||
|
|
||||||
test('returns grey for Cancelled state regardless of displayed stage', () => {
|
test('returns grey for Cancelled state regardless of displayed stage', () => {
|
||||||
@ -87,6 +102,16 @@ test('returns success for stages other than Rejected in Rejected state', () => {
|
|||||||
),
|
),
|
||||||
).toBe('success');
|
).toBe('success');
|
||||||
});
|
});
|
||||||
|
test('returns warning for Scheduled stage in Scheduled state', () => {
|
||||||
|
expect(
|
||||||
|
determineColor(
|
||||||
|
'Scheduled',
|
||||||
|
irrelevantIndex,
|
||||||
|
'Scheduled',
|
||||||
|
irrelevantIndex,
|
||||||
|
),
|
||||||
|
).toBe('warning');
|
||||||
|
});
|
||||||
|
|
||||||
test('returns success for stages at or before activeIndex', () => {
|
test('returns success for stages at or before activeIndex', () => {
|
||||||
expect(determineColor('In review', 1, 'Draft', 0)).toBe('success');
|
expect(determineColor('In review', 1, 'Draft', 0)).toBe('success');
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { Box, Paper, styled } from '@mui/material';
|
import { Box, Paper, styled, Typography } from '@mui/material';
|
||||||
import Timeline from '@mui/lab/Timeline';
|
import Timeline from '@mui/lab/Timeline';
|
||||||
import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';
|
import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';
|
||||||
import TimelineSeparator from '@mui/lab/TimelineSeparator';
|
import TimelineSeparator from '@mui/lab/TimelineSeparator';
|
||||||
@ -7,9 +7,11 @@ import TimelineDot from '@mui/lab/TimelineDot';
|
|||||||
import TimelineConnector from '@mui/lab/TimelineConnector';
|
import TimelineConnector from '@mui/lab/TimelineConnector';
|
||||||
import TimelineContent from '@mui/lab/TimelineContent';
|
import TimelineContent from '@mui/lab/TimelineContent';
|
||||||
import { ChangeRequestState } from '../../changeRequest.types';
|
import { ChangeRequestState } from '../../changeRequest.types';
|
||||||
|
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
interface ISuggestChangeTimelineProps {
|
interface ISuggestChangeTimelineProps {
|
||||||
state: ChangeRequestState;
|
state: ChangeRequestState;
|
||||||
|
scheduledAt?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledPaper = styled(Paper)(({ theme }) => ({
|
const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||||
@ -36,6 +38,13 @@ const steps: ChangeRequestState[] = [
|
|||||||
'Applied',
|
'Applied',
|
||||||
];
|
];
|
||||||
const rejectedSteps: ChangeRequestState[] = ['Draft', 'In review', 'Rejected'];
|
const rejectedSteps: ChangeRequestState[] = ['Draft', 'In review', 'Rejected'];
|
||||||
|
const scheduledSteps: ChangeRequestState[] = [
|
||||||
|
'Draft',
|
||||||
|
'In review',
|
||||||
|
'Approved',
|
||||||
|
'Scheduled',
|
||||||
|
'Applied',
|
||||||
|
];
|
||||||
|
|
||||||
export const determineColor = (
|
export const determineColor = (
|
||||||
changeRequestState: ChangeRequestState,
|
changeRequestState: ChangeRequestState,
|
||||||
@ -44,21 +53,40 @@ export const determineColor = (
|
|||||||
displayStageIndex: number,
|
displayStageIndex: number,
|
||||||
) => {
|
) => {
|
||||||
if (changeRequestState === 'Cancelled') return 'grey';
|
if (changeRequestState === 'Cancelled') return 'grey';
|
||||||
|
|
||||||
if (changeRequestState === 'Rejected')
|
if (changeRequestState === 'Rejected')
|
||||||
return displayStage === 'Rejected' ? 'error' : 'success';
|
return displayStage === 'Rejected' ? 'error' : 'success';
|
||||||
if (
|
if (
|
||||||
changeRequestStateIndex !== -1 &&
|
changeRequestStateIndex !== -1 &&
|
||||||
changeRequestStateIndex >= displayStageIndex
|
changeRequestStateIndex > displayStageIndex
|
||||||
)
|
)
|
||||||
return 'success';
|
return 'success';
|
||||||
|
if (
|
||||||
|
changeRequestStateIndex !== -1 &&
|
||||||
|
changeRequestStateIndex === displayStageIndex
|
||||||
|
) {
|
||||||
|
return changeRequestState === 'Scheduled' ? 'warning' : 'success';
|
||||||
|
}
|
||||||
|
|
||||||
if (changeRequestStateIndex + 1 === displayStageIndex) return 'primary';
|
if (changeRequestStateIndex + 1 === displayStageIndex) return 'primary';
|
||||||
return 'grey';
|
return 'grey';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
|
export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
|
||||||
state,
|
state,
|
||||||
|
scheduledAt,
|
||||||
}) => {
|
}) => {
|
||||||
const data = state === 'Rejected' ? rejectedSteps : steps;
|
let data;
|
||||||
|
switch (state) {
|
||||||
|
case 'Rejected':
|
||||||
|
data = rejectedSteps;
|
||||||
|
break;
|
||||||
|
case 'Scheduled':
|
||||||
|
data = scheduledSteps;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
data = steps;
|
||||||
|
}
|
||||||
const activeIndex = data.findIndex((item) => item === state);
|
const activeIndex = data.findIndex((item) => item === state);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -66,6 +94,12 @@ export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
|
|||||||
<StyledBox>
|
<StyledBox>
|
||||||
<StyledTimeline>
|
<StyledTimeline>
|
||||||
{data.map((title, index) => {
|
{data.map((title, index) => {
|
||||||
|
const subtitle =
|
||||||
|
scheduledAt &&
|
||||||
|
state === 'Scheduled' &&
|
||||||
|
state === title
|
||||||
|
? new Date(scheduledAt).toLocaleString()
|
||||||
|
: undefined;
|
||||||
const color = determineColor(
|
const color = determineColor(
|
||||||
state,
|
state,
|
||||||
activeIndex,
|
activeIndex,
|
||||||
@ -85,6 +119,7 @@ export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
|
|||||||
return createTimelineItem(
|
return createTimelineItem(
|
||||||
color,
|
color,
|
||||||
title,
|
title,
|
||||||
|
subtitle,
|
||||||
index < data.length - 1,
|
index < data.length - 1,
|
||||||
timelineDotProps,
|
timelineDotProps,
|
||||||
);
|
);
|
||||||
@ -96,8 +131,9 @@ export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createTimelineItem = (
|
const createTimelineItem = (
|
||||||
color: 'primary' | 'success' | 'grey' | 'error',
|
color: 'primary' | 'success' | 'grey' | 'error' | 'warning',
|
||||||
title: string,
|
title: string,
|
||||||
|
subtitle: string | undefined,
|
||||||
shouldConnectToNextItem: boolean,
|
shouldConnectToNextItem: boolean,
|
||||||
timelineDotProps: { [key: string]: string | undefined } = {},
|
timelineDotProps: { [key: string]: string | undefined } = {},
|
||||||
) => (
|
) => (
|
||||||
@ -106,6 +142,17 @@ const createTimelineItem = (
|
|||||||
<TimelineDot color={color} {...timelineDotProps} />
|
<TimelineDot color={color} {...timelineDotProps} />
|
||||||
{shouldConnectToNextItem && <TimelineConnector />}
|
{shouldConnectToNextItem && <TimelineConnector />}
|
||||||
</TimelineSeparator>
|
</TimelineSeparator>
|
||||||
<TimelineContent>{title}</TimelineContent>
|
<TimelineContent>
|
||||||
|
{title}
|
||||||
|
<br />
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(subtitle)}
|
||||||
|
show={
|
||||||
|
<Typography
|
||||||
|
color={'text.secondary'}
|
||||||
|
>{`(for ${subtitle})`}</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TimelineContent>
|
||||||
</TimelineItem>
|
</TimelineItem>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user