Skip to content

Commit

Permalink
feat: Feature lifecycle controller (#6788)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew committed Apr 5, 2024
1 parent efda70a commit 28a3a06
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 0 deletions.
86 changes: 86 additions & 0 deletions src/lib/features/feature-lifecycle/feature-lifecycle-controller.ts
Original file line number Diff line number Diff line change
@@ -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),
);
}
}
52 changes: 52 additions & 0 deletions src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -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([]);
});
2 changes: 2 additions & 0 deletions src/lib/features/project/project-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
33 changes: 33 additions & 0 deletions src/lib/openapi/spec/feature-lifecycle-schema.ts
Original file line number Diff line number Diff line change
@@ -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>;
1 change: 1 addition & 0 deletions src/lib/openapi/spec/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down

0 comments on commit 28a3a06

Please sign in to comment.