mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-27 11:02:16 +01: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 { ExportFlags } from './ExportFlags';
|
||||||
import { createFeatureOverviewCell } from 'component/common/Table/cells/FeatureOverviewCell/FeatureOverviewCell';
|
import { createFeatureOverviewCell } from 'component/common/Table/cells/FeatureOverviewCell/FeatureOverviewCell';
|
||||||
import { AvatarCell } from 'component/project/Project/PaginatedProjectFeatureToggles/AvatarCell';
|
import { AvatarCell } from 'component/project/Project/PaginatedProjectFeatureToggles/AvatarCell';
|
||||||
|
import { StatusCell } from './StatusCell/StatusCell';
|
||||||
|
|
||||||
export const featuresPlaceholder = Array(15).fill({
|
export const featuresPlaceholder = Array(15).fill({
|
||||||
name: 'Name of the feature',
|
name: 'Name of the feature',
|
||||||
@ -175,7 +176,6 @@ export const FeatureToggleListTable: FC = () => {
|
|||||||
meta: { width: '1%', align: 'center' },
|
meta: { width: '1%', align: 'center' },
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
columnHelper.accessor('lifecycle', {
|
columnHelper.accessor('lifecycle', {
|
||||||
id: 'lifecycle',
|
id: 'lifecycle',
|
||||||
header: 'Lifecycle',
|
header: 'Lifecycle',
|
||||||
@ -190,6 +190,15 @@ export const FeatureToggleListTable: FC = () => {
|
|||||||
size: 50,
|
size: 50,
|
||||||
meta: { width: '1%' },
|
meta: { width: '1%' },
|
||||||
}),
|
}),
|
||||||
|
columnHelper.accessor('environments', {
|
||||||
|
id: 'status',
|
||||||
|
header: 'Status',
|
||||||
|
cell: ({ row: { original } }) => (
|
||||||
|
<StatusCell {...original} />
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
size: 50,
|
||||||
|
}),
|
||||||
columnHelper.accessor('project', {
|
columnHelper.accessor('project', {
|
||||||
header: 'Project',
|
header: 'Project',
|
||||||
cell: ({ getValue }) => {
|
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