mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-13 13:48:59 +02:00
Show change request stage timestamps in UI
Respects user's locale, uses `time` element. Does not address potential collisions with scheduled change requests, which have additional time information.
This commit is contained in:
parent
64050121db
commit
ca128cc7f6
@ -349,7 +349,13 @@ export const ChangeRequestOverview: FC = () => {
|
|||||||
<ChangeRequestHeader changeRequest={changeRequest} />
|
<ChangeRequestHeader changeRequest={changeRequest} />
|
||||||
<ChangeRequestBody>
|
<ChangeRequestBody>
|
||||||
<StyledAsideBox>
|
<StyledAsideBox>
|
||||||
<ChangeRequestTimeline {...timelineProps} />
|
<ChangeRequestTimeline
|
||||||
|
{...timelineProps}
|
||||||
|
timestamps={
|
||||||
|
//@ts-expect-error This hasn't been put on the model yet
|
||||||
|
changeRequest.stateTransitionTimestamps || {}
|
||||||
|
}
|
||||||
|
/>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={approversEnabled}
|
condition={approversEnabled}
|
||||||
show={
|
show={
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { Box, Paper, styled, Typography } 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 MuiTimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';
|
||||||
import TimelineSeparator from '@mui/lab/TimelineSeparator';
|
import TimelineSeparator from '@mui/lab/TimelineSeparator';
|
||||||
import TimelineDot from '@mui/lab/TimelineDot';
|
import TimelineDot from '@mui/lab/TimelineDot';
|
||||||
import TimelineConnector from '@mui/lab/TimelineConnector';
|
import TimelineConnector from '@mui/lab/TimelineConnector';
|
||||||
@ -12,13 +12,13 @@ import type {
|
|||||||
} from '../../changeRequest.types';
|
} from '../../changeRequest.types';
|
||||||
import { HtmlTooltip } from '../../../common/HtmlTooltip/HtmlTooltip.tsx';
|
import { HtmlTooltip } from '../../../common/HtmlTooltip/HtmlTooltip.tsx';
|
||||||
import ErrorIcon from '@mui/icons-material/Error';
|
import ErrorIcon from '@mui/icons-material/Error';
|
||||||
import {
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
type ILocationSettings,
|
import { formatDateYMDHM } from 'utils/formatDate';
|
||||||
useLocationSettings,
|
import type { ReactNode } from 'react-markdown/lib/react-markdown';
|
||||||
} from 'hooks/useLocationSettings';
|
|
||||||
import { formatDateYMDHMS } from 'utils/formatDate';
|
|
||||||
|
|
||||||
export type ISuggestChangeTimelineProps =
|
export type ISuggestChangeTimelineProps = {
|
||||||
|
timestamps?: Record<ChangeRequestState, string>;
|
||||||
|
} & (
|
||||||
| {
|
| {
|
||||||
state: Exclude<ChangeRequestState, 'Scheduled'>;
|
state: Exclude<ChangeRequestState, 'Scheduled'>;
|
||||||
schedule?: undefined;
|
schedule?: undefined;
|
||||||
@ -26,7 +26,8 @@ export type ISuggestChangeTimelineProps =
|
|||||||
| {
|
| {
|
||||||
state: 'Scheduled';
|
state: 'Scheduled';
|
||||||
schedule: ChangeRequestSchedule;
|
schedule: ChangeRequestSchedule;
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const StyledPaper = styled(Paper)(({ theme }) => ({
|
const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
@ -89,6 +90,7 @@ export const determineColor = (
|
|||||||
export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
|
export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
|
||||||
state,
|
state,
|
||||||
schedule,
|
schedule,
|
||||||
|
timestamps,
|
||||||
}) => {
|
}) => {
|
||||||
let data: ChangeRequestState[];
|
let data: ChangeRequestState[];
|
||||||
switch (state) {
|
switch (state) {
|
||||||
@ -109,10 +111,17 @@ export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
|
|||||||
<StyledBox>
|
<StyledBox>
|
||||||
<StyledTimeline>
|
<StyledTimeline>
|
||||||
{data.map((title, index) => {
|
{data.map((title, index) => {
|
||||||
|
const timestampComponent = timestamps?.[title] ? (
|
||||||
|
<Timestamp timestamp={timestamps?.[title]} />
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
if (schedule && title === 'Scheduled') {
|
if (schedule && title === 'Scheduled') {
|
||||||
return createTimelineScheduleItem(
|
return (
|
||||||
schedule,
|
<TimelineScheduleItem
|
||||||
locationSettings,
|
key={title}
|
||||||
|
schedule={schedule}
|
||||||
|
timestamp={timestampComponent}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,11 +141,17 @@ export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
|
|||||||
timelineDotProps = { variant: 'outlined' };
|
timelineDotProps = { variant: 'outlined' };
|
||||||
}
|
}
|
||||||
|
|
||||||
return createTimelineItem(
|
return (
|
||||||
color,
|
<TimelineItem
|
||||||
title,
|
key={title}
|
||||||
index < data.length - 1,
|
color={color}
|
||||||
timelineDotProps,
|
title={title}
|
||||||
|
shouldConnectToNextItem={
|
||||||
|
index < data.length - 1
|
||||||
|
}
|
||||||
|
timestamp={timestampComponent}
|
||||||
|
timelineDotProps={timelineDotProps}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</StyledTimeline>
|
</StyledTimeline>
|
||||||
@ -145,24 +160,53 @@ export const ChangeRequestTimeline: FC<ISuggestChangeTimelineProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTimelineItem = (
|
const Timestamp = styled(({ timestamp, ...props }: { timestamp: string }) => {
|
||||||
color: 'primary' | 'success' | 'grey' | 'error' | 'warning',
|
const { locationSettings } = useLocationSettings();
|
||||||
title: string,
|
const displayTime = formatDateYMDHM(
|
||||||
shouldConnectToNextItem: boolean,
|
new Date(timestamp || ''),
|
||||||
timelineDotProps: { [key: string]: string | undefined } = {},
|
locationSettings.locale,
|
||||||
) => (
|
);
|
||||||
<TimelineItem key={title}>
|
return (
|
||||||
|
<time {...props} dateTime={timestamp}>
|
||||||
|
{displayTime}
|
||||||
|
</time>
|
||||||
|
);
|
||||||
|
})(({ theme }) => ({
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
fontSize: theme.typography.body2.fontSize,
|
||||||
|
display: 'block',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const TimelineItem = ({
|
||||||
|
color,
|
||||||
|
title,
|
||||||
|
shouldConnectToNextItem,
|
||||||
|
timestamp,
|
||||||
|
timelineDotProps = {},
|
||||||
|
}: {
|
||||||
|
color: 'primary' | 'success' | 'grey' | 'error' | 'warning';
|
||||||
|
title: string;
|
||||||
|
shouldConnectToNextItem: boolean;
|
||||||
|
timestamp?: ReactNode;
|
||||||
|
timelineDotProps: { [key: string]: string | undefined };
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<MuiTimelineItem key={title}>
|
||||||
<TimelineSeparator>
|
<TimelineSeparator>
|
||||||
<TimelineDot color={color} {...timelineDotProps} />
|
<TimelineDot color={color} {...timelineDotProps} />
|
||||||
{shouldConnectToNextItem && <TimelineConnector />}
|
{shouldConnectToNextItem && <TimelineConnector />}
|
||||||
</TimelineSeparator>
|
</TimelineSeparator>
|
||||||
<TimelineContent>{title}</TimelineContent>
|
<TimelineContent>
|
||||||
</TimelineItem>
|
{title}
|
||||||
);
|
{timestamp}
|
||||||
|
</TimelineContent>
|
||||||
|
</MuiTimelineItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const getScheduleProps = (
|
export const getScheduleProps = (
|
||||||
schedule: ChangeRequestSchedule,
|
schedule: ChangeRequestSchedule,
|
||||||
formattedTime: string,
|
formattedTime: string = '2025/09/22 12:27',
|
||||||
) => {
|
) => {
|
||||||
switch (schedule.status) {
|
switch (schedule.status) {
|
||||||
case 'suspended':
|
case 'suspended':
|
||||||
@ -202,25 +246,24 @@ export const getScheduleProps = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createTimelineScheduleItem = (
|
const TimelineScheduleItem = ({
|
||||||
schedule: ChangeRequestSchedule,
|
schedule,
|
||||||
locationSettings: ILocationSettings,
|
timestamp,
|
||||||
) => {
|
}: {
|
||||||
const time = formatDateYMDHMS(
|
schedule: ChangeRequestSchedule;
|
||||||
new Date(schedule.scheduledAt),
|
timestamp: ReactNode;
|
||||||
locationSettings?.locale,
|
}) => {
|
||||||
);
|
const { title, subtitle, color, reason } = getScheduleProps(schedule);
|
||||||
|
|
||||||
const { title, subtitle, color, reason } = getScheduleProps(schedule, time);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TimelineItem key={title}>
|
<MuiTimelineItem key={title}>
|
||||||
<TimelineSeparator>
|
<TimelineSeparator>
|
||||||
<TimelineDot color={color} />
|
<TimelineDot color={color} />
|
||||||
<TimelineConnector />
|
<TimelineConnector />
|
||||||
</TimelineSeparator>
|
</TimelineSeparator>
|
||||||
<TimelineContent>
|
<TimelineContent>
|
||||||
{title}
|
{title}
|
||||||
|
{timestamp}
|
||||||
<StyledSubtitle>
|
<StyledSubtitle>
|
||||||
<Typography
|
<Typography
|
||||||
color={'text.secondary'}
|
color={'text.secondary'}
|
||||||
@ -229,6 +272,6 @@ const createTimelineScheduleItem = (
|
|||||||
{reason}
|
{reason}
|
||||||
</StyledSubtitle>
|
</StyledSubtitle>
|
||||||
</TimelineContent>
|
</TimelineContent>
|
||||||
</TimelineItem>
|
</MuiTimelineItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user