mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: Lifecycle in project overview (#7024)
This commit is contained in:
		
							parent
							
								
									476959df8e
								
							
						
					
					
						commit
						3fc7714e78
					
				| @ -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<IFeatureSeenCellProps> = ({ | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| interface IFeatureLifecycleProps { | ||||
|     feature: { | ||||
|         environments?: FeatureSearchEnvironmentSchema[]; | ||||
|         lastSeenAt?: string | null; | ||||
|         project: string; | ||||
|         name: string; | ||||
|     }; | ||||
|     onComplete: () => void; | ||||
|     onUncomplete: () => void; | ||||
|     onArchive: () => void; | ||||
| } | ||||
| 
 | ||||
| export const FeatureLifecycleCell: VFC<IFeatureLifecycleProps> = ({ | ||||
|     feature, | ||||
|     onComplete, | ||||
|     onUncomplete, | ||||
|     onArchive, | ||||
|     ...rest | ||||
| }) => { | ||||
|     const environments = feature.environments | ||||
|         ? Object.values(feature.environments) | ||||
|         : []; | ||||
| 
 | ||||
|     return ( | ||||
|         <Box sx={{ display: 'flex' }}> | ||||
|             <FeatureEnvironmentSeen | ||||
|                 featureLastSeen={feature.lastSeenAt || undefined} | ||||
|                 environments={environments} | ||||
|                 {...rest} | ||||
|             /> | ||||
|             <FeatureLifecycle | ||||
|                 onArchive={onArchive} | ||||
|                 onComplete={onComplete} | ||||
|                 onUncomplete={onUncomplete} | ||||
|                 feature={feature} | ||||
|             /> | ||||
|         </Box> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export const MemoizedFeatureEnvironmentSeenCell = React.memo( | ||||
|     FeatureEnvironmentSeenCell, | ||||
| ); | ||||
|  | ||||
| @ -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); | ||||
| 
 | ||||
|  | ||||
| @ -504,6 +504,6 @@ export const FeatureLifecycleTooltip: FC<{ | ||||
|             </Box> | ||||
|         } | ||||
|     > | ||||
|         <Box>{children}</Box> | ||||
|         <CenteredBox>{children}</CenteredBox> | ||||
|     </HtmlTooltip> | ||||
| ); | ||||
|  | ||||
| @ -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<IFeatureToggle, 'lifecycle' | 'environments'>, | ||||
|     feature: Pick<LifecycleFeature, 'lifecycle' | 'environments'>, | ||||
| ): 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, | ||||
|  | ||||
| @ -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,10 +201,26 @@ export const ProjectFeatureToggles = ({ | ||||
|                 id: 'lastSeenAt', | ||||
|                 header: 'Last seen', | ||||
|                 cell: ({ row: { original } }) => ( | ||||
|                     <ConditionallyRender | ||||
|                         condition={featureLifecycleEnabled} | ||||
|                         show={ | ||||
|                             <FeatureLifecycleCell | ||||
|                                 feature={original} | ||||
|                                 onComplete={refetch} | ||||
|                                 onUncomplete={refetch} | ||||
|                                 onArchive={() => | ||||
|                                     setFeatureArchiveState(original.name) | ||||
|                                 } | ||||
|                                 data-loading | ||||
|                             /> | ||||
|                         } | ||||
|                         elseShow={ | ||||
|                             <MemoizedFeatureEnvironmentSeenCell | ||||
|                                 feature={original} | ||||
|                                 data-loading | ||||
|                             /> | ||||
|                         } | ||||
|                     /> | ||||
|                 ), | ||||
|                 size: 50, | ||||
|                 meta: { | ||||
| @ -308,10 +330,12 @@ export const ProjectFeatureToggles = ({ | ||||
|                         { | ||||
|                             name: 'development', | ||||
|                             enabled: false, | ||||
|                             type: 'development', | ||||
|                         }, | ||||
|                         { | ||||
|                             name: 'production', | ||||
|                             enabled: false, | ||||
|                             type: 'production', | ||||
|                         }, | ||||
|                     ], | ||||
|                 })), | ||||
|  | ||||
| @ -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<IDependency>; | ||||
|     lifecycle?: { | ||||
|         stage: 'initial' | 'pre-live' | 'live' | 'completed' | 'archived'; | ||||
|         enteredStageAt: string; | ||||
|     }; | ||||
|     lifecycle?: Lifecycle; | ||||
|     children: Array<string>; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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 */ | ||||
|  | ||||
| @ -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 */ | ||||
|  | ||||
| @ -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; | ||||
| }; | ||||
| @ -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; | ||||
| @ -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'; | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user