From b927615ebeddc747e07331cb6e1be8e02f599419 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 11 Jul 2023 14:17:10 +0200 Subject: [PATCH] openapi: update misc 'features'-tagged endpoints (#4192) This PR updates a slew of miscellaneous endpoints tagged `Features` --- src/lib/openapi/index.ts | 2 + src/lib/openapi/meta-schema-rules.test.ts | 6 --- src/lib/openapi/spec/batch-features-schema.ts | 1 + src/lib/openapi/spec/batch-stale-schema.ts | 9 ++++ src/lib/openapi/spec/feature-type-schema.ts | 16 ++++++- src/lib/openapi/spec/feature-types-schema.ts | 44 +++++++++++++++++++ src/lib/openapi/spec/index.ts | 1 + .../openapi/spec/validate-feature-schema.ts | 18 ++++++++ src/lib/routes/admin-api/constraints.ts | 11 +++-- src/lib/routes/admin-api/feature-type.ts | 5 +++ src/lib/routes/admin-api/feature.ts | 20 +++++++-- .../admin-api/project/project-archive.ts | 4 +- .../admin-api/project/project-features.ts | 9 ++-- 13 files changed, 127 insertions(+), 19 deletions(-) create mode 100644 src/lib/openapi/spec/validate-feature-schema.ts diff --git a/src/lib/openapi/index.ts b/src/lib/openapi/index.ts index a3e526bccc..e2c86ecff7 100644 --- a/src/lib/openapi/index.ts +++ b/src/lib/openapi/index.ts @@ -136,6 +136,7 @@ import { createUserResponseSchema, usersSearchSchema, validatedEdgeTokensSchema, + validateFeatureSchema, validatePasswordSchema, validateTagTypeSchema, variantSchema, @@ -333,6 +334,7 @@ export const schemas: UnleashSchemas = { usersSchema, usersSearchSchema, validatedEdgeTokensSchema, + validateFeatureSchema, validatePasswordSchema, validateTagTypeSchema, variantSchema, diff --git a/src/lib/openapi/meta-schema-rules.test.ts b/src/lib/openapi/meta-schema-rules.test.ts index 175e3ddbd4..6300f318aa 100644 --- a/src/lib/openapi/meta-schema-rules.test.ts +++ b/src/lib/openapi/meta-schema-rules.test.ts @@ -90,7 +90,6 @@ const metaRules: Rule[] = [ }, }, knownExceptions: [ - 'batchStaleSchema', 'cloneFeatureSchema', 'createFeatureSchema', 'createInvitedUserSchema', @@ -99,8 +98,6 @@ const metaRules: Rule[] = [ 'featureEnvironmentSchema', 'featuresSchema', 'featureStrategySegmentSchema', - 'featureTypeSchema', - 'featureTypesSchema', 'featureVariantsSchema', 'groupSchema', 'groupsSchema', @@ -141,7 +138,6 @@ const metaRules: Rule[] = [ 'adminFeaturesQuerySchema', 'applicationSchema', 'applicationsSchema', - 'batchStaleSchema', 'cloneFeatureSchema', 'createFeatureSchema', 'createInvitedUserSchema', @@ -149,8 +145,6 @@ const metaRules: Rule[] = [ 'environmentsSchema', 'featuresSchema', 'featureStrategySegmentSchema', - 'featureTypeSchema', - 'featureTypesSchema', 'featureVariantsSchema', 'groupSchema', 'groupsSchema', diff --git a/src/lib/openapi/spec/batch-features-schema.ts b/src/lib/openapi/spec/batch-features-schema.ts index ee0c21cb08..c2e378bdbf 100644 --- a/src/lib/openapi/spec/batch-features-schema.ts +++ b/src/lib/openapi/spec/batch-features-schema.ts @@ -12,6 +12,7 @@ export const batchFeaturesSchema = { type: 'string', }, description: 'List of feature toggle names', + example: ['my-feature-4', 'my-feature-5', 'my-feature-6'], }, }, components: { diff --git a/src/lib/openapi/spec/batch-stale-schema.ts b/src/lib/openapi/spec/batch-stale-schema.ts index cc1dc38ec6..dc80f62fdb 100644 --- a/src/lib/openapi/spec/batch-stale-schema.ts +++ b/src/lib/openapi/spec/batch-stale-schema.ts @@ -3,16 +3,25 @@ import { FromSchema } from 'json-schema-to-ts'; export const batchStaleSchema = { $id: '#/components/schemas/batchStaleSchema', type: 'object', + description: + 'A list of features to operate on and whether they should be marked as stale or as not stale.', required: ['features', 'stale'], properties: { features: { type: 'array', + description: 'A list of features to mark as (not) stale', + example: ['my-feature-1', 'my-feature-2', 'my-feature-3'], items: { type: 'string', + description: 'A feature name', + example: 'my-feature-5', }, }, stale: { type: 'boolean', + example: true, + description: + 'Whether the list of features should be marked as stale or not stale. If `true`, the features will be marked as stale. If `false`, the features will be marked as not stale.', }, }, components: { diff --git a/src/lib/openapi/spec/feature-type-schema.ts b/src/lib/openapi/spec/feature-type-schema.ts index c7a8fed031..689980bb53 100644 --- a/src/lib/openapi/spec/feature-type-schema.ts +++ b/src/lib/openapi/spec/feature-type-schema.ts @@ -3,20 +3,34 @@ import { FromSchema } from 'json-schema-to-ts'; export const featureTypeSchema = { $id: '#/components/schemas/featureTypeSchema', type: 'object', + description: + 'A [feature toggle type](https://docs.getunleash.io/reference/feature-toggle-types).', additionalProperties: false, required: ['id', 'name', 'description', 'lifetimeDays'], properties: { id: { type: 'string', + description: 'The identifier of this feature toggle type.', + example: 'kill-switch', }, name: { type: 'string', + description: 'The display name of this feature toggle type.', + example: 'Kill switch', }, description: { type: 'string', + description: + 'A description of what this feature toggle type is intended to be used for.', + example: + 'Kill switch feature toggles are used to quickly turn on or off critical functionality in your system.', }, lifetimeDays: { - type: 'number', + type: 'integer', + minimum: 0, + description: + 'How many days it takes before a feature toggle of this typed is flagged as [potentially stale](https://docs.getunleash.io/reference/technical-debt#stale-and-potentially-stale-toggles) by Unleash. If this value is `null`, Unleash will never mark it as potentially stale.', + example: 40, nullable: true, }, }, diff --git a/src/lib/openapi/spec/feature-types-schema.ts b/src/lib/openapi/spec/feature-types-schema.ts index 33ce80980a..fe5380f5ef 100644 --- a/src/lib/openapi/spec/feature-types-schema.ts +++ b/src/lib/openapi/spec/feature-types-schema.ts @@ -5,16 +5,60 @@ export const featureTypesSchema = { $id: '#/components/schemas/featureTypesSchema', type: 'object', additionalProperties: false, + description: + 'A list of [feature toggle types](https://docs.getunleash.io/reference/feature-toggle-types) and the schema version used to represent those feature types.', required: ['version', 'types'], properties: { version: { type: 'integer', + enum: [1], + example: 1, + description: + 'The schema version used to describe the feature toggle types listed in the `types` property.', }, types: { type: 'array', + description: 'The list of feature toggle types.', items: { $ref: '#/components/schemas/featureTypeSchema', }, + example: [ + { + id: 'release', + name: 'Release', + description: + 'Release feature toggles are used to release new features.', + lifetimeDays: 40, + }, + { + id: 'experiment', + name: 'Experiment', + description: + 'Experiment feature toggles are used to test and verify multiple different versions of a feature.', + lifetimeDays: 40, + }, + { + id: 'operational', + name: 'Operational', + description: + 'Operational feature toggles are used to control aspects of a rollout.', + lifetimeDays: 7, + }, + { + id: 'kill-switch', + name: 'Kill switch', + description: + 'Kill switch feature toggles are used to quickly turn on or off critical functionality in your system.', + lifetimeDays: null, + }, + { + id: 'permission', + name: 'Permission', + description: + 'Permission feature toggles are used to control permissions in your system.', + lifetimeDays: null, + }, + ], }, }, components: { diff --git a/src/lib/openapi/spec/index.ts b/src/lib/openapi/spec/index.ts index 66d1aace0a..3d8bd261e6 100644 --- a/src/lib/openapi/spec/index.ts +++ b/src/lib/openapi/spec/index.ts @@ -97,6 +97,7 @@ export * from './update-api-token-schema'; export * from './users-groups-base-schema'; export * from './validate-password-schema'; export * from './validate-tag-type-schema'; +export * from './validate-feature-schema'; export * from './client-application-schema'; export * from './playground-feature-schema'; export * from './playground-request-schema'; diff --git a/src/lib/openapi/spec/validate-feature-schema.ts b/src/lib/openapi/spec/validate-feature-schema.ts new file mode 100644 index 0000000000..c5cf33ad6c --- /dev/null +++ b/src/lib/openapi/spec/validate-feature-schema.ts @@ -0,0 +1,18 @@ +import { FromSchema } from 'json-schema-to-ts'; + +export const validateFeatureSchema = { + $id: '#/components/schemas/validateFeatureSchema', + type: 'object', + required: ['name'], + description: "Data used to validate a feature toggle's name.", + properties: { + name: { + description: 'The feature name to validate.', + type: 'string', + example: 'my-feature-3', + }, + }, + components: {}, +} as const; + +export type ValidateFeatureSchema = FromSchema; diff --git a/src/lib/routes/admin-api/constraints.ts b/src/lib/routes/admin-api/constraints.ts index b09ebaaeee..53c03e6d71 100644 --- a/src/lib/routes/admin-api/constraints.ts +++ b/src/lib/routes/admin-api/constraints.ts @@ -2,12 +2,12 @@ import { Request, Response } from 'express'; import FeatureToggleService from '../../services/feature-toggle-service'; import { IUnleashConfig } from '../../types/option'; import { IUnleashServices } from '../../types'; -import { IConstraint } from '../../types/model'; import { NONE } from '../../types/permissions'; import Controller from '../controller'; import { Logger } from '../../logger'; import { OpenApiService } from '../../services/openapi-service'; import { createRequestSchema } from '../../openapi/util/create-request-schema'; +import { ConstraintSchema, getStandardResponses } from '../../openapi'; export default class ConstraintController extends Controller { private featureService: FeatureToggleService; @@ -38,9 +38,12 @@ export default class ConstraintController extends Controller { tags: ['Features'], operationId: 'validateConstraint', requestBody: createRequestSchema('constraintSchema'), + summary: 'Validate constraint', + description: + 'Validates a constraint definition. Checks whether the context field exists and whether the applied configuration is valid. Additional properties are not allowed on data objects that you send to this endpoint.', responses: { - 204: { description: 'validConstraint' }, - 400: { description: 'invalidConstraint' }, + 204: { description: 'The constraint is valid' }, + ...getStandardResponses(400, 401, 403, 415), }, }), ], @@ -48,7 +51,7 @@ export default class ConstraintController extends Controller { } async validateConstraint( - req: Request, + req: Request, res: Response, ): Promise { await this.featureService.validateConstraint(req.body); diff --git a/src/lib/routes/admin-api/feature-type.ts b/src/lib/routes/admin-api/feature-type.ts index adea97521c..97e518d32b 100644 --- a/src/lib/routes/admin-api/feature-type.ts +++ b/src/lib/routes/admin-api/feature-type.ts @@ -8,6 +8,7 @@ import { NONE } from '../../types/permissions'; import { FeatureTypesSchema } from '../../openapi/spec/feature-types-schema'; import { createResponseSchema } from '../../openapi/util/create-response-schema'; import Controller from '../controller'; +import { getStandardResponses } from '../../openapi'; const version = 1; @@ -39,8 +40,12 @@ export class FeatureTypeController extends Controller { openApiService.validPath({ tags: ['Features'], operationId: 'getAllFeatureTypes', + summary: 'Get all feature types', + description: + 'Retrieves all feature types that exist in this Unleash instance, along with their descriptions and lifetimes.', responses: { 200: createResponseSchema('featureTypesSchema'), + ...getStandardResponses(401), }, }), ], diff --git a/src/lib/routes/admin-api/feature.ts b/src/lib/routes/admin-api/feature.ts index f3c8a7cba6..eb2d3f2b35 100644 --- a/src/lib/routes/admin-api/feature.ts +++ b/src/lib/routes/admin-api/feature.ts @@ -29,6 +29,7 @@ import { getStandardResponses, } from '../../openapi/util/standard-responses'; import { UpdateTagsSchema } from '../../openapi/spec/update-tags-schema'; +import { ValidateFeatureSchema } from '../../openapi/spec/validate-feature-schema'; const version = 1; @@ -64,7 +65,13 @@ class FeatureController extends Controller { openApiService.validPath({ tags: ['Features'], operationId: 'getAllToggles', - responses: { 200: createResponseSchema('featuresSchema') }, + responses: { + 200: createResponseSchema('featuresSchema'), + ...getStandardResponses(401, 403), + }, + summary: 'Get all features (deprecated)', + description: + 'Gets all feature toggles with their full configuration. This endpoint is **deprecated**. You should use the project-based endpoint instead (`/api/admin/projects//features`).', deprecated: true, }), ], @@ -79,7 +86,14 @@ class FeatureController extends Controller { openApiService.validPath({ tags: ['Features'], operationId: 'validateFeature', - responses: { 200: emptyResponse }, + summary: 'Validate feature name', + requestBody: createRequestSchema('validateFeatureSchema'), + description: + 'Validates a feature toggle name: checks whether the name is URL-friendly and whether a feature with the given name already exists. Returns 200 if the feature name is compliant and unused.', + responses: { + 200: emptyResponse, + ...getStandardResponses(400, 401, 409, 415), + }, }), ], }); @@ -290,7 +304,7 @@ class FeatureController extends Controller { } async validate( - req: Request, + req: Request, res: Response, ): Promise { const { name } = req.body; diff --git a/src/lib/routes/admin-api/project/project-archive.ts b/src/lib/routes/admin-api/project/project-archive.ts index 419bd54bac..d9809d06b5 100644 --- a/src/lib/routes/admin-api/project/project-archive.ts +++ b/src/lib/routes/admin-api/project/project-archive.ts @@ -100,12 +100,12 @@ export default class ProjectArchiveController extends Controller { tags: ['Features'], operationId: 'archiveFeatures', description: - 'This endpoint archives the specified features.', + "This endpoint archives the specified features. Any features that are already archived or that don't exist are ignored. All existing features (whether already archived or not) that are provided must belong to the specified project.", summary: 'Archives a list of features', requestBody: createRequestSchema('batchFeaturesSchema'), responses: { 202: emptyResponse, - ...getStandardResponses(400, 401, 403), + ...getStandardResponses(400, 401, 403, 415), }, }), ], diff --git a/src/lib/routes/admin-api/project/project-features.ts b/src/lib/routes/admin-api/project/project-features.ts index 7099e8381d..f342330225 100644 --- a/src/lib/routes/admin-api/project/project-features.ts +++ b/src/lib/routes/admin-api/project/project-features.ts @@ -532,10 +532,13 @@ export default class ProjectFeaturesController extends Controller { openApiService.validPath({ tags: ['Features'], operationId: 'staleFeatures', - description: 'This endpoint stales the specified features.', - summary: 'Stales a list of features', + summary: 'Mark features as stale / not stale', + description: `This endpoint marks the provided list of features as either [stale](https://docs.getunleash.io/reference/technical-debt#stale-and-potentially-stale-toggles) or not stale depending on the request body you send. Any provided features that don't exist are ignored.`, requestBody: createRequestSchema('batchStaleSchema'), - responses: { 202: emptyResponse }, + responses: { + 202: emptyResponse, + ...getStandardResponses(401, 403, 415), + }, }), ], });