mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: Feature lifecycle controller (#6788)
This commit is contained in:
		
							parent
							
								
									efda70ac5d
								
							
						
					
					
						commit
						28a3a064b9
					
				| @ -0,0 +1,86 @@ | ||||
| import type { FeatureLifecycleService } from './feature-lifecycle-service'; | ||||
| import { | ||||
|     type IFlagResolver, | ||||
|     type IUnleashConfig, | ||||
|     type IUnleashServices, | ||||
|     NONE, | ||||
|     serializeDates, | ||||
| } from '../../types'; | ||||
| import type { OpenApiService } from '../../services'; | ||||
| import { | ||||
|     createResponseSchema, | ||||
|     featureLifecycleSchema, | ||||
|     type FeatureLifecycleSchema, | ||||
|     getStandardResponses, | ||||
| } from '../../openapi'; | ||||
| import Controller from '../../routes/controller'; | ||||
| import type { Request, Response } from 'express'; | ||||
| import { NotFoundError } from '../../error'; | ||||
| 
 | ||||
| interface FeatureLifecycleParams { | ||||
|     projectId: string; | ||||
|     featureName: string; | ||||
| } | ||||
| 
 | ||||
| const PATH = '/:projectId/features/:featureName/lifecycle'; | ||||
| 
 | ||||
| export default class FeatureLifecycleController extends Controller { | ||||
|     private featureLifecycleService: FeatureLifecycleService; | ||||
| 
 | ||||
|     private openApiService: OpenApiService; | ||||
| 
 | ||||
|     private flagResolver: IFlagResolver; | ||||
| 
 | ||||
|     constructor( | ||||
|         config: IUnleashConfig, | ||||
|         { | ||||
|             featureLifecycleService, | ||||
|             openApiService, | ||||
|         }: Pick<IUnleashServices, 'openApiService' | 'featureLifecycleService'>, | ||||
|     ) { | ||||
|         super(config); | ||||
|         this.featureLifecycleService = featureLifecycleService; | ||||
|         this.openApiService = openApiService; | ||||
|         this.flagResolver = config.flagResolver; | ||||
| 
 | ||||
|         this.route({ | ||||
|             method: 'get', | ||||
|             path: PATH, | ||||
|             handler: this.getFeatureLifecycle, | ||||
|             permission: NONE, | ||||
|             middleware: [ | ||||
|                 openApiService.validPath({ | ||||
|                     tags: ['Unstable'], | ||||
|                     summary: 'Get feature lifecycle', | ||||
|                     description: | ||||
|                         'Information about the lifecycle stages of the feature.', | ||||
|                     operationId: 'getFeatureLifecycle', | ||||
|                     responses: { | ||||
|                         200: createResponseSchema('featureLifecycleSchema'), | ||||
|                         ...getStandardResponses(401, 403, 404), | ||||
|                     }, | ||||
|                 }), | ||||
|             ], | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     async getFeatureLifecycle( | ||||
|         req: Request<FeatureLifecycleParams, any, any, any>, | ||||
|         res: Response<FeatureLifecycleSchema>, | ||||
|     ): Promise<void> { | ||||
|         if (!this.flagResolver.isEnabled('featureLifecycle')) { | ||||
|             throw new NotFoundError('Feature lifecycle is disabled.'); | ||||
|         } | ||||
|         const { featureName } = req.params; | ||||
| 
 | ||||
|         const result = | ||||
|             await this.featureLifecycleService.getFeatureLifecycle(featureName); | ||||
| 
 | ||||
|         this.openApiService.respondWithValidation( | ||||
|             200, | ||||
|             res, | ||||
|             featureLifecycleSchema.$id, | ||||
|             serializeDates(result), | ||||
|         ); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init'; | ||||
| import { | ||||
|     type IUnleashTest, | ||||
|     setupAppWithAuth, | ||||
| } from '../../../test/e2e/helpers/test-helper'; | ||||
| import getLogger from '../../../test/fixtures/no-logger'; | ||||
| 
 | ||||
| let app: IUnleashTest; | ||||
| let db: ITestDb; | ||||
| 
 | ||||
| beforeAll(async () => { | ||||
|     db = await dbInit('feature_lifecycle', getLogger); | ||||
|     app = await setupAppWithAuth( | ||||
|         db.stores, | ||||
|         { | ||||
|             experimental: { | ||||
|                 flags: { | ||||
|                     featureLifecycle: true, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|         db.rawDatabase, | ||||
|     ); | ||||
| 
 | ||||
|     await app.request | ||||
|         .post(`/auth/demo/login`) | ||||
|         .send({ | ||||
|             email: 'user@getunleash.io', | ||||
|         }) | ||||
|         .expect(200); | ||||
| }); | ||||
| 
 | ||||
| afterAll(async () => { | ||||
|     await app.destroy(); | ||||
|     await db.destroy(); | ||||
| }); | ||||
| 
 | ||||
| beforeEach(async () => {}); | ||||
| 
 | ||||
| const getFeatureLifecycle = async (featureName: string, expectedCode = 200) => { | ||||
|     return app.request | ||||
|         .get(`/api/admin/projects/default/features/${featureName}/lifecycle`) | ||||
|         .expect(expectedCode); | ||||
| }; | ||||
| 
 | ||||
| test('should return lifecycle stages', async () => { | ||||
|     await app.createFeature('my_feature_a'); | ||||
| 
 | ||||
|     const { body } = await getFeatureLifecycle('my_feature_a'); | ||||
| 
 | ||||
|     expect(body).toEqual([]); | ||||
| }); | ||||
| @ -40,6 +40,7 @@ import { | ||||
| import { projectApplicationsQueryParameters } from '../../openapi/spec/project-applications-query-parameters'; | ||||
| import { normalizeQueryParams } from '../feature-search/search-utils'; | ||||
| import ProjectInsightsController from '../project-insights/project-insights-controller'; | ||||
| import FeatureLifecycleController from '../feature-lifecycle/feature-lifecycle-controller'; | ||||
| 
 | ||||
| export default class ProjectController extends Controller { | ||||
|     private projectService: ProjectService; | ||||
| @ -181,6 +182,7 @@ export default class ProjectController extends Controller { | ||||
|             ).router, | ||||
|         ); | ||||
|         this.use('/', new ProjectInsightsController(config, services).router); | ||||
|         this.use('/', new FeatureLifecycleController(config, services).router); | ||||
|     } | ||||
| 
 | ||||
|     async getProjects( | ||||
|  | ||||
							
								
								
									
										33
									
								
								src/lib/openapi/spec/feature-lifecycle-schema.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/lib/openapi/spec/feature-lifecycle-schema.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| import type { FromSchema } from 'json-schema-to-ts'; | ||||
| 
 | ||||
| export const featureLifecycleSchema = { | ||||
|     $id: '#/components/schemas/featureLifecycleSchema', | ||||
|     type: 'array', | ||||
|     description: 'A list of lifecycle stages for a given feature', | ||||
|     items: { | ||||
|         additionalProperties: false, | ||||
|         type: 'object', | ||||
|         required: ['stage', 'enteredStageAt'], | ||||
|         properties: { | ||||
|             stage: { | ||||
|                 type: 'string', | ||||
|                 enum: ['initial', 'pre-live', 'live', 'completed', 'archived'], | ||||
|                 example: 'initial', | ||||
|                 description: | ||||
|                     'The name of the lifecycle stage that got recorded for a given feature', | ||||
|             }, | ||||
|             enteredStageAt: { | ||||
|                 type: 'string', | ||||
|                 format: 'date-time', | ||||
|                 example: '2023-01-28T16:21:39.975Z', | ||||
|                 description: 'The date when the feature entered a given stage', | ||||
|             }, | ||||
|         }, | ||||
|         description: 'The lifecycle stage of the feature', | ||||
|     }, | ||||
|     components: { | ||||
|         schemas: {}, | ||||
|     }, | ||||
| } as const; | ||||
| 
 | ||||
| export type FeatureLifecycleSchema = FromSchema<typeof featureLifecycleSchema>; | ||||
| @ -75,6 +75,7 @@ export * from './feature-dependencies-schema'; | ||||
| export * from './feature-environment-metrics-schema'; | ||||
| export * from './feature-environment-schema'; | ||||
| export * from './feature-events-schema'; | ||||
| export * from './feature-lifecycle-schema'; | ||||
| export * from './feature-metrics-schema'; | ||||
| export * from './feature-schema'; | ||||
| export * from './feature-search-environment-schema'; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user