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:
parent
67c0ffa1ab
commit
1043eb8f03
@ -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 }) => {
|
||||
|
@ -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>;
|
||||
};
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
@ -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 '–';
|
||||
};
|
Loading…
Reference in New Issue
Block a user