diff --git a/frontend/src/component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell.tsx b/frontend/src/component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell.tsx index 9defcae3c11f..e87ebe3853fc 100644 --- a/frontend/src/component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell.tsx +++ b/frontend/src/component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell.tsx @@ -1,6 +1,8 @@ import React, { type VFC } from 'react'; import { FeatureEnvironmentSeen } from 'component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen'; import type { FeatureSearchEnvironmentSchema } from 'openapi'; +import { FeatureLifecycle } from 'component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycle'; +import { Box } from '@mui/material'; interface IFeatureSeenCellProps { feature: { @@ -26,6 +28,46 @@ export const FeatureEnvironmentSeenCell: VFC = ({ ); }; +interface IFeatureLifecycleProps { + feature: { + environments?: FeatureSearchEnvironmentSchema[]; + lastSeenAt?: string | null; + project: string; + name: string; + }; + onComplete: () => void; + onUncomplete: () => void; + onArchive: () => void; +} + +export const FeatureLifecycleCell: VFC = ({ + feature, + onComplete, + onUncomplete, + onArchive, + ...rest +}) => { + const environments = feature.environments + ? Object.values(feature.environments) + : []; + + return ( + + + + + ); +}; + export const MemoizedFeatureEnvironmentSeenCell = React.memo( FeatureEnvironmentSeenCell, ); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycle.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycle.tsx index 1457835b7f3b..b4b5cc756ecf 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycle.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycle.tsx @@ -2,17 +2,25 @@ import { FeatureLifecycleStageIcon } from './FeatureLifecycleStageIcon'; import { FeatureLifecycleTooltip } from './FeatureLifecycleTooltip'; import useFeatureLifecycleApi from 'hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi'; import { populateCurrentStage } from './populateCurrentStage'; -import type { IFeatureToggle } from 'interfaces/featureToggle'; import type { FC } from 'react'; +import type { Lifecycle } from 'interfaces/featureToggle'; + +export interface LifecycleFeature { + lifecycle?: Lifecycle; + project: string; + name: string; + environments?: Array<{ + type: string; + name: string; + lastSeenAt?: string | null; + }>; +} export const FeatureLifecycle: FC<{ onArchive: () => void; onComplete: () => void; onUncomplete: () => void; - feature: Pick< - IFeatureToggle, - 'lifecycle' | 'environments' | 'project' | 'name' - >; + feature: LifecycleFeature; }> = ({ feature, onComplete, onUncomplete, onArchive }) => { const currentStage = populateCurrentStage(feature); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleTooltip.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleTooltip.tsx index 06ba0bab16a0..201ec897bfc1 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleTooltip.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleTooltip.tsx @@ -504,6 +504,6 @@ export const FeatureLifecycleTooltip: FC<{ } > - {children} + {children} ); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/populateCurrentStage.ts b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/populateCurrentStage.ts index fd33662f95ca..a7d914383f5c 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/populateCurrentStage.ts +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/populateCurrentStage.ts @@ -1,13 +1,13 @@ -import type { IFeatureToggle } from 'interfaces/featureToggle'; import type { LifecycleStage } from './LifecycleStage'; +import type { LifecycleFeature } from './FeatureLifecycle'; export const populateCurrentStage = ( - feature: Pick, + feature: Pick, ): LifecycleStage | undefined => { if (!feature.lifecycle) return undefined; const getFilteredEnvironments = (condition: (type: string) => boolean) => { - return feature.environments + return (feature.environments || []) .filter((env) => condition(env.type) && Boolean(env.lastSeenAt)) .map((env) => ({ name: env.name, diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx index f51c2e310631..9f5cf8187778 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectFeatureToggles.tsx @@ -13,7 +13,10 @@ import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi import { MemoizedRowSelectCell } from '../ProjectFeatureToggles/RowSelectCell/RowSelectCell'; import { BatchSelectionActionsBar } from 'component/common/BatchSelectionActionsBar/BatchSelectionActionsBar'; import { ProjectFeaturesBatchActions } from '../ProjectFeatureToggles/ProjectFeaturesBatchActions/ProjectFeaturesBatchActions'; -import { MemoizedFeatureEnvironmentSeenCell } from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell'; +import { + FeatureLifecycleCell, + MemoizedFeatureEnvironmentSeenCell, +} from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { useFeatureToggleSwitch } from '../ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch'; import useLoading from 'hooks/useLoading'; @@ -48,6 +51,7 @@ import { TableEmptyState } from './TableEmptyState/TableEmptyState'; import { useRowActions } from './hooks/useRowActions'; import { useSelectedData } from './hooks/useSelectedData'; import { FeatureOverviewCell } from '../../../common/Table/cells/FeatureOverviewCell/FeatureOverviewCell'; +import { useUiFlag } from 'hooks/useUiFlag'; interface IPaginatedProjectFeatureTogglesProps { environments: string[]; @@ -126,6 +130,8 @@ export const ProjectFeatureToggles = ({ const isPlaceholder = Boolean(initialLoad || (loading && total)); + const featureLifecycleEnabled = useUiFlag('featureLifecycle'); + const columns = useMemo( () => [ columnHelper.display({ @@ -195,9 +201,25 @@ export const ProjectFeatureToggles = ({ id: 'lastSeenAt', header: 'Last seen', cell: ({ row: { original } }) => ( - + setFeatureArchiveState(original.name) + } + data-loading + /> + } + elseShow={ + + } /> ), size: 50, @@ -308,10 +330,12 @@ export const ProjectFeatureToggles = ({ { name: 'development', enabled: false, + type: 'development', }, { name: 'production', enabled: false, + type: 'production', }, ], })), diff --git a/frontend/src/interfaces/featureToggle.ts b/frontend/src/interfaces/featureToggle.ts index 165ba75797be..4d5edd72c05e 100644 --- a/frontend/src/interfaces/featureToggle.ts +++ b/frontend/src/interfaces/featureToggle.ts @@ -32,6 +32,11 @@ export type ILastSeenEnvironments = Pick< 'name' | 'enabled' | 'lastSeenAt' | 'yes' | 'no' >; +export type Lifecycle = { + stage: 'initial' | 'pre-live' | 'live' | 'completed' | 'archived'; + enteredStageAt: string; +}; + export interface IFeatureToggle { stale: boolean; archived: boolean; @@ -49,10 +54,7 @@ export interface IFeatureToggle { impressionData: boolean; strategies?: IFeatureStrategy[]; dependencies: Array; - lifecycle?: { - stage: 'initial' | 'pre-live' | 'live' | 'completed' | 'archived'; - enteredStageAt: string; - }; + lifecycle?: Lifecycle; children: Array; } diff --git a/frontend/src/openapi/models/featureSearchEnvironmentSchema.ts b/frontend/src/openapi/models/featureSearchEnvironmentSchema.ts index cc584f04700c..0bcfeaecf5fb 100644 --- a/frontend/src/openapi/models/featureSearchEnvironmentSchema.ts +++ b/frontend/src/openapi/models/featureSearchEnvironmentSchema.ts @@ -37,7 +37,7 @@ export interface FeatureSearchEnvironmentSchema { /** A list of activation strategies for the feature environment */ strategies?: FeatureStrategySchema[]; /** The type of the environment */ - type?: string; + type: string; /** The number of defined variants */ variantCount?: number; /** A list of variants for the feature environment */ diff --git a/frontend/src/openapi/models/featureSearchResponseSchema.ts b/frontend/src/openapi/models/featureSearchResponseSchema.ts index d653ef59eae4..2f2f78a4b687 100644 --- a/frontend/src/openapi/models/featureSearchResponseSchema.ts +++ b/frontend/src/openapi/models/featureSearchResponseSchema.ts @@ -5,6 +5,7 @@ */ import type { FeatureSearchResponseSchemaDependencyType } from './featureSearchResponseSchemaDependencyType'; import type { FeatureSearchEnvironmentSchema } from './featureSearchEnvironmentSchema'; +import type { FeatureSearchResponseSchemaLifecycle } from './featureSearchResponseSchemaLifecycle'; import type { FeatureSearchResponseSchemaStrategiesItem } from './featureSearchResponseSchemaStrategiesItem'; import type { TagSchema } from './tagSchema'; import type { VariantSchema } from './variantSchema'; @@ -47,6 +48,8 @@ export interface FeatureSearchResponseSchema { * @nullable */ lastSeenAt?: string | null; + /** Current lifecycle stage of the feature */ + lifecycle?: FeatureSearchResponseSchemaLifecycle; /** Unique feature name */ name: string; /** Name of the project the feature belongs to */ diff --git a/frontend/src/openapi/models/featureSearchResponseSchemaLifecycle.ts b/frontend/src/openapi/models/featureSearchResponseSchemaLifecycle.ts new file mode 100644 index 000000000000..7be1f2267fc1 --- /dev/null +++ b/frontend/src/openapi/models/featureSearchResponseSchemaLifecycle.ts @@ -0,0 +1,16 @@ +/** + * Generated by Orval + * Do not edit manually. + * See `gen:api` script in package.json + */ +import type { FeatureSearchResponseSchemaLifecycleStage } from './featureSearchResponseSchemaLifecycleStage'; + +/** + * Current lifecycle stage of the feature + */ +export type FeatureSearchResponseSchemaLifecycle = { + /** When the feature entered this stage */ + enteredStageAt: string; + /** The name of the current lifecycle stage */ + stage: FeatureSearchResponseSchemaLifecycleStage; +}; diff --git a/frontend/src/openapi/models/featureSearchResponseSchemaLifecycleStage.ts b/frontend/src/openapi/models/featureSearchResponseSchemaLifecycleStage.ts new file mode 100644 index 000000000000..75849bc7e756 --- /dev/null +++ b/frontend/src/openapi/models/featureSearchResponseSchemaLifecycleStage.ts @@ -0,0 +1,20 @@ +/** + * Generated by Orval + * Do not edit manually. + * See `gen:api` script in package.json + */ + +/** + * The name of the current lifecycle stage + */ +export type FeatureSearchResponseSchemaLifecycleStage = + (typeof FeatureSearchResponseSchemaLifecycleStage)[keyof typeof FeatureSearchResponseSchemaLifecycleStage]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const FeatureSearchResponseSchemaLifecycleStage = { + initial: 'initial', + 'pre-live': 'pre-live', + live: 'live', + completed: 'completed', + archived: 'archived', +} as const; diff --git a/frontend/src/openapi/models/index.ts b/frontend/src/openapi/models/index.ts index 0c59f3de6825..e33be3f77bb4 100644 --- a/frontend/src/openapi/models/index.ts +++ b/frontend/src/openapi/models/index.ts @@ -554,6 +554,8 @@ export * from './featureSchemaStrategiesItem'; export * from './featureSearchEnvironmentSchema'; export * from './featureSearchResponseSchema'; export * from './featureSearchResponseSchemaDependencyType'; +export * from './featureSearchResponseSchemaLifecycle'; +export * from './featureSearchResponseSchemaLifecycleStage'; export * from './featureSearchResponseSchemaStrategiesItem'; export * from './featureStrategySchema'; export * from './featureStrategySegmentSchema'; diff --git a/src/lib/openapi/spec/feature-search-environment-schema.ts b/src/lib/openapi/spec/feature-search-environment-schema.ts index f266c30f7741..577fec9a6210 100644 --- a/src/lib/openapi/spec/feature-search-environment-schema.ts +++ b/src/lib/openapi/spec/feature-search-environment-schema.ts @@ -10,7 +10,7 @@ export const featureSearchEnvironmentSchema = { $id: '#/components/schemas/featureSearchEnvironmentSchema', type: 'object', additionalProperties: false, - required: ['name', 'enabled'], + required: ['name', 'enabled', 'type'], description: 'A detailed description of the feature environment', properties: { ...featureEnvironmentSchema.properties,