1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-31 13:47:02 +02:00

feat: lifecycle stage dates (#6926)

This commit is contained in:
Mateusz Kwasniewski 2024-04-25 13:30:00 +02:00 committed by GitHub
parent 68e7a3164e
commit 0eaf725e82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 56 additions and 27 deletions

View File

@ -23,6 +23,9 @@ import {
} from 'component/providers/AccessProvider/permissions'; } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { isSafeToArchive } from './isSafeToArchive'; import { isSafeToArchive } from './isSafeToArchive';
import { useLocationSettings } from 'hooks/useLocationSettings';
import { formatDateYMDHMS } from 'utils/formatDate';
import { formatDistanceToNow, parseISO } from 'date-fns';
const TimeLabel = styled('span')(({ theme }) => ({ const TimeLabel = styled('span')(({ theme }) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
@ -330,6 +333,18 @@ const CompletedStageDescription: FC<{
); );
}; };
const FormatTime: FC<{ time: string }> = ({ time }) => {
const { locationSettings } = useLocationSettings();
return <span>{formatDateYMDHMS(time, locationSettings.locale)}</span>;
};
const FormatElapsedTime: FC<{ time: string }> = ({ time }) => {
const pastTime = parseISO(time);
const elapsedTime = formatDistanceToNow(pastTime, { addSuffix: false });
return <span>{elapsedTime}</span>;
};
export const FeatureLifecycleTooltip: FC<{ export const FeatureLifecycleTooltip: FC<{
children: React.ReactElement<any, any>; children: React.ReactElement<any, any>;
stage: LifecycleStage; stage: LifecycleStage;
@ -358,11 +373,12 @@ export const FeatureLifecycleTooltip: FC<{
</MainLifecycleRow> </MainLifecycleRow>
<TimeLifecycleRow> <TimeLifecycleRow>
<TimeLabel>Stage entered at</TimeLabel> <TimeLabel>Stage entered at</TimeLabel>
<span>14/01/2024</span>
<FormatTime time={stage.enteredStageAt} />
</TimeLifecycleRow> </TimeLifecycleRow>
<TimeLifecycleRow> <TimeLifecycleRow>
<TimeLabel>Time spent in stage</TimeLabel> <TimeLabel>Time spent in stage</TimeLabel>
<span>3 days</span> <FormatElapsedTime time={stage.enteredStageAt} />
</TimeLifecycleRow> </TimeLifecycleRow>
<StageTimeline stage={stage} /> <StageTimeline stage={stage} />
</Box> </Box>

View File

@ -1,16 +1,19 @@
export type LifecycleStage = type TimedStage = { enteredStageAt: string };
| { name: 'initial' } export type LifecycleStage = TimedStage &
| { (
name: 'pre-live'; | { name: 'initial' }
environments: Array<{ name: string; lastSeenAt: string }>; | {
} name: 'pre-live';
| { environments: Array<{ name: string; lastSeenAt: string }>;
name: 'live'; }
environments: Array<{ name: string; lastSeenAt: string }>; | {
} name: 'live';
| { environments: Array<{ name: string; lastSeenAt: string }>;
name: 'completed'; }
environments: Array<{ name: string; lastSeenAt: string }>; | {
status: 'kept' | 'discarded'; name: 'completed';
} environments: Array<{ name: string; lastSeenAt: string }>;
| { name: 'archived' }; status: 'kept' | 'discarded';
}
| { name: 'archived' }
);

View File

@ -1,6 +1,8 @@
import { populateCurrentStage } from './populateCurrentStage'; import { populateCurrentStage } from './populateCurrentStage';
import type { IFeatureToggle } from '../../../../../interfaces/featureToggle'; import type { IFeatureToggle } from '../../../../../interfaces/featureToggle';
const enteredStageAt = 'date';
describe('populateCurrentStage', () => { describe('populateCurrentStage', () => {
it('should return undefined if lifecycle is not defined', () => { it('should return undefined if lifecycle is not defined', () => {
const feature = {}; const feature = {};
@ -10,16 +12,16 @@ describe('populateCurrentStage', () => {
it('should return initial stage when lifecycle stage is initial', () => { it('should return initial stage when lifecycle stage is initial', () => {
const feature = { const feature = {
lifecycle: { stage: 'initial' }, lifecycle: { stage: 'initial', enteredStageAt },
}; };
const expected = { name: 'initial' }; const expected = { name: 'initial', enteredStageAt };
const result = populateCurrentStage(feature as IFeatureToggle); const result = populateCurrentStage(feature as IFeatureToggle);
expect(result).toEqual(expected); expect(result).toEqual(expected);
}); });
it('should correctly populate pre-live stage with dev environments', () => { it('should correctly populate pre-live stage with dev environments', () => {
const feature = { const feature = {
lifecycle: { stage: 'pre-live' }, lifecycle: { stage: 'pre-live', enteredStageAt },
environments: [ environments: [
{ name: 'test', type: 'development', lastSeenAt: null }, { name: 'test', type: 'development', lastSeenAt: null },
{ name: 'test1', type: 'production', lastSeenAt: '2022-08-01' }, { name: 'test1', type: 'production', lastSeenAt: '2022-08-01' },
@ -29,6 +31,7 @@ describe('populateCurrentStage', () => {
const expected = { const expected = {
name: 'pre-live', name: 'pre-live',
environments: [{ name: 'dev', lastSeenAt: '2022-08-01' }], environments: [{ name: 'dev', lastSeenAt: '2022-08-01' }],
enteredStageAt,
}; };
const result = populateCurrentStage(feature); const result = populateCurrentStage(feature);
expect(result).toEqual(expected); expect(result).toEqual(expected);
@ -36,7 +39,7 @@ describe('populateCurrentStage', () => {
it('should handle live stage with production environments', () => { it('should handle live stage with production environments', () => {
const feature = { const feature = {
lifecycle: { stage: 'live' }, lifecycle: { stage: 'live', enteredStageAt },
environments: [ environments: [
{ name: 'prod', type: 'production', lastSeenAt: '2022-08-01' }, { name: 'prod', type: 'production', lastSeenAt: '2022-08-01' },
], ],
@ -44,6 +47,7 @@ describe('populateCurrentStage', () => {
const expected = { const expected = {
name: 'live', name: 'live',
environments: [{ name: 'prod', lastSeenAt: '2022-08-01' }], environments: [{ name: 'prod', lastSeenAt: '2022-08-01' }],
enteredStageAt,
}; };
const result = populateCurrentStage(feature); const result = populateCurrentStage(feature);
expect(result).toEqual(expected); expect(result).toEqual(expected);
@ -51,7 +55,7 @@ describe('populateCurrentStage', () => {
it('should return completed stage with production environments', () => { it('should return completed stage with production environments', () => {
const feature = { const feature = {
lifecycle: { stage: 'completed' }, lifecycle: { stage: 'completed', enteredStageAt },
environments: [ environments: [
{ name: 'prod', type: 'production', lastSeenAt: '2022-08-01' }, { name: 'prod', type: 'production', lastSeenAt: '2022-08-01' },
], ],
@ -60,6 +64,7 @@ describe('populateCurrentStage', () => {
name: 'completed', name: 'completed',
status: 'kept', status: 'kept',
environments: [{ name: 'prod', lastSeenAt: '2022-08-01' }], environments: [{ name: 'prod', lastSeenAt: '2022-08-01' }],
enteredStageAt,
}; };
const result = populateCurrentStage(feature); const result = populateCurrentStage(feature);
expect(result).toEqual(expected); expect(result).toEqual(expected);
@ -67,9 +72,9 @@ describe('populateCurrentStage', () => {
it('should return archived stage when lifecycle stage is archived', () => { it('should return archived stage when lifecycle stage is archived', () => {
const feature = { const feature = {
lifecycle: { stage: 'archived' }, lifecycle: { stage: 'archived', enteredStageAt },
} as IFeatureToggle; } as IFeatureToggle;
const expected = { name: 'archived' }; const expected = { name: 'archived', enteredStageAt };
const result = populateCurrentStage(feature); const result = populateCurrentStage(feature);
expect(result).toEqual(expected); expect(result).toEqual(expected);
}); });

View File

@ -15,15 +15,18 @@ export const populateCurrentStage = (
})); }));
}; };
const enteredStageAt = feature.lifecycle.enteredStageAt;
switch (feature.lifecycle.stage) { switch (feature.lifecycle.stage) {
case 'initial': case 'initial':
return { name: 'initial' }; return { name: 'initial', enteredStageAt };
case 'pre-live': case 'pre-live':
return { return {
name: 'pre-live', name: 'pre-live',
environments: getFilteredEnvironments( environments: getFilteredEnvironments(
(type) => type !== 'production', (type) => type !== 'production',
), ),
enteredStageAt,
}; };
case 'live': case 'live':
return { return {
@ -31,6 +34,7 @@ export const populateCurrentStage = (
environments: getFilteredEnvironments( environments: getFilteredEnvironments(
(type) => type === 'production', (type) => type === 'production',
), ),
enteredStageAt,
}; };
case 'completed': case 'completed':
return { return {
@ -39,9 +43,10 @@ export const populateCurrentStage = (
environments: getFilteredEnvironments( environments: getFilteredEnvironments(
(type) => type === 'production', (type) => type === 'production',
), ),
enteredStageAt,
}; };
case 'archived': case 'archived':
return { name: 'archived' }; return { name: 'archived', enteredStageAt };
default: default:
return undefined; return undefined;
} }