diff --git a/frontend/src/hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi.ts b/frontend/src/hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi.ts index 76d590ef7b..09551ec0fc 100644 --- a/frontend/src/hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi.ts +++ b/frontend/src/hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi.ts @@ -10,6 +10,7 @@ const useFeatureLifecycleApi = () => { const path = `api/admin/projects/${project}/features/${name}/lifecycle/complete`; const req = createRequest(path, { method: 'POST', + body: JSON.stringify({ status: 'kept' }), }); return makeRequest(req.caller, req.id); diff --git a/src/lib/features/feature-lifecycle/feature-lifecycle-controller.ts b/src/lib/features/feature-lifecycle/feature-lifecycle-controller.ts index d91b8b74cf..be415264a2 100644 --- a/src/lib/features/feature-lifecycle/feature-lifecycle-controller.ts +++ b/src/lib/features/feature-lifecycle/feature-lifecycle-controller.ts @@ -9,6 +9,7 @@ import { } from '../../types'; import type { OpenApiService } from '../../services'; import { + createRequestSchema, createResponseSchema, emptyResponse, featureLifecycleSchema, @@ -78,6 +79,9 @@ export default class FeatureLifecycleController extends Controller { summary: 'Set feature completed', description: 'This will set the feature as completed.', operationId: 'complete', + requestBody: createRequestSchema( + 'featureLifecycleCompletedSchema', + ), responses: { 200: emptyResponse, ...getStandardResponses(401, 403, 404), diff --git a/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts b/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts index 8902f9ba44..ec05cbb0ed 100644 --- a/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts +++ b/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts @@ -17,6 +17,7 @@ import { type FeatureLifecycleService, STAGE_ENTERED, } from './feature-lifecycle-service'; +import type { FeatureLifecycleCompletedSchema } from '../../openapi'; let app: IUnleashTest; let db: ITestDb; @@ -61,11 +62,16 @@ const getFeatureLifecycle = async (featureName: string, expectedCode = 200) => { .get(`/api/admin/projects/default/features/${featureName}/lifecycle`) .expect(expectedCode); }; -const completeFeature = async (featureName: string, expectedCode = 200) => { +const completeFeature = async ( + featureName: string, + status: FeatureLifecycleCompletedSchema, + expectedCode = 200, +) => { return app.request .post( `/api/admin/projects/default/features/${featureName}/lifecycle/complete`, ) + .send(status) .expect(expectedCode); }; @@ -156,7 +162,10 @@ test('should return lifecycle stages', async () => { test('should be able to toggle between completed/uncompleted', async () => { await app.createFeature('my_feature_b'); - await completeFeature('my_feature_b'); + await completeFeature('my_feature_b', { + status: 'kept', + statusValue: 'variant1', + }); await expectFeatureStage('my_feature_b', 'completed'); diff --git a/src/lib/openapi/spec/feature-lifecycle-completed-schema.test.ts b/src/lib/openapi/spec/feature-lifecycle-completed-schema.test.ts new file mode 100644 index 0000000000..812f7e264d --- /dev/null +++ b/src/lib/openapi/spec/feature-lifecycle-completed-schema.test.ts @@ -0,0 +1,29 @@ +import { validateSchema } from '../validate'; +import type { FeatureLifecycleCompletedSchema } from './feature-lifecycle-completed-schema'; + +test('featureLifecycleCompletedSchema', () => { + const data: FeatureLifecycleCompletedSchema = { + status: 'kept', + statusValue: 'variant1', + }; + + expect( + validateSchema( + '#/components/schemas/featureLifecycleCompletedSchema', + data, + ), + ).toBeUndefined(); +}); + +test('featureLifecycleCompletedSchema without status', () => { + const data: FeatureLifecycleCompletedSchema = { + status: 'kept', + }; + + expect( + validateSchema( + '#/components/schemas/featureLifecycleCompletedSchema', + data, + ), + ).toBeUndefined(); +}); diff --git a/src/lib/openapi/spec/feature-lifecycle-completed-schema.ts b/src/lib/openapi/spec/feature-lifecycle-completed-schema.ts new file mode 100644 index 0000000000..e08a817dfe --- /dev/null +++ b/src/lib/openapi/spec/feature-lifecycle-completed-schema.ts @@ -0,0 +1,30 @@ +import type { FromSchema } from 'json-schema-to-ts'; + +export const featureLifecycleCompletedSchema = { + $id: '#/components/schemas/featureLifecycleCompletedSchema', + description: 'A feature that has been marked as completed', + additionalProperties: false, + type: 'object', + required: ['status'], + properties: { + status: { + type: 'string', + enum: ['kept', 'discarded'], + example: 'kept', + description: + 'The status of the feature after it has been marked as completed', + }, + statusValue: { + type: 'string', + example: 'variant1', + description: 'The metadata value passed in together with status', + }, + }, + components: { + schemas: {}, + }, +} as const; + +export type FeatureLifecycleCompletedSchema = FromSchema< + typeof featureLifecycleCompletedSchema +>; diff --git a/src/lib/openapi/spec/index.ts b/src/lib/openapi/spec/index.ts index b4dc8ae6a7..5e24718c14 100644 --- a/src/lib/openapi/spec/index.ts +++ b/src/lib/openapi/spec/index.ts @@ -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-completed-schema'; export * from './feature-lifecycle-schema'; export * from './feature-metrics-schema'; export * from './feature-schema';