diff --git a/src/lib/features/export-import-toggles/export-import-controller.ts b/src/lib/features/export-import-toggles/export-import-controller.ts index 0ea42b9ad8..12a267c263 100644 --- a/src/lib/features/export-import-toggles/export-import-controller.ts +++ b/src/lib/features/export-import-toggles/export-import-controller.ts @@ -16,6 +16,7 @@ import { emptyResponse, ExportQuerySchema, exportResultSchema, + getStandardResponses, ImportTogglesSchema, importTogglesValidateSchema, } from '../../openapi'; @@ -23,6 +24,7 @@ import { IAuthRequest } from '../../routes/unleash-types'; import { extractUsername } from '../../util'; import { BadDataError, InvalidOperationError } from '../../error'; import ApiUser from '../../types/api-user'; +import { endpointDescriptions } from '../../openapi/endpoint-descriptions'; class ExportImportController extends Controller { private logger: Logger; @@ -65,12 +67,14 @@ class ExportImportController extends Controller { handler: this.export, middleware: [ this.openApiService.validPath({ - tags: ['Unstable'], + tags: ['Import/Export'], operationId: 'exportFeatures', requestBody: createRequestSchema('exportQuerySchema'), responses: { 200: createResponseSchema('exportResultSchema'), + ...getStandardResponses(404), }, + ...endpointDescriptions.admin.export, }), ], }); @@ -81,17 +85,16 @@ class ExportImportController extends Controller { handler: this.validateImport, middleware: [ openApiService.validPath({ - summary: - 'Validate import of feature toggles for an environment in the project', - description: `Unleash toggles exported from a different instance can be imported into a new project and environment`, - tags: ['Unstable'], + tags: ['Import/Export'], operationId: 'validateImport', requestBody: createRequestSchema('importTogglesSchema'), responses: { 200: createResponseSchema( 'importTogglesValidateSchema', ), + ...getStandardResponses(404), }, + ...endpointDescriptions.admin.validateImport, }), ], }); @@ -102,15 +105,14 @@ class ExportImportController extends Controller { handler: this.importData, middleware: [ openApiService.validPath({ - summary: - 'Import feature toggles for an environment in the project', - description: `Unleash toggles exported from a different instance can be imported into a new project and environment`, - tags: ['Unstable'], + tags: ['Import/Export'], operationId: 'importToggles', requestBody: createRequestSchema('importTogglesSchema'), responses: { 200: emptyResponse, + ...getStandardResponses(404), }, + ...endpointDescriptions.admin.import, }), ], }); diff --git a/src/lib/openapi/endpoint-descriptions.ts b/src/lib/openapi/endpoint-descriptions.ts index 1eeb92b226..6d12d03da1 100644 --- a/src/lib/openapi/endpoint-descriptions.ts +++ b/src/lib/openapi/endpoint-descriptions.ts @@ -23,5 +23,18 @@ export const endpointDescriptions = { summary: 'Batch evaluate an Unleash context against a set of environments and projects.', }, + export: { + description: + "Exports all features listed in the `features` property from the environment specified in the request body. If set to `true`, the `downloadFile` property will let you download a file with the exported data. Otherwise, the export data is returned directly as JSON. Refer to the documentation for more information about [Unleash's export functionality](https://docs.getunleash.io/reference/deploy/environment-import-export#export).", + summary: 'Export feature toggles from an environment', + }, + validateImport: { + summary: 'Validate feature import data', + description: `Validates a feature toggle data set. Checks whether the data can be imported into the specified project and environment. The returned value is an object that contains errors, warnings, and permissions required to perform the import, as described in the [import documentation](https://docs.getunleash.io/reference/deploy/environment-import-export#import).`, + }, + import: { + summary: 'Import feature toggles', + description: `[Import feature toggles](https://docs.getunleash.io/reference/deploy/environment-import-export#import) into a specific project and environment.`, + }, }, } as const; diff --git a/src/lib/openapi/meta-schema-rules.test.ts b/src/lib/openapi/meta-schema-rules.test.ts index 71cd13bbb2..ce8bf49d78 100644 --- a/src/lib/openapi/meta-schema-rules.test.ts +++ b/src/lib/openapi/meta-schema-rules.test.ts @@ -101,8 +101,6 @@ const metaRules: Rule[] = [ 'environmentsProjectSchema', 'eventSchema', 'eventsSchema', - 'exportResultSchema', - 'exportQuerySchema', 'featureEnvironmentSchema', 'featureEventsSchema', 'featureSchema', @@ -155,9 +153,6 @@ const metaRules: Rule[] = [ 'variantFlagSchema', 'versionSchema', 'projectOverviewSchema', - 'importTogglesSchema', - 'importTogglesValidateSchema', - 'importTogglesValidateItemSchema', ], }, { @@ -185,8 +180,6 @@ const metaRules: Rule[] = [ 'environmentsSchema', 'eventSchema', 'eventsSchema', - 'exportResultSchema', - 'exportQuerySchema', 'featureEventsSchema', 'featureSchema', 'featuresSchema', @@ -241,9 +234,6 @@ const metaRules: Rule[] = [ 'variantFlagSchema', 'variantsSchema', 'versionSchema', - 'importTogglesSchema', - 'importTogglesValidateSchema', - 'importTogglesValidateItemSchema', ], }, ]; diff --git a/src/lib/openapi/spec/export-query-schema.ts b/src/lib/openapi/spec/export-query-schema.ts index 9af9a2f63e..fab0cee34a 100644 --- a/src/lib/openapi/spec/export-query-schema.ts +++ b/src/lib/openapi/spec/export-query-schema.ts @@ -5,12 +5,18 @@ export const exportQuerySchema = { type: 'object', additionalProperties: true, required: ['environment'], + description: + 'Available query parameters for the [deprecated export/import](https://docs.getunleash.io/reference/deploy/import-export) functionality.', properties: { environment: { type: 'string', + example: 'development', + description: 'The environment to export from', }, downloadFile: { type: 'boolean', + example: true, + description: 'Whether to return a downloadable file', }, }, oneOf: [ @@ -19,6 +25,7 @@ export const exportQuerySchema = { properties: { features: { type: 'array', + example: ['MyAwesomeFeature'], items: { type: 'string', minLength: 1, @@ -32,6 +39,7 @@ export const exportQuerySchema = { properties: { tag: { type: 'string', + example: 'release', description: 'Selects features to export by tag. Takes precedence over the features field.', }, diff --git a/src/lib/openapi/spec/export-result-schema.ts b/src/lib/openapi/spec/export-result-schema.ts index 02944841c7..4e325215bd 100644 --- a/src/lib/openapi/spec/export-result-schema.ts +++ b/src/lib/openapi/spec/export-result-schema.ts @@ -16,40 +16,126 @@ export const exportResultSchema = { $id: '#/components/schemas/exportResultSchema', type: 'object', additionalProperties: false, + description: + 'The result of the export operation, providing you with the feature toggle definitions, strategy definitions and the rest of the elements relevant to the features (tags, environments etc.)', required: ['features', 'featureStrategies', 'tagTypes'], properties: { features: { type: 'array', + description: 'All the exported features.', + example: [ + { + name: 'my-feature', + description: 'best feature ever', + type: 'release', + project: 'default', + stale: false, + impressionData: false, + archived: false, + }, + ], items: { $ref: '#/components/schemas/featureSchema', }, }, featureStrategies: { type: 'array', + description: + 'All strategy instances that are used by the exported features in the `features` list.', + example: [ + { + name: 'flexibleRollout', + id: '924974d7-8003-43ee-87eb-c5f887c06fd1', + featureName: 'my-feature', + title: 'Rollout 50%', + parameters: { + groupId: 'default', + rollout: '50', + stickiness: 'random', + }, + constraints: [], + disabled: false, + segments: [1], + }, + ], items: { $ref: '#/components/schemas/featureStrategySchema', }, }, featureEnvironments: { type: 'array', + description: + 'Environment-specific configuration for all the features in the `features` list. Includes data such as whether the feature is enabled in the selected export environment, whether there are any variants assigned, etc.', + example: [ + { + enabled: true, + featureName: 'my-feature', + environment: 'development', + variants: [ + { + name: 'a', + weight: 500, + overrides: [], + stickiness: 'random', + weightType: 'variable', + }, + { + name: 'b', + weight: 500, + overrides: [], + stickiness: 'random', + weightType: 'variable', + }, + ], + name: 'variant-testing', + }, + ], items: { $ref: '#/components/schemas/featureEnvironmentSchema', }, }, contextFields: { type: 'array', + description: + 'A list of all the context fields that are in use by any of the strategies in the `featureStrategies` list.', + example: [ + { + name: 'appName', + description: 'Allows you to constrain on application name', + stickiness: false, + sortOrder: 2, + legalValues: [], + }, + ], items: { $ref: '#/components/schemas/contextFieldSchema', }, }, featureTags: { type: 'array', + description: + 'A list of all the tags that have been applied to any of the features in the `features` list.', + example: [ + { + featureName: 'my-feature', + tagType: 'simple', + tagValue: 'user-facing', + }, + ], items: { $ref: '#/components/schemas/featureTagSchema', }, }, segments: { type: 'array', + description: + 'A list of all the segments that are used by the strategies in the `featureStrategies` list.', + example: [ + { + id: 1, + name: 'new-segment-name', + }, + ], items: { type: 'object', additionalProperties: false, @@ -66,6 +152,15 @@ export const exportResultSchema = { }, tagTypes: { type: 'array', + description: + 'A list of all of the tag types that are used in the `featureTags` list.', + example: [ + { + name: 'simple', + description: 'Used to simplify filtering of features', + icon: '#', + }, + ], items: { $ref: '#/components/schemas/tagTypeSchema', }, diff --git a/src/lib/openapi/spec/import-toggles-schema.ts b/src/lib/openapi/spec/import-toggles-schema.ts index ca82dd8062..3c82019b1d 100644 --- a/src/lib/openapi/spec/import-toggles-schema.ts +++ b/src/lib/openapi/spec/import-toggles-schema.ts @@ -19,12 +19,20 @@ export const importTogglesSchema = { type: 'object', required: ['project', 'environment', 'data'], additionalProperties: false, + description: + 'The result of the export operation for a project and environment, used at import', properties: { project: { type: 'string', + example: 'My awesome project', + description: + 'The exported [project](https://docs.getunleash.io/reference/projects)', }, environment: { type: 'string', + example: 'development', + description: + 'The exported [environment](https://docs.getunleash.io/reference/environments)', }, data: { $ref: '#/components/schemas/exportResultSchema', diff --git a/src/lib/openapi/spec/import-toggles-validate-item-schema.ts b/src/lib/openapi/spec/import-toggles-validate-item-schema.ts index 5b43cbea2a..878aa8b53d 100644 --- a/src/lib/openapi/spec/import-toggles-validate-item-schema.ts +++ b/src/lib/openapi/spec/import-toggles-validate-item-schema.ts @@ -5,12 +5,19 @@ export const importTogglesValidateItemSchema = { type: 'object', required: ['message', 'affectedItems'], additionalProperties: false, + description: + 'A description of an error or warning pertaining to a feature toggle import job.', properties: { message: { type: 'string', + description: 'The validation error message', + example: + 'You cannot import a feature that already exist in other projects. You already have the following features defined outside of project default:', }, affectedItems: { type: 'array', + description: 'The items affected by this error message ', + example: ['some-feature-a', 'some-feature-b'], items: { type: 'string', }, diff --git a/src/lib/openapi/spec/import-toggles-validate-schema.ts b/src/lib/openapi/spec/import-toggles-validate-schema.ts index 4b5883e70e..5c0fc3750c 100644 --- a/src/lib/openapi/spec/import-toggles-validate-schema.ts +++ b/src/lib/openapi/spec/import-toggles-validate-schema.ts @@ -6,24 +6,46 @@ export const importTogglesValidateSchema = { type: 'object', required: ['errors', 'warnings'], additionalProperties: false, + description: + 'An object containing [feature import](https://docs.getunleash.io/reference/deploy/environment-import-export) validation results.', properties: { errors: { + description: + 'A list of errors that prevent the provided data from being successfully imported.', type: 'array', + example: [ + { + message: + 'You cannot import a feature that already exist in other projects. You already have the following features defined outside of project default:', + affectedItems: ['my-feature (in project project-854)'], + }, + ], items: { $ref: '#/components/schemas/importTogglesValidateItemSchema', }, }, warnings: { type: 'array', + description: 'A list of warnings related to the provided data.', + example: [ + { + message: + 'The following strategy types will be used in import. Please make sure the strategy type parameters are configured as in source environment:', + affectedItems: ['custom-strategy-7'], + }, + ], items: { $ref: '#/components/schemas/importTogglesValidateItemSchema', }, }, permissions: { type: 'array', + description: + 'Any additional permissions required to import the data. If the list is empty, you require no additional permissions beyond what your user already has.', items: { $ref: '#/components/schemas/importTogglesValidateItemSchema', }, + example: [], }, }, components: { diff --git a/src/lib/openapi/util/openapi-tags.ts b/src/lib/openapi/util/openapi-tags.ts index 4a3e487f27..311423bd79 100644 --- a/src/lib/openapi/util/openapi-tags.ts +++ b/src/lib/openapi/util/openapi-tags.ts @@ -128,6 +128,11 @@ const OPENAPI_TAGS = [ name: 'Telemetry', description: 'API for information about telemetry collection', }, + { + name: 'Notifications', + description: + 'API for managing [notifications](https://docs.getunleash.io/reference/notifications).', + }, ] as const; // make the export mutable, so it can be used in a schema 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 9d4b5e3283..0870c3b254 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 @@ -2393,11 +2393,15 @@ The provider you choose for your addon dictates what properties the \`parameters }, "exportQuerySchema": { "additionalProperties": true, + "description": "Available query parameters for the [deprecated export/import](https://docs.getunleash.io/reference/deploy/import-export) functionality.", "oneOf": [ { "properties": { "features": { "description": "Selects features to export by name.", + "example": [ + "MyAwesomeFeature", + ], "items": { "minLength": 1, "type": "string", @@ -2413,6 +2417,7 @@ The provider you choose for your addon dictates what properties the \`parameters "properties": { "tag": { "description": "Selects features to export by tag. Takes precedence over the features field.", + "example": "release", "type": "string", }, }, @@ -2423,9 +2428,13 @@ The provider you choose for your addon dictates what properties the \`parameters ], "properties": { "downloadFile": { + "description": "Whether to return a downloadable file", + "example": true, "type": "boolean", }, "environment": { + "description": "The environment to export from", + "example": "development", "type": "string", }, }, @@ -2436,38 +2445,120 @@ The provider you choose for your addon dictates what properties the \`parameters }, "exportResultSchema": { "additionalProperties": false, + "description": "The result of the export operation, providing you with the feature toggle definitions, strategy definitions and the rest of the elements relevant to the features (tags, environments etc.)", "properties": { "contextFields": { + "description": "A list of all the context fields that are in use by any of the strategies in the \`featureStrategies\` list.", + "example": [ + { + "description": "Allows you to constrain on application name", + "legalValues": [], + "name": "appName", + "sortOrder": 2, + "stickiness": false, + }, + ], "items": { "$ref": "#/components/schemas/contextFieldSchema", }, "type": "array", }, "featureEnvironments": { + "description": "Environment-specific configuration for all the features in the \`features\` list. Includes data such as whether the feature is enabled in the selected export environment, whether there are any variants assigned, etc.", + "example": [ + { + "enabled": true, + "environment": "development", + "featureName": "my-feature", + "name": "variant-testing", + "variants": [ + { + "name": "a", + "overrides": [], + "stickiness": "random", + "weight": 500, + "weightType": "variable", + }, + { + "name": "b", + "overrides": [], + "stickiness": "random", + "weight": 500, + "weightType": "variable", + }, + ], + }, + ], "items": { "$ref": "#/components/schemas/featureEnvironmentSchema", }, "type": "array", }, "featureStrategies": { + "description": "All strategy instances that are used by the exported features in the \`features\` list.", + "example": [ + { + "constraints": [], + "disabled": false, + "featureName": "my-feature", + "id": "924974d7-8003-43ee-87eb-c5f887c06fd1", + "name": "flexibleRollout", + "parameters": { + "groupId": "default", + "rollout": "50", + "stickiness": "random", + }, + "segments": [ + 1, + ], + "title": "Rollout 50%", + }, + ], "items": { "$ref": "#/components/schemas/featureStrategySchema", }, "type": "array", }, "featureTags": { + "description": "A list of all the tags that have been applied to any of the features in the \`features\` list.", + "example": [ + { + "featureName": "my-feature", + "tagType": "simple", + "tagValue": "user-facing", + }, + ], "items": { "$ref": "#/components/schemas/featureTagSchema", }, "type": "array", }, "features": { + "description": "All the exported features.", + "example": [ + { + "archived": false, + "description": "best feature ever", + "impressionData": false, + "name": "my-feature", + "project": "default", + "stale": false, + "type": "release", + }, + ], "items": { "$ref": "#/components/schemas/featureSchema", }, "type": "array", }, "segments": { + "description": "A list of all the segments that are used by the strategies in the \`featureStrategies\` list.", + "example": [ + { + "id": 1, + "name": "new-segment-name", + }, + ], "items": { "additionalProperties": false, "properties": { @@ -2486,6 +2577,14 @@ The provider you choose for your addon dictates what properties the \`parameters "type": "array", }, "tagTypes": { + "description": "A list of all of the tag types that are used in the \`featureTags\` list.", + "example": [ + { + "description": "Used to simplify filtering of features", + "icon": "#", + "name": "simple", + }, + ], "items": { "$ref": "#/components/schemas/tagTypeSchema", }, @@ -3358,14 +3457,19 @@ The provider you choose for your addon dictates what properties the \`parameters }, "importTogglesSchema": { "additionalProperties": false, + "description": "The result of the export operation for a project and environment, used at import", "properties": { "data": { "$ref": "#/components/schemas/exportResultSchema", }, "environment": { + "description": "The exported [environment](https://docs.getunleash.io/reference/environments)", + "example": "development", "type": "string", }, "project": { + "description": "The exported [project](https://docs.getunleash.io/reference/projects)", + "example": "My awesome project", "type": "string", }, }, @@ -3378,14 +3482,22 @@ The provider you choose for your addon dictates what properties the \`parameters }, "importTogglesValidateItemSchema": { "additionalProperties": false, + "description": "A description of an error or warning pertaining to a feature toggle import job.", "properties": { "affectedItems": { + "description": "The items affected by this error message ", + "example": [ + "some-feature-a", + "some-feature-b", + ], "items": { "type": "string", }, "type": "array", }, "message": { + "description": "The validation error message", + "example": "You cannot import a feature that already exist in other projects. You already have the following features defined outside of project default:", "type": "string", }, }, @@ -3397,20 +3509,41 @@ The provider you choose for your addon dictates what properties the \`parameters }, "importTogglesValidateSchema": { "additionalProperties": false, + "description": "An object containing [feature import](https://docs.getunleash.io/reference/deploy/environment-import-export) validation results.", "properties": { "errors": { + "description": "A list of errors that prevent the provided data from being successfully imported.", + "example": [ + { + "affectedItems": [ + "my-feature (in project project-854)", + ], + "message": "You cannot import a feature that already exist in other projects. You already have the following features defined outside of project default:", + }, + ], "items": { "$ref": "#/components/schemas/importTogglesValidateItemSchema", }, "type": "array", }, "permissions": { + "description": "Any additional permissions required to import the data. If the list is empty, you require no additional permissions beyond what your user already has.", + "example": [], "items": { "$ref": "#/components/schemas/importTogglesValidateItemSchema", }, "type": "array", }, "warnings": { + "description": "A list of warnings related to the provided data.", + "example": [ + { + "affectedItems": [ + "custom-strategy-7", + ], + "message": "The following strategy types will be used in import. Please make sure the strategy type parameters are configured as in source environment:", + }, + ], "items": { "$ref": "#/components/schemas/importTogglesValidateItemSchema", }, @@ -8548,6 +8681,7 @@ If the provided project does not exist, the list of events will be empty.", }, "/api/admin/features-batch/export": { "post": { + "description": "Exports all features listed in the \`features\` property from the environment specified in the request body. If set to \`true\`, the \`downloadFile\` property will let you download a file with the exported data. Otherwise, the export data is returned directly as JSON. Refer to the documentation for more information about [Unleash's export functionality](https://docs.getunleash.io/reference/deploy/environment-import-export#export).", "operationId": "exportFeatures", "requestBody": { "content": { @@ -8571,15 +8705,43 @@ If the provided project does not exist, the list of events will be empty.", }, "description": "exportResultSchema", }, + "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": "Export feature toggles from an environment", "tags": [ - "Unstable", + "Import/Export", ], }, }, "/api/admin/features-batch/import": { "post": { - "description": "Unleash toggles exported from a different instance can be imported into a new project and environment", + "description": "[Import feature toggles](https://docs.getunleash.io/reference/deploy/environment-import-export#import) into a specific project and environment.", "operationId": "importToggles", "requestBody": { "content": { @@ -8596,16 +8758,43 @@ If the provided project does not exist, the list of events will be empty.", "200": { "description": "This response has no body.", }, + "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": "Import feature toggles for an environment in the project", + "summary": "Import feature toggles", "tags": [ - "Unstable", + "Import/Export", ], }, }, "/api/admin/features-batch/validate": { "post": { - "description": "Unleash toggles exported from a different instance can be imported into a new project and environment", + "description": "Validates a feature toggle data set. Checks whether the data can be imported into the specified project and environment. The returned value is an object that contains errors, warnings, and permissions required to perform the import, as described in the [import documentation](https://docs.getunleash.io/reference/deploy/environment-import-export#import).", "operationId": "validateImport", "requestBody": { "content": { @@ -8629,10 +8818,37 @@ If the provided project does not exist, the list of events will be empty.", }, "description": "importTogglesValidateSchema", }, + "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": "Validate import of feature toggles for an environment in the project", + "summary": "Validate feature import data", "tags": [ - "Unstable", + "Import/Export", ], }, }, @@ -16431,6 +16647,10 @@ true,false,"[{""range"":""allTime"",""count"":15},{""range"":""30d"",""count"":9 "description": "Register, read, or delete metrics recorded by Unleash.", "name": "Metrics", }, + { + "description": "API for managing [notifications](https://docs.getunleash.io/reference/notifications).", + "name": "Notifications", + }, { "description": "Endpoints related to the operational status of this Unleash instance.", "name": "Operational",