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 React, { type VFC } from 'react'; | ||||||
| import { FeatureEnvironmentSeen } from 'component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen'; | import { FeatureEnvironmentSeen } from 'component/feature/FeatureView/FeatureEnvironmentSeen/FeatureEnvironmentSeen'; | ||||||
| import type { FeatureSearchEnvironmentSchema } from 'openapi'; | import type { FeatureSearchEnvironmentSchema } from 'openapi'; | ||||||
|  | import { FeatureLifecycle } from 'component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycle'; | ||||||
|  | import { Box } from '@mui/material'; | ||||||
| 
 | 
 | ||||||
| interface IFeatureSeenCellProps { | interface IFeatureSeenCellProps { | ||||||
|     feature: { |     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( | export const MemoizedFeatureEnvironmentSeenCell = React.memo( | ||||||
|     FeatureEnvironmentSeenCell, |     FeatureEnvironmentSeenCell, | ||||||
| ); | ); | ||||||
|  | |||||||
| @ -2,17 +2,25 @@ import { FeatureLifecycleStageIcon } from './FeatureLifecycleStageIcon'; | |||||||
| import { FeatureLifecycleTooltip } from './FeatureLifecycleTooltip'; | import { FeatureLifecycleTooltip } from './FeatureLifecycleTooltip'; | ||||||
| import useFeatureLifecycleApi from 'hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi'; | import useFeatureLifecycleApi from 'hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi'; | ||||||
| import { populateCurrentStage } from './populateCurrentStage'; | import { populateCurrentStage } from './populateCurrentStage'; | ||||||
| import type { IFeatureToggle } from 'interfaces/featureToggle'; |  | ||||||
| import type { FC } from 'react'; | 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<{ | export const FeatureLifecycle: FC<{ | ||||||
|     onArchive: () => void; |     onArchive: () => void; | ||||||
|     onComplete: () => void; |     onComplete: () => void; | ||||||
|     onUncomplete: () => void; |     onUncomplete: () => void; | ||||||
|     feature: Pick< |     feature: LifecycleFeature; | ||||||
|         IFeatureToggle, |  | ||||||
|         'lifecycle' | 'environments' | 'project' | 'name' |  | ||||||
|     >; |  | ||||||
| }> = ({ feature, onComplete, onUncomplete, onArchive }) => { | }> = ({ feature, onComplete, onUncomplete, onArchive }) => { | ||||||
|     const currentStage = populateCurrentStage(feature); |     const currentStage = populateCurrentStage(feature); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -504,6 +504,6 @@ export const FeatureLifecycleTooltip: FC<{ | |||||||
|             </Box> |             </Box> | ||||||
|         } |         } | ||||||
|     > |     > | ||||||
|         <Box>{children}</Box> |         <CenteredBox>{children}</CenteredBox> | ||||||
|     </HtmlTooltip> |     </HtmlTooltip> | ||||||
| ); | ); | ||||||
|  | |||||||
| @ -1,13 +1,13 @@ | |||||||
| import type { IFeatureToggle } from 'interfaces/featureToggle'; |  | ||||||
| import type { LifecycleStage } from './LifecycleStage'; | import type { LifecycleStage } from './LifecycleStage'; | ||||||
|  | import type { LifecycleFeature } from './FeatureLifecycle'; | ||||||
| 
 | 
 | ||||||
| export const populateCurrentStage = ( | export const populateCurrentStage = ( | ||||||
|     feature: Pick<IFeatureToggle, 'lifecycle' | 'environments'>, |     feature: Pick<LifecycleFeature, 'lifecycle' | 'environments'>, | ||||||
| ): LifecycleStage | undefined => { | ): LifecycleStage | undefined => { | ||||||
|     if (!feature.lifecycle) return undefined; |     if (!feature.lifecycle) return undefined; | ||||||
| 
 | 
 | ||||||
|     const getFilteredEnvironments = (condition: (type: string) => boolean) => { |     const getFilteredEnvironments = (condition: (type: string) => boolean) => { | ||||||
|         return feature.environments |         return (feature.environments || []) | ||||||
|             .filter((env) => condition(env.type) && Boolean(env.lastSeenAt)) |             .filter((env) => condition(env.type) && Boolean(env.lastSeenAt)) | ||||||
|             .map((env) => ({ |             .map((env) => ({ | ||||||
|                 name: env.name, |                 name: env.name, | ||||||
|  | |||||||
| @ -13,7 +13,10 @@ import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi | |||||||
| import { MemoizedRowSelectCell } from '../ProjectFeatureToggles/RowSelectCell/RowSelectCell'; | import { MemoizedRowSelectCell } from '../ProjectFeatureToggles/RowSelectCell/RowSelectCell'; | ||||||
| import { BatchSelectionActionsBar } from 'component/common/BatchSelectionActionsBar/BatchSelectionActionsBar'; | import { BatchSelectionActionsBar } from 'component/common/BatchSelectionActionsBar/BatchSelectionActionsBar'; | ||||||
| import { ProjectFeaturesBatchActions } from '../ProjectFeatureToggles/ProjectFeaturesBatchActions/ProjectFeaturesBatchActions'; | 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 { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; | ||||||
| import { useFeatureToggleSwitch } from '../ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch'; | import { useFeatureToggleSwitch } from '../ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch'; | ||||||
| import useLoading from 'hooks/useLoading'; | import useLoading from 'hooks/useLoading'; | ||||||
| @ -48,6 +51,7 @@ import { TableEmptyState } from './TableEmptyState/TableEmptyState'; | |||||||
| import { useRowActions } from './hooks/useRowActions'; | import { useRowActions } from './hooks/useRowActions'; | ||||||
| import { useSelectedData } from './hooks/useSelectedData'; | import { useSelectedData } from './hooks/useSelectedData'; | ||||||
| import { FeatureOverviewCell } from '../../../common/Table/cells/FeatureOverviewCell/FeatureOverviewCell'; | import { FeatureOverviewCell } from '../../../common/Table/cells/FeatureOverviewCell/FeatureOverviewCell'; | ||||||
|  | import { useUiFlag } from 'hooks/useUiFlag'; | ||||||
| 
 | 
 | ||||||
| interface IPaginatedProjectFeatureTogglesProps { | interface IPaginatedProjectFeatureTogglesProps { | ||||||
|     environments: string[]; |     environments: string[]; | ||||||
| @ -126,6 +130,8 @@ export const ProjectFeatureToggles = ({ | |||||||
| 
 | 
 | ||||||
|     const isPlaceholder = Boolean(initialLoad || (loading && total)); |     const isPlaceholder = Boolean(initialLoad || (loading && total)); | ||||||
| 
 | 
 | ||||||
|  |     const featureLifecycleEnabled = useUiFlag('featureLifecycle'); | ||||||
|  | 
 | ||||||
|     const columns = useMemo( |     const columns = useMemo( | ||||||
|         () => [ |         () => [ | ||||||
|             columnHelper.display({ |             columnHelper.display({ | ||||||
| @ -195,9 +201,25 @@ export const ProjectFeatureToggles = ({ | |||||||
|                 id: 'lastSeenAt', |                 id: 'lastSeenAt', | ||||||
|                 header: 'Last seen', |                 header: 'Last seen', | ||||||
|                 cell: ({ row: { original } }) => ( |                 cell: ({ row: { original } }) => ( | ||||||
|                     <MemoizedFeatureEnvironmentSeenCell |                     <ConditionallyRender | ||||||
|                         feature={original} |                         condition={featureLifecycleEnabled} | ||||||
|                         data-loading |                         show={ | ||||||
|  |                             <FeatureLifecycleCell | ||||||
|  |                                 feature={original} | ||||||
|  |                                 onComplete={refetch} | ||||||
|  |                                 onUncomplete={refetch} | ||||||
|  |                                 onArchive={() => | ||||||
|  |                                     setFeatureArchiveState(original.name) | ||||||
|  |                                 } | ||||||
|  |                                 data-loading | ||||||
|  |                             /> | ||||||
|  |                         } | ||||||
|  |                         elseShow={ | ||||||
|  |                             <MemoizedFeatureEnvironmentSeenCell | ||||||
|  |                                 feature={original} | ||||||
|  |                                 data-loading | ||||||
|  |                             /> | ||||||
|  |                         } | ||||||
|                     /> |                     /> | ||||||
|                 ), |                 ), | ||||||
|                 size: 50, |                 size: 50, | ||||||
| @ -308,10 +330,12 @@ export const ProjectFeatureToggles = ({ | |||||||
|                         { |                         { | ||||||
|                             name: 'development', |                             name: 'development', | ||||||
|                             enabled: false, |                             enabled: false, | ||||||
|  |                             type: 'development', | ||||||
|                         }, |                         }, | ||||||
|                         { |                         { | ||||||
|                             name: 'production', |                             name: 'production', | ||||||
|                             enabled: false, |                             enabled: false, | ||||||
|  |                             type: 'production', | ||||||
|                         }, |                         }, | ||||||
|                     ], |                     ], | ||||||
|                 })), |                 })), | ||||||
|  | |||||||
| @ -32,6 +32,11 @@ export type ILastSeenEnvironments = Pick< | |||||||
|     'name' | 'enabled' | 'lastSeenAt' | 'yes' | 'no' |     'name' | 'enabled' | 'lastSeenAt' | 'yes' | 'no' | ||||||
| >; | >; | ||||||
| 
 | 
 | ||||||
|  | export type Lifecycle = { | ||||||
|  |     stage: 'initial' | 'pre-live' | 'live' | 'completed' | 'archived'; | ||||||
|  |     enteredStageAt: string; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| export interface IFeatureToggle { | export interface IFeatureToggle { | ||||||
|     stale: boolean; |     stale: boolean; | ||||||
|     archived: boolean; |     archived: boolean; | ||||||
| @ -49,10 +54,7 @@ export interface IFeatureToggle { | |||||||
|     impressionData: boolean; |     impressionData: boolean; | ||||||
|     strategies?: IFeatureStrategy[]; |     strategies?: IFeatureStrategy[]; | ||||||
|     dependencies: Array<IDependency>; |     dependencies: Array<IDependency>; | ||||||
|     lifecycle?: { |     lifecycle?: Lifecycle; | ||||||
|         stage: 'initial' | 'pre-live' | 'live' | 'completed' | 'archived'; |  | ||||||
|         enteredStageAt: string; |  | ||||||
|     }; |  | ||||||
|     children: Array<string>; |     children: Array<string>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ export interface FeatureSearchEnvironmentSchema { | |||||||
|     /** A list of activation strategies for the feature environment */ |     /** A list of activation strategies for the feature environment */ | ||||||
|     strategies?: FeatureStrategySchema[]; |     strategies?: FeatureStrategySchema[]; | ||||||
|     /** The type of the environment */ |     /** The type of the environment */ | ||||||
|     type?: string; |     type: string; | ||||||
|     /** The number of defined variants */ |     /** The number of defined variants */ | ||||||
|     variantCount?: number; |     variantCount?: number; | ||||||
|     /** A list of variants for the feature environment */ |     /** A list of variants for the feature environment */ | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ | |||||||
|  */ |  */ | ||||||
| import type { FeatureSearchResponseSchemaDependencyType } from './featureSearchResponseSchemaDependencyType'; | import type { FeatureSearchResponseSchemaDependencyType } from './featureSearchResponseSchemaDependencyType'; | ||||||
| import type { FeatureSearchEnvironmentSchema } from './featureSearchEnvironmentSchema'; | import type { FeatureSearchEnvironmentSchema } from './featureSearchEnvironmentSchema'; | ||||||
|  | import type { FeatureSearchResponseSchemaLifecycle } from './featureSearchResponseSchemaLifecycle'; | ||||||
| import type { FeatureSearchResponseSchemaStrategiesItem } from './featureSearchResponseSchemaStrategiesItem'; | import type { FeatureSearchResponseSchemaStrategiesItem } from './featureSearchResponseSchemaStrategiesItem'; | ||||||
| import type { TagSchema } from './tagSchema'; | import type { TagSchema } from './tagSchema'; | ||||||
| import type { VariantSchema } from './variantSchema'; | import type { VariantSchema } from './variantSchema'; | ||||||
| @ -47,6 +48,8 @@ export interface FeatureSearchResponseSchema { | |||||||
|      * @nullable |      * @nullable | ||||||
|      */ |      */ | ||||||
|     lastSeenAt?: string | null; |     lastSeenAt?: string | null; | ||||||
|  |     /** Current lifecycle stage of the feature */ | ||||||
|  |     lifecycle?: FeatureSearchResponseSchemaLifecycle; | ||||||
|     /** Unique feature name */ |     /** Unique feature name */ | ||||||
|     name: string; |     name: string; | ||||||
|     /** Name of the project the feature belongs to */ |     /** 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 './featureSearchEnvironmentSchema'; | ||||||
| export * from './featureSearchResponseSchema'; | export * from './featureSearchResponseSchema'; | ||||||
| export * from './featureSearchResponseSchemaDependencyType'; | export * from './featureSearchResponseSchemaDependencyType'; | ||||||
|  | export * from './featureSearchResponseSchemaLifecycle'; | ||||||
|  | export * from './featureSearchResponseSchemaLifecycleStage'; | ||||||
| export * from './featureSearchResponseSchemaStrategiesItem'; | export * from './featureSearchResponseSchemaStrategiesItem'; | ||||||
| export * from './featureStrategySchema'; | export * from './featureStrategySchema'; | ||||||
| export * from './featureStrategySegmentSchema'; | export * from './featureStrategySegmentSchema'; | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ export const featureSearchEnvironmentSchema = { | |||||||
|     $id: '#/components/schemas/featureSearchEnvironmentSchema', |     $id: '#/components/schemas/featureSearchEnvironmentSchema', | ||||||
|     type: 'object', |     type: 'object', | ||||||
|     additionalProperties: false, |     additionalProperties: false, | ||||||
|     required: ['name', 'enabled'], |     required: ['name', 'enabled', 'type'], | ||||||
|     description: 'A detailed description of the feature environment', |     description: 'A detailed description of the feature environment', | ||||||
|     properties: { |     properties: { | ||||||
|         ...featureEnvironmentSchema.properties, |         ...featureEnvironmentSchema.properties, | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user