From 485dab87d45e37cf84353e9e8292f69d32555110 Mon Sep 17 00:00:00 2001 From: David Leek Date: Fri, 19 May 2023 09:07:23 +0200 Subject: [PATCH] docs: openapi schema specifications for Projects tag (#3571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## About the changes Improves the openapi schema specifications for the schemas belonging to the "Projects" tag. Expected error codes/http statues, descriptions, and example data --------- Co-authored-by: Christopher Kolstad Co-authored-by: Thomas Heartman --- src/lib/openapi/meta-schema-rules.test.ts | 10 - .../api-token-schema.test.ts.snap | 42 +- src/lib/openapi/spec/api-token-schema.test.ts | 1 + src/lib/openapi/spec/api-token-schema.ts | 52 +- src/lib/openapi/spec/api-tokens-schema.ts | 2 + .../openapi/spec/health-overview-schema.ts | 39 +- src/lib/openapi/spec/health-report-schema.ts | 8 + .../spec/project-environment-schema.ts | 9 + src/lib/routes/admin-api/project/api-token.ts | 13 +- .../routes/admin-api/project/environments.ts | 16 +- .../routes/admin-api/project/health-report.ts | 5 + src/lib/routes/admin-api/project/index.ts | 9 + src/lib/types/models/api-token.ts | 4 +- .../project/project.api.tokens.e2e.test.ts | 115 +++ .../__snapshots__/openapi.e2e.test.ts.snap | 734 +++++++++++++++++- 15 files changed, 979 insertions(+), 80 deletions(-) create mode 100644 src/test/e2e/api/admin/project/project.api.tokens.e2e.test.ts diff --git a/src/lib/openapi/meta-schema-rules.test.ts b/src/lib/openapi/meta-schema-rules.test.ts index ef807b3e62..314861edfe 100644 --- a/src/lib/openapi/meta-schema-rules.test.ts +++ b/src/lib/openapi/meta-schema-rules.test.ts @@ -79,8 +79,6 @@ const metaRules: Rule[] = [ }, }, knownExceptions: [ - 'apiTokenSchema', - 'apiTokensSchema', 'batchFeaturesSchema', 'batchStaleSchema', 'changePasswordSchema', @@ -115,8 +113,6 @@ const metaRules: Rule[] = [ 'groupsSchema', 'groupUserModelSchema', 'healthCheckSchema', - 'healthOverviewSchema', - 'healthReportSchema', 'idSchema', 'instanceAdminStatsSchema', 'legalValueSchema', @@ -132,7 +128,6 @@ const metaRules: Rule[] = [ 'playgroundFeatureSchema', 'playgroundRequestSchema', 'profileSchema', - 'projectEnvironmentSchema', 'projectSchema', 'projectsSchema', 'proxyClientSchema', @@ -187,8 +182,6 @@ const metaRules: Rule[] = [ }, knownExceptions: [ 'adminFeaturesQuerySchema', - 'apiTokenSchema', - 'apiTokensSchema', 'applicationSchema', 'applicationsSchema', 'batchFeaturesSchema', @@ -225,8 +218,6 @@ const metaRules: Rule[] = [ 'groupsSchema', 'groupUserModelSchema', 'healthCheckSchema', - 'healthOverviewSchema', - 'healthReportSchema', 'idSchema', 'instanceAdminStatsSchema', 'legalValueSchema', @@ -244,7 +235,6 @@ const metaRules: Rule[] = [ 'playgroundSegmentSchema', 'playgroundStrategySchema', 'profileSchema', - 'projectEnvironmentSchema', 'proxyClientSchema', 'proxyFeatureSchema', 'proxyFeaturesSchema', diff --git a/src/lib/openapi/spec/__snapshots__/api-token-schema.test.ts.snap b/src/lib/openapi/spec/__snapshots__/api-token-schema.test.ts.snap index 485792fc4d..d1a3a22ec0 100644 --- a/src/lib/openapi/spec/__snapshots__/api-token-schema.test.ts.snap +++ b/src/lib/openapi/spec/__snapshots__/api-token-schema.test.ts.snap @@ -6,11 +6,11 @@ exports[`apiTokenSchema empty 1`] = ` { "instancePath": "", "keyword": "required", - "message": "must have required property 'username'", + "message": "must have required property 'secret'", "params": { - "missingProperty": "username", + "missingProperty": "secret", }, - "schemaPath": "#/anyOf/0/required", + "schemaPath": "#/required", }, { "instancePath": "", @@ -19,14 +19,7 @@ exports[`apiTokenSchema empty 1`] = ` "params": { "missingProperty": "tokenName", }, - "schemaPath": "#/anyOf/1/required", - }, - { - "instancePath": "", - "keyword": "anyOf", - "message": "must match a schema in anyOf", - "params": {}, - "schemaPath": "#/anyOf", + "schemaPath": "#/required", }, { "instancePath": "", @@ -37,6 +30,33 @@ exports[`apiTokenSchema empty 1`] = ` }, "schemaPath": "#/required", }, + { + "instancePath": "", + "keyword": "required", + "message": "must have required property 'project'", + "params": { + "missingProperty": "project", + }, + "schemaPath": "#/required", + }, + { + "instancePath": "", + "keyword": "required", + "message": "must have required property 'projects'", + "params": { + "missingProperty": "projects", + }, + "schemaPath": "#/required", + }, + { + "instancePath": "", + "keyword": "required", + "message": "must have required property 'createdAt'", + "params": { + "missingProperty": "createdAt", + }, + "schemaPath": "#/required", + }, ], "schema": "#/components/schemas/apiTokenSchema", } diff --git a/src/lib/openapi/spec/api-token-schema.test.ts b/src/lib/openapi/spec/api-token-schema.test.ts index 610b29ca86..10c111e79a 100644 --- a/src/lib/openapi/spec/api-token-schema.test.ts +++ b/src/lib/openapi/spec/api-token-schema.test.ts @@ -5,6 +5,7 @@ import { ApiTokenSchema } from './api-token-schema'; const defaultData: ApiTokenSchema = { secret: '', username: '', + tokenName: '', type: ApiTokenType.CLIENT, environment: '', projects: [], diff --git a/src/lib/openapi/spec/api-token-schema.ts b/src/lib/openapi/spec/api-token-schema.ts index 6cf7d9374b..162d2cd424 100644 --- a/src/lib/openapi/spec/api-token-schema.ts +++ b/src/lib/openapi/spec/api-token-schema.ts @@ -5,16 +5,28 @@ export const apiTokenSchema = { $id: '#/components/schemas/apiTokenSchema', type: 'object', additionalProperties: false, - required: ['type'], + required: [ + 'secret', + 'tokenName', + 'type', + 'project', + 'projects', + 'createdAt', + ], + description: + 'An overview of an [Unleash API token](https://docs.getunleash.io/reference/api-tokens-and-client-keys).', properties: { secret: { type: 'string', + description: 'The token used for authentication.', + example: 'project:environment.xyzrandomstring', }, username: { type: 'string', deprecated: true, description: 'This property was deprecated in Unleash v5. Prefer the `tokenName` property instead.', + example: 'a-name', }, tokenName: { type: 'string', @@ -24,57 +36,57 @@ export const apiTokenSchema = { type: { type: 'string', enum: Object.values(ApiTokenType), + description: 'The type of API token', + example: 'client', }, environment: { type: 'string', + description: + 'The environment the token has access to. `*` if it has access to all environments.', + example: 'development', }, project: { type: 'string', + description: 'The project this token belongs to.', + example: 'developerexperience', }, projects: { type: 'array', + description: + 'The list of projects this token has access to. If the token has access to specific projects they will be listed here. If the token has access to all projects it will be represented as `[*]`', items: { type: 'string', }, + example: ['developerexperience', 'enterprisegrowth'], }, expiresAt: { type: 'string', format: 'date-time', nullable: true, + description: `The token's expiration date. NULL if the token doesn't have an expiration set.`, + example: '2023-04-19T08:15:14.000Z', }, createdAt: { type: 'string', format: 'date-time', - nullable: true, + example: '2023-04-19T08:15:14.000Z', + description: 'When the token was created.', }, seenAt: { type: 'string', format: 'date-time', nullable: true, + example: '2023-04-19T08:15:14.000Z', + description: + 'When the token was last seen/used to authenticate with. NULL if the token has not yet been used for authentication.', }, alias: { type: 'string', nullable: true, + description: `Alias is no longer in active use and will often be NULL. It's kept around as a way of allowing old proxy tokens created with the old metadata format to keep working.`, + example: 'randomid-or-some-alias', }, }, - anyOf: [ - { - properties: { - username: { - type: 'string', - }, - }, - required: ['username'], - }, - { - properties: { - tokenName: { - type: 'string', - }, - }, - required: ['tokenName'], - }, - ], components: {}, } as const; diff --git a/src/lib/openapi/spec/api-tokens-schema.ts b/src/lib/openapi/spec/api-tokens-schema.ts index 48379f16c7..e4d4ca1b98 100644 --- a/src/lib/openapi/spec/api-tokens-schema.ts +++ b/src/lib/openapi/spec/api-tokens-schema.ts @@ -6,12 +6,14 @@ export const apiTokensSchema = { type: 'object', additionalProperties: false, required: ['tokens'], + description: 'Contains a list of API tokens.', properties: { tokens: { type: 'array', items: { $ref: '#/components/schemas/apiTokenSchema', }, + description: 'A list of API tokens.', }, }, components: { diff --git a/src/lib/openapi/spec/health-overview-schema.ts b/src/lib/openapi/spec/health-overview-schema.ts index 5659fec7c1..2353d427c7 100644 --- a/src/lib/openapi/spec/health-overview-schema.ts +++ b/src/lib/openapi/spec/health-overview-schema.ts @@ -15,17 +15,33 @@ export const healthOverviewSchema = { $id: '#/components/schemas/healthOverviewSchema', type: 'object', additionalProperties: false, - required: ['version', 'name'], + required: [ + 'version', + 'name', + 'defaultStickiness', + 'mode', + 'members', + 'health', + 'environments', + 'features', + ], + description: `An overview of a project's stats and its health as described in the documentation on [technical debt](https://docs.getunleash.io/reference/technical-debt)`, properties: { version: { - type: 'number', + type: 'integer', + description: 'The project overview version.', + example: 1, }, name: { type: 'string', + description: `The project's name`, + example: 'enterprisegrowth', }, description: { type: 'string', nullable: true, + description: `The project's description`, + example: 'The project for all things enterprisegrowth', }, defaultStickiness: { type: 'string', @@ -41,30 +57,45 @@ export const healthOverviewSchema = { "The project's [collaboration mode](https://docs.getunleash.io/reference/project-collaboration-mode). Determines whether non-project members can submit change requests or not.", }, members: { - type: 'number', + type: 'integer', + description: 'The number of users/members in the project.', + example: 5, + minimum: 0, }, health: { - type: 'number', + type: 'integer', + description: + 'The overall [health rating](https://docs.getunleash.io/reference/technical-debt#health-rating) of the project.', + example: 95, }, environments: { type: 'array', items: { $ref: '#/components/schemas/projectEnvironmentSchema', }, + description: + 'An array containing the names of all the environments configured for the project.', }, features: { type: 'array', items: { $ref: '#/components/schemas/featureSchema', }, + description: + 'An array containing an overview of all the features of the project and their individual status', }, updatedAt: { type: 'string', format: 'date-time', nullable: true, + description: 'When the project was last updated.', + example: '2023-04-19T08:15:14.000Z', }, favorite: { type: 'boolean', + description: + 'Indicates if the project has been marked as a favorite by the current user requesting the project health overview.', + example: true, }, stats: { $ref: '#/components/schemas/projectStatsSchema', diff --git a/src/lib/openapi/spec/health-report-schema.ts b/src/lib/openapi/spec/health-report-schema.ts index ac445b78bc..e9073c0656 100644 --- a/src/lib/openapi/spec/health-report-schema.ts +++ b/src/lib/openapi/spec/health-report-schema.ts @@ -4,6 +4,8 @@ import { healthOverviewSchema } from './health-overview-schema'; export const healthReportSchema = { ...healthOverviewSchema, $id: '#/components/schemas/healthReportSchema', + description: + 'A report of the current health of the requested project, with datapoints like counters of currently active, stale, and potentially stale feature toggles.', required: [ ...healthOverviewSchema.required, 'potentiallyStaleCount', @@ -14,12 +16,18 @@ export const healthReportSchema = { ...healthOverviewSchema.properties, potentiallyStaleCount: { type: 'number', + description: 'The number of potentially stale feature toggles.', + example: 5, }, activeCount: { type: 'number', + description: 'The number of active feature toggles.', + example: 2, }, staleCount: { type: 'number', + description: 'The number of stale feature toggles.', + example: 10, }, }, } as const; diff --git a/src/lib/openapi/spec/project-environment-schema.ts b/src/lib/openapi/spec/project-environment-schema.ts index 82b76ef111..b78915eeda 100644 --- a/src/lib/openapi/spec/project-environment-schema.ts +++ b/src/lib/openapi/spec/project-environment-schema.ts @@ -5,16 +5,25 @@ export const projectEnvironmentSchema = { $id: '#/components/schemas/projectEnvironmentSchema', type: 'object', additionalProperties: false, + description: + 'Add an environment to a project, optionally also sets if change requests are enabled for this environment on the project', required: ['environment'], properties: { environment: { type: 'string', + description: 'The environment to add to the project', + example: 'development', }, changeRequestsEnabled: { type: 'boolean', + description: + 'Whether change requests should be enabled or for this environment on the project or not', + example: true, }, defaultStrategy: { $ref: '#/components/schemas/createFeatureStrategySchema', + description: + 'A default strategy to create for this environment on the project.', }, }, components: { diff --git a/src/lib/routes/admin-api/project/api-token.ts b/src/lib/routes/admin-api/project/api-token.ts index 9ab2908981..1b2277ce5c 100644 --- a/src/lib/routes/admin-api/project/api-token.ts +++ b/src/lib/routes/admin-api/project/api-token.ts @@ -8,6 +8,7 @@ import { emptyResponse, resourceCreatedResponseSchema, } from '../../../openapi'; +import { getStandardResponses } from '../../../openapi/util/standard-responses'; import User from '../../../types/user'; import { ADMIN, @@ -82,8 +83,12 @@ export class ProjectApiTokenController extends Controller { openApiService.validPath({ tags: ['Projects'], operationId: 'getProjectApiTokens', + summary: 'Get api tokens for project.', + description: + 'Returns the [project API tokens](https://docs.getunleash.io/how-to/how-to-create-project-api-tokens) that have been created for this project.', responses: { 200: createResponseSchema('apiTokensSchema'), + ...getStandardResponses(401, 403, 404), }, }), ], @@ -99,9 +104,12 @@ export class ProjectApiTokenController extends Controller { tags: ['Projects'], operationId: 'createProjectApiToken', requestBody: createRequestSchema('createApiTokenSchema'), + summary: 'Create a project API token.', + description: + 'Endpoint that allows creation of [project API tokens](https://docs.getunleash.io/reference/api-tokens-and-client-keys#api-token-visibility) for the specified project.', responses: { 201: resourceCreatedResponseSchema('apiTokenSchema'), - 400: emptyResponse, + ...getStandardResponses(400, 401, 403), }, }), ], @@ -117,8 +125,11 @@ export class ProjectApiTokenController extends Controller { openApiService.validPath({ tags: ['Projects'], operationId: 'deleteProjectApiToken', + summary: 'Delete a project API token.', + description: `This operation deletes the API token specified in the request URL. If the token doesn't exist, returns an OK response (status code 200).`, responses: { 200: emptyResponse, + ...getStandardResponses(401, 403), }, }), ], diff --git a/src/lib/routes/admin-api/project/environments.ts b/src/lib/routes/admin-api/project/environments.ts index 41b7ba49ef..9231609afc 100644 --- a/src/lib/routes/admin-api/project/environments.ts +++ b/src/lib/routes/admin-api/project/environments.ts @@ -55,10 +55,16 @@ export default class EnvironmentsController extends Controller { openApiService.validPath({ tags: ['Projects'], operationId: 'addEnvironmentToProject', + summary: 'Add an environment to a project.', + description: + 'This endpoint adds the provided environment to the specified project, with optional support for enabling and disabling change requests for the environment and project.', requestBody: createRequestSchema( 'projectEnvironmentSchema', ), - responses: { 200: emptyResponse }, + responses: { + 200: emptyResponse, + ...getStandardResponses(401, 403, 409), + }, }), ], }); @@ -73,7 +79,13 @@ export default class EnvironmentsController extends Controller { openApiService.validPath({ tags: ['Projects'], operationId: 'removeEnvironmentFromProject', - responses: { 200: emptyResponse }, + summary: 'Remove an environment from a project.', + description: + 'This endpoint removes the specified environment from the project.', + responses: { + 200: emptyResponse, + ...getStandardResponses(400, 401, 403), + }, }), ], }); diff --git a/src/lib/routes/admin-api/project/health-report.ts b/src/lib/routes/admin-api/project/health-report.ts index bb2434e7c3..6aa7c0d918 100644 --- a/src/lib/routes/admin-api/project/health-report.ts +++ b/src/lib/routes/admin-api/project/health-report.ts @@ -8,6 +8,7 @@ import { IProjectParam } from '../../../types/model'; import { NONE } from '../../../types/permissions'; import { OpenApiService } from '../../../services/openapi-service'; import { createResponseSchema } from '../../../openapi/util/create-response-schema'; +import { getStandardResponses } from '../../../openapi/util/standard-responses'; import { serializeDates } from '../../../types/serialize-dates'; import { healthReportSchema, @@ -42,8 +43,12 @@ export default class ProjectHealthReport extends Controller { openApiService.validPath({ tags: ['Projects'], operationId: 'getProjectHealthReport', + summary: 'Get a health report for a project.', + description: + 'This endpoint returns a health report for the specified project. This data is used for [the technical debt dashboard](https://docs.getunleash.io/reference/technical-debt#the-technical-debt-dashboard)', responses: { 200: createResponseSchema('healthReportSchema'), + ...getStandardResponses(401, 403, 404), }, }), ], diff --git a/src/lib/routes/admin-api/project/index.ts b/src/lib/routes/admin-api/project/index.ts index 845f6c081d..227064ef67 100644 --- a/src/lib/routes/admin-api/project/index.ts +++ b/src/lib/routes/admin-api/project/index.ts @@ -20,6 +20,7 @@ import { projectsSchema, ProjectsSchema, } from '../../../openapi'; +import { getStandardResponses } from '../../../openapi/util/standard-responses'; import { OpenApiService, SettingService } from '../../../services'; import { IAuthRequest } from '../../unleash-types'; import { ProjectApiTokenController } from './api-token'; @@ -49,8 +50,12 @@ export default class ProjectApi extends Controller { services.openApiService.validPath({ tags: ['Projects'], operationId: 'getProjects', + summary: 'Get a list of all projects.', + description: + 'This endpoint returns an list of all the projects in the Unleash instance.', responses: { 200: createResponseSchema('projectsSchema'), + ...getStandardResponses(401, 403), }, }), ], @@ -65,8 +70,12 @@ export default class ProjectApi extends Controller { services.openApiService.validPath({ tags: ['Projects'], operationId: 'getProjectOverview', + summary: 'Get an overview of a project.', + description: + 'This endpoint returns an overview of the specified projects stats, project health, number of members, which environments are configured, and the features in the project.', responses: { 200: createResponseSchema('projectOverviewSchema'), + ...getStandardResponses(401, 403, 404), }, }), ], diff --git a/src/lib/types/models/api-token.ts b/src/lib/types/models/api-token.ts index b1d55577ed..e0f0ef1648 100644 --- a/src/lib/types/models/api-token.ts +++ b/src/lib/types/models/api-token.ts @@ -16,7 +16,7 @@ export interface ILegacyApiTokenCreate { */ username?: string; type: ApiTokenType; - environment: string; + environment?: string; project?: string; projects?: string[]; expiresAt?: Date; @@ -42,7 +42,7 @@ export interface IApiToken extends Omit { seenAt?: Date; environment: string; project: string; - alias: string | null; + alias?: string | null; } export const isAllProjects = (projects: string[]): boolean => { diff --git a/src/test/e2e/api/admin/project/project.api.tokens.e2e.test.ts b/src/test/e2e/api/admin/project/project.api.tokens.e2e.test.ts new file mode 100644 index 0000000000..e60e02100b --- /dev/null +++ b/src/test/e2e/api/admin/project/project.api.tokens.e2e.test.ts @@ -0,0 +1,115 @@ +import dbInit, { ITestDb } from '../../../helpers/database-init'; +import { + IUnleashTest, + setupAppWithCustomConfig, +} from '../../../helpers/test-helper'; +import getLogger from '../../../../fixtures/no-logger'; +import { ApiTokenType } from '../../../../../lib/types/models/api-token'; + +let app: IUnleashTest; +let db: ITestDb; + +beforeAll(async () => { + db = await dbInit('project_api_tokens_serial', getLogger); + app = await setupAppWithCustomConfig(db.stores, { + experimental: { + flags: { + strictSchemaValidation: true, + }, + }, + }); +}); + +afterEach(async () => { + await db.stores.apiTokenStore.deleteAll(); +}); + +afterAll(async () => { + await app.destroy(); + await db.destroy(); +}); + +test('Returns empty list of tokens', async () => { + return app.request + .get('/api/admin/projects/default/api-tokens') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.tokens.length).toBe(0); + }); +}); +test('Returns list of tokens', async () => { + const tokenSecret = 'random-secret'; + + await db.stores.apiTokenStore.insert({ + tokenName: 'test', + secret: tokenSecret, + type: ApiTokenType.CLIENT, + environment: 'default', + projects: ['default'], + }); + return app.request + .get('/api/admin/projects/default/api-tokens') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.tokens.length).toBe(1); + expect(res.body.tokens[0].secret).toBe(tokenSecret); + }); +}); + +test('Returns 404 when given non-existant projectId', async () => { + return app.request + .get('/api/admin/projects/wrong/api-tokens') + .expect('Content-Type', /json/) + .expect(200) + .expect((res) => { + expect(res.body.tokens.length).toBe(0); + }); +}); + +test('fails to create new client token when given wrong project', async () => { + return app.request + .post('/api/admin/projects/wrong/api-tokens') + .send({ + username: 'default-client', + type: 'client', + projects: ['wrong'], + environment: 'default', + }) + .set('Content-Type', 'application/json') + .expect(400); +}); + +test('creates new client token', async () => { + return app.request + .post('/api/admin/projects/default/api-tokens') + .send({ + username: 'default-client', + type: 'client', + projects: ['default'], + environment: 'default', + }) + .set('Content-Type', 'application/json') + .expect(201) + .expect((res) => { + expect(res.body.username).toBe('default-client'); + }); +}); + +test('Deletes existing tokens', async () => { + const tokenSecret = 'random-secret'; + + await db.stores.apiTokenStore.insert({ + tokenName: 'test', + secret: tokenSecret, + type: ApiTokenType.CLIENT, + environment: 'default', + projects: ['default'], + }); + + return app.request + .delete(`/api/admin/projects/default/api-tokens/${tokenSecret}`) + .set('Content-Type', 'application/json') + .expect(200); +}); 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 7dd086925c..bc7aeb9044 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 @@ -632,59 +632,56 @@ The provider you choose for your addon dictates what properties the \`parameters }, "apiTokenSchema": { "additionalProperties": false, - "anyOf": [ - { - "properties": { - "username": { - "type": "string", - }, - }, - "required": [ - "username", - ], - }, - { - "properties": { - "tokenName": { - "type": "string", - }, - }, - "required": [ - "tokenName", - ], - }, - ], + "description": "An overview of an [Unleash API token](https://docs.getunleash.io/reference/api-tokens-and-client-keys).", "properties": { "alias": { + "description": "Alias is no longer in active use and will often be NULL. It's kept around as a way of allowing old proxy tokens created with the old metadata format to keep working.", + "example": "randomid-or-some-alias", "nullable": true, "type": "string", }, "createdAt": { + "description": "When the token was created.", + "example": "2023-04-19T08:15:14.000Z", "format": "date-time", - "nullable": true, "type": "string", }, "environment": { + "description": "The environment the token has access to. \`*\` if it has access to all environments.", + "example": "development", "type": "string", }, "expiresAt": { + "description": "The token's expiration date. NULL if the token doesn't have an expiration set.", + "example": "2023-04-19T08:15:14.000Z", "format": "date-time", "nullable": true, "type": "string", }, "project": { + "description": "The project this token belongs to.", + "example": "developerexperience", "type": "string", }, "projects": { + "description": "The list of projects this token has access to. If the token has access to specific projects they will be listed here. If the token has access to all projects it will be represented as \`[*]\`", + "example": [ + "developerexperience", + "enterprisegrowth", + ], "items": { "type": "string", }, "type": "array", }, "secret": { + "description": "The token used for authentication.", + "example": "project:environment.xyzrandomstring", "type": "string", }, "seenAt": { + "description": "When the token was last seen/used to authenticate with. NULL if the token has not yet been used for authentication.", + "example": "2023-04-19T08:15:14.000Z", "format": "date-time", "nullable": true, "type": "string", @@ -695,28 +692,38 @@ The provider you choose for your addon dictates what properties the \`parameters "type": "string", }, "type": { + "description": "The type of API token", "enum": [ "client", "admin", "frontend", ], + "example": "client", "type": "string", }, "username": { "deprecated": true, "description": "This property was deprecated in Unleash v5. Prefer the \`tokenName\` property instead.", + "example": "a-name", "type": "string", }, }, "required": [ + "secret", + "tokenName", "type", + "project", + "projects", + "createdAt", ], "type": "object", }, "apiTokensSchema": { "additionalProperties": false, + "description": "Contains a list of API tokens.", "properties": { "tokens": { + "description": "A list of API tokens.", "items": { "$ref": "#/components/schemas/apiTokenSchema", }, @@ -2665,6 +2672,7 @@ The provider you choose for your addon dictates what properties the \`parameters }, "healthOverviewSchema": { "additionalProperties": false, + "description": "An overview of a project's stats and its health as described in the documentation on [technical debt](https://docs.getunleash.io/reference/technical-debt)", "properties": { "defaultStickiness": { "description": "A default stickiness for the project affecting the default stickiness value for variants and Gradual Rollout strategy", @@ -2672,29 +2680,40 @@ The provider you choose for your addon dictates what properties the \`parameters "type": "string", }, "description": { + "description": "The project's description", + "example": "The project for all things enterprisegrowth", "nullable": true, "type": "string", }, "environments": { + "description": "An array containing the names of all the environments configured for the project.", "items": { "$ref": "#/components/schemas/projectEnvironmentSchema", }, "type": "array", }, "favorite": { + "description": "Indicates if the project has been marked as a favorite by the current user requesting the project health overview.", + "example": true, "type": "boolean", }, "features": { + "description": "An array containing an overview of all the features of the project and their individual status", "items": { "$ref": "#/components/schemas/featureSchema", }, "type": "array", }, "health": { - "type": "number", + "description": "The overall [health rating](https://docs.getunleash.io/reference/technical-debt#health-rating) of the project.", + "example": 95, + "type": "integer", }, "members": { - "type": "number", + "description": "The number of users/members in the project.", + "example": 5, + "minimum": 0, + "type": "integer", }, "mode": { "description": "The project's [collaboration mode](https://docs.getunleash.io/reference/project-collaboration-mode). Determines whether non-project members can submit change requests or not.", @@ -2706,6 +2725,8 @@ The provider you choose for your addon dictates what properties the \`parameters "type": "string", }, "name": { + "description": "The project's name", + "example": "enterprisegrowth", "type": "string", }, "stats": { @@ -2713,24 +2734,37 @@ The provider you choose for your addon dictates what properties the \`parameters "description": "Project statistics", }, "updatedAt": { + "description": "When the project was last updated.", + "example": "2023-04-19T08:15:14.000Z", "format": "date-time", "nullable": true, "type": "string", }, "version": { - "type": "number", + "description": "The project overview version.", + "example": 1, + "type": "integer", }, }, "required": [ "version", "name", + "defaultStickiness", + "mode", + "members", + "health", + "environments", + "features", ], "type": "object", }, "healthReportSchema": { "additionalProperties": false, + "description": "A report of the current health of the requested project, with datapoints like counters of currently active, stale, and potentially stale feature toggles.", "properties": { "activeCount": { + "description": "The number of active feature toggles.", + "example": 2, "type": "number", }, "defaultStickiness": { @@ -2739,29 +2773,40 @@ The provider you choose for your addon dictates what properties the \`parameters "type": "string", }, "description": { + "description": "The project's description", + "example": "The project for all things enterprisegrowth", "nullable": true, "type": "string", }, "environments": { + "description": "An array containing the names of all the environments configured for the project.", "items": { "$ref": "#/components/schemas/projectEnvironmentSchema", }, "type": "array", }, "favorite": { + "description": "Indicates if the project has been marked as a favorite by the current user requesting the project health overview.", + "example": true, "type": "boolean", }, "features": { + "description": "An array containing an overview of all the features of the project and their individual status", "items": { "$ref": "#/components/schemas/featureSchema", }, "type": "array", }, "health": { - "type": "number", + "description": "The overall [health rating](https://docs.getunleash.io/reference/technical-debt#health-rating) of the project.", + "example": 95, + "type": "integer", }, "members": { - "type": "number", + "description": "The number of users/members in the project.", + "example": 5, + "minimum": 0, + "type": "integer", }, "mode": { "description": "The project's [collaboration mode](https://docs.getunleash.io/reference/project-collaboration-mode). Determines whether non-project members can submit change requests or not.", @@ -2773,12 +2818,18 @@ The provider you choose for your addon dictates what properties the \`parameters "type": "string", }, "name": { + "description": "The project's name", + "example": "enterprisegrowth", "type": "string", }, "potentiallyStaleCount": { + "description": "The number of potentially stale feature toggles.", + "example": 5, "type": "number", }, "staleCount": { + "description": "The number of stale feature toggles.", + "example": 10, "type": "number", }, "stats": { @@ -2786,17 +2837,27 @@ The provider you choose for your addon dictates what properties the \`parameters "description": "Project statistics", }, "updatedAt": { + "description": "When the project was last updated.", + "example": "2023-04-19T08:15:14.000Z", "format": "date-time", "nullable": true, "type": "string", }, "version": { - "type": "number", + "description": "The project overview version.", + "example": 1, + "type": "integer", }, }, "required": [ "version", "name", + "defaultStickiness", + "mode", + "members", + "health", + "environments", + "features", "potentiallyStaleCount", "activeCount", "staleCount", @@ -3606,14 +3667,20 @@ The provider you choose for your addon dictates what properties the \`parameters }, "projectEnvironmentSchema": { "additionalProperties": false, + "description": "Add an environment to a project, optionally also sets if change requests are enabled for this environment on the project", "properties": { "changeRequestsEnabled": { + "description": "Whether change requests should be enabled or for this environment on the project or not", + "example": true, "type": "boolean", }, "defaultStrategy": { "$ref": "#/components/schemas/createFeatureStrategySchema", + "description": "A default strategy to create for this environment on the project.", }, "environment": { + "description": "The environment to add to the project", + "example": "development", "type": "string", }, }, @@ -9017,6 +9084,7 @@ If the provided project does not exist, the list of events will be empty.", }, "/api/admin/projects": { "get": { + "description": "This endpoint returns an list of all the projects in the Unleash instance.", "operationId": "getProjects", "responses": { "200": { @@ -9029,7 +9097,62 @@ If the provided project does not exist, the list of events will be empty.", }, "description": "projectsSchema", }, + "401": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You must log in to use Unleash. Your request had no authorization header, so we could not authorize you. Try logging in at /auth/simple/login.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "AuthenticationRequired", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.", + }, + "403": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You need the "UPDATE_ADDON" permission to perform this action in the "development" environment.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NoAccessError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "User credentials are valid but does not have enough privileges to execute this operation", + }, }, + "summary": "Get a list of all projects.", "tags": [ "Projects", ], @@ -9037,6 +9160,7 @@ If the provided project does not exist, the list of events will be empty.", }, "/api/admin/projects/{projectId}": { "get": { + "description": "This endpoint returns an overview of the specified projects stats, project health, number of members, which environments are configured, and the features in the project.", "operationId": "getProjectOverview", "parameters": [ { @@ -9059,7 +9183,89 @@ If the provided project does not exist, the list of events will be empty.", }, "description": "projectOverviewSchema", }, + "401": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You must log in to use Unleash. Your request had no authorization header, so we could not authorize you. Try logging in at /auth/simple/login.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "AuthenticationRequired", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.", + }, + "403": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You need the "UPDATE_ADDON" permission to perform this action in the "development" environment.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NoAccessError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "User credentials are valid but does not have enough privileges to execute this operation", + }, + "404": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "Could not find the addon with ID "12345".", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NotFoundError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "The requested resource was not found.", + }, }, + "summary": "Get an overview of a project.", "tags": [ "Projects", ], @@ -9067,6 +9273,7 @@ If the provided project does not exist, the list of events will be empty.", }, "/api/admin/projects/{projectId}/api-tokens": { "get": { + "description": "Returns the [project API tokens](https://docs.getunleash.io/how-to/how-to-create-project-api-tokens) that have been created for this project.", "operationId": "getProjectApiTokens", "parameters": [ { @@ -9089,12 +9296,95 @@ If the provided project does not exist, the list of events will be empty.", }, "description": "apiTokensSchema", }, + "401": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You must log in to use Unleash. Your request had no authorization header, so we could not authorize you. Try logging in at /auth/simple/login.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "AuthenticationRequired", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.", + }, + "403": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You need the "UPDATE_ADDON" permission to perform this action in the "development" environment.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NoAccessError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "User credentials are valid but does not have enough privileges to execute this operation", + }, + "404": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "Could not find the addon with ID "12345".", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NotFoundError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "The requested resource was not found.", + }, }, + "summary": "Get api tokens for project.", "tags": [ "Projects", ], }, "post": { + "description": "Endpoint that allows creation of [project API tokens](https://docs.getunleash.io/reference/api-tokens-and-client-keys#api-token-visibility) for the specified project.", "operationId": "createProjectApiToken", "parameters": [ { @@ -9138,9 +9428,88 @@ If the provided project does not exist, the list of events will be empty.", }, }, "400": { - "description": "This response has no body.", + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "The request payload you provided doesn't conform to the schema. The .parameters property should be object. You sent [].", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "ValidationError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "The request data does not match what we expect.", + }, + "401": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You must log in to use Unleash. Your request had no authorization header, so we could not authorize you. Try logging in at /auth/simple/login.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "AuthenticationRequired", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.", + }, + "403": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You need the "UPDATE_ADDON" permission to perform this action in the "development" environment.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NoAccessError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "User credentials are valid but does not have enough privileges to execute this operation", }, }, + "summary": "Create a project API token.", "tags": [ "Projects", ], @@ -9148,6 +9517,7 @@ If the provided project does not exist, the list of events will be empty.", }, "/api/admin/projects/{projectId}/api-tokens/{token}": { "delete": { + "description": "This operation deletes the API token specified in the request URL. If the token doesn't exist, returns an OK response (status code 200).", "operationId": "deleteProjectApiToken", "parameters": [ { @@ -9171,7 +9541,62 @@ If the provided project does not exist, the list of events will be empty.", "200": { "description": "This response has no body.", }, + "401": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You must log in to use Unleash. Your request had no authorization header, so we could not authorize you. Try logging in at /auth/simple/login.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "AuthenticationRequired", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.", + }, + "403": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You need the "UPDATE_ADDON" permission to perform this action in the "development" environment.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NoAccessError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "User credentials are valid but does not have enough privileges to execute this operation", + }, }, + "summary": "Delete a project API token.", "tags": [ "Projects", ], @@ -9339,6 +9764,7 @@ If the provided project does not exist, the list of events will be empty.", }, "/api/admin/projects/{projectId}/environments": { "post": { + "description": "This endpoint adds the provided environment to the specified project, with optional support for enabling and disabling change requests for the environment and project.", "operationId": "addEnvironmentToProject", "parameters": [ { @@ -9365,7 +9791,89 @@ If the provided project does not exist, the list of events will be empty.", "200": { "description": "This response has no body.", }, + "401": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You must log in to use Unleash. Your request had no authorization header, so we could not authorize you. Try logging in at /auth/simple/login.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "AuthenticationRequired", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.", + }, + "403": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You need the "UPDATE_ADDON" permission to perform this action in the "development" environment.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NoAccessError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "User credentials are valid but does not have enough privileges to execute this operation", + }, + "409": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "There is already a feature called "my-awesome-feature".", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NameExistsError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "The provided resource can not be created or updated because it would conflict with the current state of the resource or with an already existing resource, respectively.", + }, }, + "summary": "Add an environment to a project.", "tags": [ "Projects", ], @@ -9373,6 +9881,7 @@ If the provided project does not exist, the list of events will be empty.", }, "/api/admin/projects/{projectId}/environments/{environment}": { "delete": { + "description": "This endpoint removes the specified environment from the project.", "operationId": "removeEnvironmentFromProject", "parameters": [ { @@ -9396,7 +9905,89 @@ If the provided project does not exist, the list of events will be empty.", "200": { "description": "This response has no body.", }, + "400": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "The request payload you provided doesn't conform to the schema. The .parameters property should be object. You sent [].", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "ValidationError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "The request data does not match what we expect.", + }, + "401": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You must log in to use Unleash. Your request had no authorization header, so we could not authorize you. Try logging in at /auth/simple/login.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "AuthenticationRequired", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.", + }, + "403": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You need the "UPDATE_ADDON" permission to perform this action in the "development" environment.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NoAccessError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "User credentials are valid but does not have enough privileges to execute this operation", + }, }, + "summary": "Remove an environment from a project.", "tags": [ "Projects", ], @@ -10862,6 +11453,7 @@ If the provided project does not exist, the list of events will be empty.", }, "/api/admin/projects/{projectId}/health-report": { "get": { + "description": "This endpoint returns a health report for the specified project. This data is used for [the technical debt dashboard](https://docs.getunleash.io/reference/technical-debt#the-technical-debt-dashboard)", "operationId": "getProjectHealthReport", "parameters": [ { @@ -10884,7 +11476,89 @@ If the provided project does not exist, the list of events will be empty.", }, "description": "healthReportSchema", }, + "401": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You must log in to use Unleash. Your request had no authorization header, so we could not authorize you. Try logging in at /auth/simple/login.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "AuthenticationRequired", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.", + }, + "403": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "You need the "UPDATE_ADDON" permission to perform this action in the "development" environment.", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NoAccessError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "User credentials are valid but does not have enough privileges to execute this operation", + }, + "404": { + "content": { + "application/json": { + "schema": { + "properties": { + "id": { + "description": "The ID of the error instance", + "example": "9c40958a-daac-400e-98fb-3bb438567008", + "type": "string", + }, + "message": { + "description": "A description of what went wrong.", + "example": "Could not find the addon with ID "12345".", + "type": "string", + }, + "name": { + "description": "The name of the error kind", + "example": "NotFoundError", + "type": "string", + }, + }, + "type": "object", + }, + }, + }, + "description": "The requested resource was not found.", + }, }, + "summary": "Get a health report for a project.", "tags": [ "Projects", ],