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

feat: flag lifecycle status - first pass (#9736)

Resolving "status" column for flags overview page
This commit is contained in:
Tymoteusz Czech 2025-04-10 12:02:25 +02:00 committed by GitHub
parent 67c0ffa1ab
commit 1043eb8f03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 247 additions and 1 deletions

View File

@ -41,6 +41,7 @@ import { LifecycleFilters } from './FeatureToggleFilters/LifecycleFilters';
import { ExportFlags } from './ExportFlags';
import { createFeatureOverviewCell } from 'component/common/Table/cells/FeatureOverviewCell/FeatureOverviewCell';
import { AvatarCell } from 'component/project/Project/PaginatedProjectFeatureToggles/AvatarCell';
import { StatusCell } from './StatusCell/StatusCell';
export const featuresPlaceholder = Array(15).fill({
name: 'Name of the feature',
@ -175,7 +176,6 @@ export const FeatureToggleListTable: FC = () => {
meta: { width: '1%', align: 'center' },
enableSorting: false,
}),
columnHelper.accessor('lifecycle', {
id: 'lifecycle',
header: 'Lifecycle',
@ -190,6 +190,15 @@ export const FeatureToggleListTable: FC = () => {
size: 50,
meta: { width: '1%' },
}),
columnHelper.accessor('environments', {
id: 'status',
header: 'Status',
cell: ({ row: { original } }) => (
<StatusCell {...original} />
),
enableSorting: false,
size: 50,
}),
columnHelper.accessor('project', {
header: 'Project',
cell: ({ getValue }) => {

View File

@ -0,0 +1,20 @@
import { type FC, useMemo } from 'react';
import type { FeatureSearchResponseSchema } from 'openapi';
import { styled } from '@mui/material';
import { getStatus } from './getStatus';
const Container = styled('div')(({ theme }) => ({
padding: theme.spacing(0, 2),
}));
export const StatusCell: FC<FeatureSearchResponseSchema> = ({
lifecycle,
environments,
}) => {
const status = useMemo(
() => getStatus({ lifecycle, environments }),
[lifecycle, environments],
);
return <Container>{status}</Container>;
};

View File

@ -0,0 +1,151 @@
import { getStatus } from './getStatus';
import { PRODUCTION } from 'constants/environmentTypes';
import type { FeatureSearchEnvironmentSchema } from 'openapi';
const prodEnvEnabled: FeatureSearchEnvironmentSchema = {
name: 'production',
enabled: true,
type: PRODUCTION,
sortOrder: 1,
variantCount: 0,
lastSeenAt: null,
hasStrategies: true,
hasEnabledStrategies: true,
};
const prodEnvDisabled: FeatureSearchEnvironmentSchema = {
...prodEnvEnabled,
enabled: false,
hasEnabledStrategies: false,
};
const prodEnvDisabledNoStrategies: FeatureSearchEnvironmentSchema = {
...prodEnvDisabled,
hasStrategies: false,
};
const devEnvEnabled: FeatureSearchEnvironmentSchema = {
name: 'development',
enabled: true,
type: 'development',
sortOrder: 0,
variantCount: 0,
lastSeenAt: null,
hasStrategies: true,
hasEnabledStrategies: true,
};
const devEnvDisabledWithStrategies: FeatureSearchEnvironmentSchema = {
...devEnvEnabled,
enabled: false,
hasEnabledStrategies: false,
};
const devEnvDisabledNoStrategies: FeatureSearchEnvironmentSchema = {
...devEnvDisabledWithStrategies,
hasStrategies: false,
};
describe('getStatus', () => {
describe("lifecycle 'define' stage", () => {
it('has no strategies', () => {
expect(
getStatus({
environments: [devEnvDisabledNoStrategies, prodEnvDisabled],
lifecycle: {
stage: 'initial',
enteredStageAt: null as any,
},
}),
).toBe('No strategies');
});
it('has no enabled strategies', () => {
expect(
getStatus({
environments: [
devEnvDisabledWithStrategies,
prodEnvDisabled,
],
lifecycle: {
stage: 'initial',
enteredStageAt: null as any,
},
}),
).toBe('No enabled strategies');
});
it('has a non-production environment enabled', () => {
expect(
getStatus({
environments: [devEnvEnabled],
lifecycle: {
stage: 'initial',
enteredStageAt: null as any,
},
}),
).toBe('No traffic');
});
});
describe("lifecycle 'develop' stage", () => {
it('is paused', () => {
expect(
getStatus({
environments: [
devEnvDisabledWithStrategies,
prodEnvDisabled,
],
lifecycle: {
stage: 'pre-live',
enteredStageAt: null as any,
},
}),
).toBe('Paused');
expect(
getStatus({
environments: [devEnvEnabled, prodEnvDisabled],
lifecycle: {
stage: 'pre-live',
enteredStageAt: null as any,
},
}),
).not.toBe('Paused');
});
});
describe("lifecycle 'production' stage", () => {
it('has no production environments', () => {
expect(
getStatus({
environments: [devEnvEnabled],
lifecycle: { stage: 'live', enteredStageAt: null as any },
}),
).toBe('No production environments');
});
it('is paused', () => {
expect(
getStatus({
environments: [prodEnvDisabled],
lifecycle: { stage: 'live', enteredStageAt: null as any },
}),
).toBe('Paused');
expect(
getStatus({
environments: [prodEnvEnabled],
lifecycle: { stage: 'live', enteredStageAt: null as any },
}),
).not.toBe('Paused');
});
it('has no produciton strategies', () => {
expect(
getStatus({
environments: [prodEnvDisabledNoStrategies],
lifecycle: { stage: 'live', enteredStageAt: null as any },
}),
).toBe('No strategies');
});
});
});

View File

@ -0,0 +1,66 @@
import { PRODUCTION } from 'constants/environmentTypes';
import type { FeatureSearchResponseSchema } from 'openapi';
export const getStatus = ({
lifecycle,
environments,
}: Pick<
FeatureSearchResponseSchema,
'lifecycle' | 'environments' | 'lastSeenAt'
>) => {
if (lifecycle?.stage === 'initial') {
if (
environments?.some((env) => env.type !== PRODUCTION && env.enabled)
) {
return 'No traffic';
}
if (
!environments?.some(
(env) => env.type !== PRODUCTION && env.hasStrategies,
)
) {
return 'No strategies';
}
if (
!environments?.some(
(env) => env.type !== PRODUCTION && env.hasEnabledStrategies,
)
) {
return 'No enabled strategies';
}
}
if (lifecycle?.stage === 'pre-live') {
if (!environments?.some((env) => env.enabled)) {
return 'Paused';
}
}
const productionEnvironment = environments?.find(
(env) => env.type === PRODUCTION,
);
if (lifecycle?.stage === 'live') {
if (!productionEnvironment) {
return 'No production environments';
}
if (!productionEnvironment?.hasStrategies) {
return 'No strategies';
}
if (!productionEnvironment?.enabled) {
return 'Paused';
}
}
if (lifecycle?.stage === 'completed') {
if (productionEnvironment?.enabled) {
return 'Enabled';
}
}
return '';
};