From 45505d699641082a7d044c863c79b3ca113a664f Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Wed, 17 May 2023 10:21:08 +0200 Subject: [PATCH] feat: create stubs for bulk toggle (#3792) --- src/lib/openapi/index.ts | 2 + src/lib/openapi/meta-schema-rules.test.ts | 2 +- .../spec/bulk-toggle-features-schema.ts | 24 ++++ src/lib/openapi/spec/index.ts | 1 + .../admin-api/project/project-features.ts | 71 ++++++++++++ .../api/admin/project/features.e2e.test.ts | 51 ++++++++ .../__snapshots__/openapi.e2e.test.ts.snap | 109 ++++++++++++++++++ 7 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 src/lib/openapi/spec/bulk-toggle-features-schema.ts diff --git a/src/lib/openapi/index.ts b/src/lib/openapi/index.ts index dca15410a2..5d5d6b480f 100644 --- a/src/lib/openapi/index.ts +++ b/src/lib/openapi/index.ts @@ -11,6 +11,7 @@ import { applicationSchema, applicationsSchema, batchFeaturesSchema, + bulkToggleFeaturesSchema, changePasswordSchema, clientApplicationSchema, clientFeatureSchema, @@ -194,6 +195,7 @@ export const schemas: UnleashSchemas = { batchStaleSchema, bulkRegistrationSchema, bulkMetricsSchema, + bulkToggleFeaturesSchema, changePasswordSchema, clientApplicationSchema, clientFeatureSchema, diff --git a/src/lib/openapi/meta-schema-rules.test.ts b/src/lib/openapi/meta-schema-rules.test.ts index 9a29e1e6ee..634e801274 100644 --- a/src/lib/openapi/meta-schema-rules.test.ts +++ b/src/lib/openapi/meta-schema-rules.test.ts @@ -289,7 +289,7 @@ const metaRules: Rule[] = [ describe.each(metaRules)('OpenAPI schemas $name', (rule) => { const validateMetaSchema = ajv.compile(rule.metaSchema); - // test all schemas agaisnt the rule + // test all schemas against the rule Object.entries(schemas).forEach(([schemaName, schema]) => { if (!rule.match || rule.match(schemaName, schema)) { it(`${schemaName}`, () => { diff --git a/src/lib/openapi/spec/bulk-toggle-features-schema.ts b/src/lib/openapi/spec/bulk-toggle-features-schema.ts new file mode 100644 index 0000000000..8697b45596 --- /dev/null +++ b/src/lib/openapi/spec/bulk-toggle-features-schema.ts @@ -0,0 +1,24 @@ +import { FromSchema } from 'json-schema-to-ts'; + +export const bulkToggleFeaturesSchema = { + $id: '#/components/schemas/bulkToggleFeaturesSchema', + type: 'object', + required: ['features'], + description: 'The feature list used for bulk toggle operations', + properties: { + features: { + type: 'array', + description: 'The features that we want to bulk toggle', + items: { + type: 'string', + description: 'The feature name we want to toggle', + }, + example: ['feature-a', 'feature-b'], + }, + }, + components: {}, +} as const; + +export type BulkToggleFeaturesSchema = FromSchema< + typeof bulkToggleFeaturesSchema +>; diff --git a/src/lib/openapi/spec/index.ts b/src/lib/openapi/spec/index.ts index 0ae8539aa3..dfa209993e 100644 --- a/src/lib/openapi/spec/index.ts +++ b/src/lib/openapi/spec/index.ts @@ -134,3 +134,4 @@ export * from './tags-bulk-add-schema'; export * from './upsert-segment-schema'; export * from './batch-features-schema'; export * from './token-string-list-schema'; +export * from './bulk-toggle-features-schema'; diff --git a/src/lib/routes/admin-api/project/project-features.ts b/src/lib/routes/admin-api/project/project-features.ts index e6e81ca87a..5bb7b29d42 100644 --- a/src/lib/routes/admin-api/project/project-features.ts +++ b/src/lib/routes/admin-api/project/project-features.ts @@ -20,6 +20,7 @@ import { extractUsername } from '../../../util'; import { IAuthRequest } from '../../unleash-types'; import { AdminFeaturesQuerySchema, + BulkToggleFeaturesSchema, CreateFeatureSchema, CreateFeatureStrategySchema, createRequestSchema, @@ -49,6 +50,11 @@ interface FeatureStrategyParams { sortOrder?: number; } +interface BulkFeaturesStrategyParams { + projectId: string; + environment: string; +} + interface FeatureStrategyQuery { shouldActivateDisabledStrategies: string; } @@ -78,6 +84,7 @@ const PATH_STALE = '/:projectId/stale'; const PATH_FEATURE = `${PATH}/:featureName`; const PATH_FEATURE_CLONE = `${PATH_FEATURE}/clone`; const PATH_ENV = `${PATH_FEATURE}/environments/:environment`; +const BULK_PATH_ENV = `/:projectId/bulk_features/environments/:environment`; const PATH_STRATEGIES = `${PATH_ENV}/strategies`; const PATH_STRATEGY = `${PATH_STRATEGIES}/:strategyId`; @@ -151,6 +158,46 @@ export default class ProjectFeaturesController extends Controller { ], }); + this.route({ + method: 'post', + path: `${BULK_PATH_ENV}/on`, + handler: this.bulkToggleFeaturesEnvironmentOn, + permission: UPDATE_FEATURE_ENVIRONMENT, + middleware: [ + openApiService.validPath({ + tags: ['Unstable'], + description: + 'This endpoint enables multiple feature toggles.', + summary: 'Bulk enable a list of features.', + operationId: 'bulkToggleFeaturesEnvironmentOn', + requestBody: createRequestSchema( + 'bulkToggleFeaturesSchema', + ), + responses: { 405: emptyResponse }, + }), + ], + }); + + this.route({ + method: 'post', + path: `${BULK_PATH_ENV}/off`, + handler: this.bulkToggleFeaturesEnvironmentOff, + permission: UPDATE_FEATURE_ENVIRONMENT, + middleware: [ + openApiService.validPath({ + tags: ['Unstable'], + description: + 'This endpoint disables multiple feature toggles.', + summary: 'Bulk disabled a list of features.', + operationId: 'bulkToggleFeaturesEnvironmentOff', + requestBody: createRequestSchema( + 'bulkToggleFeaturesSchema', + ), + responses: { 405: emptyResponse }, + }), + ], + }); + this.route({ method: 'get', path: PATH_STRATEGIES, @@ -667,6 +714,30 @@ export default class ProjectFeaturesController extends Controller { res.status(200).end(); } + async bulkToggleFeaturesEnvironmentOn( + req: IAuthRequest< + BulkFeaturesStrategyParams, + any, + BulkToggleFeaturesSchema, + FeatureStrategyQuery + >, + res: Response, + ): Promise { + res.status(405).end(); + } + + async bulkToggleFeaturesEnvironmentOff( + req: IAuthRequest< + BulkFeaturesStrategyParams, + any, + BulkToggleFeaturesSchema, + FeatureStrategyQuery + >, + res: Response, + ): Promise { + res.status(405).end(); + } + async toggleFeatureEnvironmentOff( req: IAuthRequest, res: Response, diff --git a/src/test/e2e/api/admin/project/features.e2e.test.ts b/src/test/e2e/api/admin/project/features.e2e.test.ts index f3efc73935..af6fd51e28 100644 --- a/src/test/e2e/api/admin/project/features.e2e.test.ts +++ b/src/test/e2e/api/admin/project/features.e2e.test.ts @@ -372,6 +372,57 @@ test('Can enable/disable environment for feature with strategies', async () => { }); }); +test('Can bulk enable/disable environment for feature with strategies', async () => { + const envName = 'bulk-enable-feature-environment'; + const featureName = 'com.test.bulk.enable.environment'; + const project = 'default'; + // Create environment + await db.stores.environmentStore.create({ + name: envName, + type: 'production', + }); + // Connect environment to project + await app.request + .post(`/api/admin/projects/${project}/environments`) + .send({ + environment: envName, + }) + .expect(200); + + // Create feature + await app.createFeature(featureName).expect((res) => { + expect(res.body.name).toBe(featureName); + expect(res.body.createdAt).toBeTruthy(); + }); + + // Add strategy to it + await app.request + .post( + `/api/admin/projects/${project}/features/${featureName}/environments/${envName}/strategies`, + ) + .send({ + name: 'default', + parameters: { + userId: 'string', + }, + }) + .expect(200); + await app.request + .post( + `/api/admin/projects/${project}/bulk_features/environments/${envName}/on`, + ) + .send({ features: [featureName] }) + .set('Content-Type', 'application/json') + .expect(405); + + await app.request + .post( + `/api/admin/projects/${project}/bulk_features/environments/${envName}/off`, + ) + .send({ features: [featureName] }) + .expect(405); +}); + test("Trying to get a project that doesn't exist yields 404", async () => { await app.request.get('/api/admin/projects/nonexisting').expect(404); }); diff --git a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap index a53b239203..7e4dd6fc3e 100644 --- a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap +++ b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap @@ -926,6 +926,27 @@ The provider you choose for your addon dictates what properties the \`parameters ], "type": "object", }, + "bulkToggleFeaturesSchema": { + "description": "The feature list used for bulk toggle operations", + "properties": { + "features": { + "description": "The features that we want to bulk toggle", + "example": [ + "feature-a", + "feature-b", + ], + "items": { + "description": "The feature name we want to toggle", + "type": "string", + }, + "type": "array", + }, + }, + "required": [ + "features", + ], + "type": "object", + }, "changePasswordSchema": { "additionalProperties": false, "properties": { @@ -9159,6 +9180,94 @@ If the provided project does not exist, the list of events will be empty.", ], }, }, + "/api/admin/projects/{projectId}/bulk_features/environments/{environment}/off": { + "post": { + "description": "This endpoint disables multiple feature toggles.", + "operationId": "bulkToggleFeaturesEnvironmentOff", + "parameters": [ + { + "in": "path", + "name": "projectId", + "required": true, + "schema": { + "type": "string", + }, + }, + { + "in": "path", + "name": "environment", + "required": true, + "schema": { + "type": "string", + }, + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bulkToggleFeaturesSchema", + }, + }, + }, + "description": "bulkToggleFeaturesSchema", + "required": true, + }, + "responses": { + "405": { + "description": "This response has no body.", + }, + }, + "summary": "Bulk disabled a list of features.", + "tags": [ + "Unstable", + ], + }, + }, + "/api/admin/projects/{projectId}/bulk_features/environments/{environment}/on": { + "post": { + "description": "This endpoint enables multiple feature toggles.", + "operationId": "bulkToggleFeaturesEnvironmentOn", + "parameters": [ + { + "in": "path", + "name": "projectId", + "required": true, + "schema": { + "type": "string", + }, + }, + { + "in": "path", + "name": "environment", + "required": true, + "schema": { + "type": "string", + }, + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bulkToggleFeaturesSchema", + }, + }, + }, + "description": "bulkToggleFeaturesSchema", + "required": true, + }, + "responses": { + "405": { + "description": "This response has no body.", + }, + }, + "summary": "Bulk enable a list of features.", + "tags": [ + "Unstable", + ], + }, + }, "/api/admin/projects/{projectId}/delete": { "post": { "description": "This endpoint deletes the specified features, that are in archive.",