mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
fix: feature OpenAPI endpoints - project related (#4212)
Update OpenAPI for `/api/admin/projects/{projectId}/features/` and related endpoints --------- Co-authored-by: Thomas Heartman <thomas@getunleash.ai>
This commit is contained in:
parent
99d63cff33
commit
f91c8a338a
@ -90,15 +90,8 @@ const metaRules: Rule[] = [
|
||||
},
|
||||
},
|
||||
knownExceptions: [
|
||||
'cloneFeatureSchema',
|
||||
'createFeatureSchema',
|
||||
'createInvitedUserSchema',
|
||||
'environmentsSchema',
|
||||
'environmentsProjectSchema',
|
||||
'featureEnvironmentSchema',
|
||||
'featuresSchema',
|
||||
'featureStrategySegmentSchema',
|
||||
'featureVariantsSchema',
|
||||
'groupSchema',
|
||||
'groupsSchema',
|
||||
'groupUserModelSchema',
|
||||
@ -112,9 +105,7 @@ const metaRules: Rule[] = [
|
||||
'sdkContextSchema',
|
||||
'setUiConfigSchema',
|
||||
'stateSchema',
|
||||
'strategiesSchema',
|
||||
'uiConfigSchema',
|
||||
'updateFeatureSchema',
|
||||
'upsertContextFieldSchema',
|
||||
'upsertStrategySchema',
|
||||
'usersGroupsBaseSchema',
|
||||
@ -137,31 +128,23 @@ const metaRules: Rule[] = [
|
||||
'adminFeaturesQuerySchema',
|
||||
'applicationSchema',
|
||||
'applicationsSchema',
|
||||
'cloneFeatureSchema',
|
||||
'createFeatureSchema',
|
||||
'createInvitedUserSchema',
|
||||
'dateSchema',
|
||||
'environmentsSchema',
|
||||
'featuresSchema',
|
||||
'featureStrategySegmentSchema',
|
||||
'featureVariantsSchema',
|
||||
'groupSchema',
|
||||
'groupsSchema',
|
||||
'groupUserModelSchema',
|
||||
'maintenanceSchema',
|
||||
'toggleMaintenanceSchema',
|
||||
'patchesSchema',
|
||||
'patchSchema',
|
||||
'playgroundSegmentSchema',
|
||||
'playgroundStrategySchema',
|
||||
'pushVariantsSchema',
|
||||
'resetPasswordSchema',
|
||||
'setStrategySortOrderSchema',
|
||||
'setUiConfigSchema',
|
||||
'sortOrderSchema',
|
||||
'strategiesSchema',
|
||||
'uiConfigSchema',
|
||||
'updateFeatureSchema',
|
||||
'upsertContextFieldSchema',
|
||||
'upsertStrategySchema',
|
||||
'usersGroupsBaseSchema',
|
||||
|
@ -4,12 +4,18 @@ export const cloneFeatureSchema = {
|
||||
$id: '#/components/schemas/cloneFeatureSchema',
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
description: 'Copy of a feature with a new name',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'The name of the new feature',
|
||||
example: 'new-feature',
|
||||
},
|
||||
replaceGroupId: {
|
||||
type: 'boolean',
|
||||
example: true,
|
||||
description:
|
||||
'Whether to use the new feature name as its group ID or not. Group ID is used for calculating [stickiness](https://docs.getunleash.io/reference/stickiness#calculation). Defaults to true.',
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
|
@ -7,15 +7,27 @@ export const createFeatureSchema = {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
example: 'disable-comments',
|
||||
description: 'Unique feature name',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
example: 'release',
|
||||
description:
|
||||
"The feature toggle's [type](https://docs.getunleash.io/reference/feature-toggle-types). One of experiment, kill-switch, release, operational, or permission",
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
example:
|
||||
'Controls disabling of the comments section in case of an incident',
|
||||
description: 'Detailed description of the feature',
|
||||
},
|
||||
impressionData: {
|
||||
type: 'boolean',
|
||||
example: false,
|
||||
description:
|
||||
'`true` if the impression data collection is enabled for the feature, otherwise `false`.',
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
|
@ -26,6 +26,7 @@ export const createStrategyVariantSchema = {
|
||||
'Set to `fix` if this variant must have exactly the weight allocated to it. If the type is `variable`, the weight will adjust so that the total weight of all variants adds up to 1000. Refer to the [variant weight documentation](https://docs.getunleash.io/reference/feature-toggle-variants#variant-weight).',
|
||||
type: 'string',
|
||||
example: 'fix',
|
||||
enum: ['variable', 'fix'],
|
||||
},
|
||||
stickiness: {
|
||||
type: 'string',
|
||||
@ -48,7 +49,7 @@ export const createStrategyVariantSchema = {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
example: { type: 'json', value: '{color: red}' },
|
||||
example: { type: 'json', value: '{"color": "red"}' },
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
|
@ -10,12 +10,15 @@ export const environmentsProjectSchema = {
|
||||
properties: {
|
||||
version: {
|
||||
type: 'integer',
|
||||
example: 1,
|
||||
description: 'Version of the environments schema',
|
||||
},
|
||||
environments: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/environmentProjectSchema',
|
||||
},
|
||||
description: 'List of environments',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
@ -6,15 +6,19 @@ export const environmentsSchema = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['version', 'environments'],
|
||||
description: 'A versioned list of environments',
|
||||
properties: {
|
||||
version: {
|
||||
type: 'integer',
|
||||
example: 1,
|
||||
description: 'Version of the environments schema',
|
||||
},
|
||||
environments: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/environmentSchema',
|
||||
},
|
||||
description: 'List of environments',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
@ -20,9 +20,12 @@ export const featureEnvironmentSchema = {
|
||||
featureName: {
|
||||
type: 'string',
|
||||
example: 'disable-comments',
|
||||
description: 'The name of the feature',
|
||||
},
|
||||
environment: {
|
||||
type: 'string',
|
||||
example: 'development',
|
||||
description: 'The name of the environment',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
|
@ -84,7 +84,7 @@ test('featureSchema variant override values must be an array', () => {
|
||||
{
|
||||
name: 'a',
|
||||
weight: 1,
|
||||
weightType: 'a',
|
||||
weightType: 'fix',
|
||||
stickiness: 'a',
|
||||
overrides: [{ contextName: 'a', values: 'b' }],
|
||||
payload: { type: 'a', value: 'b' },
|
||||
|
@ -7,15 +7,19 @@ export const featureVariantsSchema = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['version', 'variants'],
|
||||
description: 'A versioned collection of feature toggle variants.',
|
||||
properties: {
|
||||
version: {
|
||||
type: 'integer',
|
||||
example: 1,
|
||||
description: 'The version of the feature variants schema.',
|
||||
},
|
||||
variants: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/variantSchema',
|
||||
},
|
||||
description: 'All variants defined for a specific feature toggle.',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
@ -14,15 +14,19 @@ export const featuresSchema = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['version', 'features'],
|
||||
description: 'A list of features',
|
||||
deprecated: true,
|
||||
properties: {
|
||||
version: {
|
||||
type: 'integer',
|
||||
description: "The version of the feature's schema",
|
||||
},
|
||||
features: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/featureSchema',
|
||||
},
|
||||
description: 'A list of features',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
@ -4,6 +4,7 @@ import { patchSchema } from './patch-schema';
|
||||
export const patchesSchema = {
|
||||
$id: '#/components/schemas/patchesSchema',
|
||||
type: 'array',
|
||||
description: 'A list of patches',
|
||||
items: {
|
||||
$ref: '#/components/schemas/patchSchema',
|
||||
},
|
||||
|
@ -46,7 +46,7 @@ export const proxyFeatureSchema = {
|
||||
additionalProperties: false,
|
||||
required: ['type', 'value'],
|
||||
description: 'Extra data configured for this variant',
|
||||
example: { type: 'json', value: '{color: red}' },
|
||||
example: { type: 'json', value: '{"color": "red"}' },
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
|
@ -3,16 +3,22 @@ import { FromSchema } from 'json-schema-to-ts';
|
||||
export const setStrategySortOrderSchema = {
|
||||
$id: '#/components/schemas/setStrategySortOrderSchema',
|
||||
type: 'array',
|
||||
description: 'An array of strategies with their new sort order',
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['id', 'sortOrder'],
|
||||
description: 'A strategy with its new sort order',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
example: '9c40958a-daac-400e-98fb-3bb438567008',
|
||||
description: 'The ID of the strategy',
|
||||
},
|
||||
sortOrder: {
|
||||
type: 'number',
|
||||
example: 1,
|
||||
description: 'The new sort order of the strategy',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -6,17 +6,20 @@ export const strategiesSchema = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['version', 'strategies'],
|
||||
description: 'List of strategies',
|
||||
properties: {
|
||||
version: {
|
||||
type: 'integer',
|
||||
enum: [1],
|
||||
example: 1,
|
||||
description: 'Version of the strategies schema',
|
||||
},
|
||||
strategies: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/strategySchema',
|
||||
},
|
||||
description: 'List of strategies',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
@ -4,35 +4,36 @@ import { constraintSchema } from './constraint-schema';
|
||||
export const updateFeatureSchema = {
|
||||
$id: '#/components/schemas/updateFeatureSchema',
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
description: 'Data used for updating a feature toggle',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
example:
|
||||
'Controls disabling of the comments section in case of an incident',
|
||||
description: 'Detailed description of the feature',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
example: 'kill-switch',
|
||||
description:
|
||||
'Type of the toggle e.g. experiment, kill-switch, release, operational, permission',
|
||||
},
|
||||
stale: {
|
||||
type: 'boolean',
|
||||
example: true,
|
||||
description: '`true` if the feature is archived',
|
||||
},
|
||||
archived: {
|
||||
type: 'boolean',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
example: true,
|
||||
description:
|
||||
'If `true` the feature toggle will be moved to the [archive](https://docs.getunleash.io/reference/archived-toggles) with a property `archivedAt` set to current time',
|
||||
},
|
||||
impressionData: {
|
||||
type: 'boolean',
|
||||
},
|
||||
constraints: {
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: '#/components/schemas/constraintSchema',
|
||||
},
|
||||
example: false,
|
||||
description:
|
||||
'`true` if the impression data collection is enabled for the feature',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
@ -25,7 +25,8 @@ export const variantSchema = {
|
||||
description:
|
||||
'Set to fix if this variant must have exactly the weight allocated to it. If the type is variable, the weight will adjust so that the total weight of all variants adds up to 1000',
|
||||
type: 'string',
|
||||
example: 'fix',
|
||||
example: 'variable',
|
||||
enum: ['variable', 'fix'],
|
||||
},
|
||||
stickiness: {
|
||||
type: 'string',
|
||||
@ -48,7 +49,7 @@ export const variantSchema = {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
example: { type: 'json', value: '{color: red}' },
|
||||
example: { type: 'json', value: '{"color": "red"}' },
|
||||
},
|
||||
overrides: {
|
||||
description: `Overrides assigning specific variants to specific users. The weighting system automatically assigns users to specific groups for you, but any overrides in this list will take precedence.`,
|
||||
|
@ -69,7 +69,7 @@ class FeatureController extends Controller {
|
||||
200: createResponseSchema('featuresSchema'),
|
||||
...getStandardResponses(401, 403),
|
||||
},
|
||||
summary: 'Get all features (deprecated)',
|
||||
summary: 'Get all feature toggles (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/<project-id>/features`).',
|
||||
deprecated: true,
|
||||
@ -86,7 +86,7 @@ class FeatureController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
operationId: 'validateFeature',
|
||||
summary: 'Validate feature name',
|
||||
summary: 'Validate a feature toggle 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.',
|
||||
|
@ -52,6 +52,7 @@ import {
|
||||
TransactionCreator,
|
||||
UnleashTransaction,
|
||||
} from '../../../db/transaction';
|
||||
import { BadDataError } from '../../../error';
|
||||
|
||||
interface FeatureStrategyParams {
|
||||
projectId: string;
|
||||
@ -152,7 +153,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
handler: this.getFeatureEnvironment,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
summary: 'Get a feature environment.',
|
||||
summary: 'Get a feature environment',
|
||||
description:
|
||||
'Information about the enablement status and strategies for a feature toggle in specified environment.',
|
||||
tags: ['Features'],
|
||||
@ -173,7 +174,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
permission: UPDATE_FEATURE_ENVIRONMENT,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
summary: 'Disable a feature toggle.',
|
||||
summary: 'Disable a feature toggle',
|
||||
description:
|
||||
'Disable a feature toggle in the specified environment.',
|
||||
tags: ['Features'],
|
||||
@ -194,7 +195,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
permission: UPDATE_FEATURE_ENVIRONMENT,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
summary: 'Enable a feature toggle.',
|
||||
summary: 'Enable a feature toggle',
|
||||
description:
|
||||
'Enable a feature toggle in the specified environment.',
|
||||
tags: ['Features'],
|
||||
@ -215,9 +216,9 @@ export default class ProjectFeaturesController extends Controller {
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
summary: 'Bulk enable a list of features',
|
||||
description:
|
||||
'This endpoint enables multiple feature toggles.',
|
||||
summary: 'Bulk enable a list of features.',
|
||||
operationId: 'bulkToggleFeaturesEnvironmentOn',
|
||||
requestBody: createRequestSchema(
|
||||
'bulkToggleFeaturesSchema',
|
||||
@ -238,9 +239,9 @@ export default class ProjectFeaturesController extends Controller {
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
summary: 'Bulk disable a list of features',
|
||||
description:
|
||||
'This endpoint disables multiple feature toggles.',
|
||||
summary: 'Bulk disabled a list of features.',
|
||||
operationId: 'bulkToggleFeaturesEnvironmentOff',
|
||||
requestBody: createRequestSchema(
|
||||
'bulkToggleFeaturesSchema',
|
||||
@ -261,7 +262,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
summary: 'Get feature toggle strategies.',
|
||||
summary: 'Get feature toggle strategies',
|
||||
operationId: 'getFeatureStrategies',
|
||||
description:
|
||||
'Get strategies defined for a feature toggle in the specified environment.',
|
||||
@ -281,7 +282,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
summary: 'Add a strategy to a feature toggle.',
|
||||
summary: 'Add a strategy to a feature toggle',
|
||||
description:
|
||||
'Add a strategy to a feature toggle in the specified environment.',
|
||||
operationId: 'addFeatureStrategy',
|
||||
@ -304,12 +305,13 @@ export default class ProjectFeaturesController extends Controller {
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
summary: 'Get a strategy configuration.',
|
||||
summary: 'Get a strategy configuration',
|
||||
description:
|
||||
'Get a strategy configuration for an environment in a feature toggle.',
|
||||
operationId: 'getFeatureStrategy',
|
||||
responses: {
|
||||
200: createResponseSchema('featureStrategySchema'),
|
||||
...getStandardResponses(401, 403, 404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -323,14 +325,14 @@ export default class ProjectFeaturesController extends Controller {
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
summary: 'Set the order of strategies on the list.',
|
||||
summary: 'Set the order of strategies on the list',
|
||||
operationId: 'setStrategySortOrder',
|
||||
requestBody: createRequestSchema(
|
||||
'setStrategySortOrderSchema',
|
||||
),
|
||||
responses: {
|
||||
200: emptyResponse,
|
||||
...getStandardResponses(401, 403),
|
||||
...getStandardResponses(400, 401, 403),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -344,7 +346,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
summary: 'Update a strategy.',
|
||||
summary: 'Update a strategy',
|
||||
description:
|
||||
'Replace strategy configuration for a feature toggle in the specified environment.',
|
||||
operationId: 'updateFeatureStrategy',
|
||||
@ -353,6 +355,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
),
|
||||
responses: {
|
||||
200: createResponseSchema('featureStrategySchema'),
|
||||
...getStandardResponses(400, 401, 403, 404, 415),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -366,13 +369,14 @@ export default class ProjectFeaturesController extends Controller {
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
summary: 'Change specific properties of a strategy.',
|
||||
summary: 'Change specific properties of a strategy',
|
||||
description:
|
||||
'Change specific properties of a strategy configuration in a feature toggle.',
|
||||
operationId: 'patchFeatureStrategy',
|
||||
requestBody: createRequestSchema('patchesSchema'),
|
||||
responses: {
|
||||
200: createResponseSchema('featureStrategySchema'),
|
||||
...getStandardResponses(400, 401, 403, 404, 415),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -387,7 +391,7 @@ export default class ProjectFeaturesController extends Controller {
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
summary: 'Delete a strategy from a feature toggle.',
|
||||
summary: 'Delete a strategy from a feature toggle',
|
||||
description:
|
||||
'Delete a strategy configuration from a feature toggle in the specified environment.',
|
||||
operationId: 'deleteFeatureStrategy',
|
||||
@ -406,9 +410,15 @@ export default class ProjectFeaturesController extends Controller {
|
||||
permission: NONE,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
summary: 'Get all features in a project',
|
||||
description:
|
||||
'A list of all features for the specified project.',
|
||||
tags: ['Features'],
|
||||
operationId: 'getFeatures',
|
||||
responses: { 200: createResponseSchema('featuresSchema') },
|
||||
responses: {
|
||||
200: createResponseSchema('featuresSchema'),
|
||||
...getStandardResponses(400, 401, 403),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -420,10 +430,16 @@ export default class ProjectFeaturesController extends Controller {
|
||||
permission: CREATE_FEATURE,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
summary: 'Add a new feature toggle',
|
||||
description:
|
||||
'Create a new feature toggle in a specified project.',
|
||||
tags: ['Features'],
|
||||
operationId: 'createFeature',
|
||||
requestBody: createRequestSchema('createFeatureSchema'),
|
||||
responses: { 200: createResponseSchema('featureSchema') },
|
||||
responses: {
|
||||
200: createResponseSchema('featureSchema'),
|
||||
...getStandardResponses(401, 403, 404, 415),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -436,10 +452,16 @@ export default class ProjectFeaturesController extends Controller {
|
||||
permission: CREATE_FEATURE,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
summary: 'Clone a feature toggle',
|
||||
description:
|
||||
'Creates a copy of the specified feature toggle. The copy can be created in any project.',
|
||||
tags: ['Features'],
|
||||
operationId: 'cloneFeature',
|
||||
requestBody: createRequestSchema('cloneFeatureSchema'),
|
||||
responses: { 200: createResponseSchema('featureSchema') },
|
||||
responses: {
|
||||
200: createResponseSchema('featureSchema'),
|
||||
...getStandardResponses(401, 403, 404, 415),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -453,16 +475,16 @@ export default class ProjectFeaturesController extends Controller {
|
||||
openApiService.validPath({
|
||||
operationId: 'getFeature',
|
||||
tags: ['Features'],
|
||||
summary: 'Get a feature',
|
||||
description:
|
||||
'This endpoint returns the information about the requested feature if the feature belongs to the specified project.',
|
||||
summary: 'Get a feature.',
|
||||
responses: {
|
||||
200: createResponseSchema('featureSchema'),
|
||||
403: {
|
||||
description:
|
||||
'You either do not have the required permissions or used an invalid URL.',
|
||||
},
|
||||
...getStandardResponses(401, 404),
|
||||
...getStandardResponses(401, 403, 404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -477,8 +499,14 @@ export default class ProjectFeaturesController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
operationId: 'updateFeature',
|
||||
summary: 'Update a feature toggle',
|
||||
description:
|
||||
'Updates the specified feature if the feature belongs to the specified project. Only the provided properties are updated; any feature properties left out of the request body are left untouched.',
|
||||
requestBody: createRequestSchema('updateFeatureSchema'),
|
||||
responses: { 200: createResponseSchema('featureSchema') },
|
||||
responses: {
|
||||
200: createResponseSchema('featureSchema'),
|
||||
...getStandardResponses(401, 403, 404, 415),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -492,8 +520,14 @@ export default class ProjectFeaturesController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
operationId: 'patchFeature',
|
||||
summary: 'Modify a feature toggle',
|
||||
description:
|
||||
'Change specific properties of a feature toggle.',
|
||||
requestBody: createRequestSchema('patchesSchema'),
|
||||
responses: { 200: createResponseSchema('featureSchema') },
|
||||
responses: {
|
||||
200: createResponseSchema('featureSchema'),
|
||||
...getStandardResponses(401, 403, 404, 415),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -508,16 +542,16 @@ export default class ProjectFeaturesController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
operationId: 'archiveFeature',
|
||||
summary: 'Archive a feature toggle',
|
||||
description:
|
||||
'This endpoint archives the specified feature if the feature belongs to the specified project.',
|
||||
summary: 'Archive a feature.',
|
||||
responses: {
|
||||
202: emptyResponse,
|
||||
403: {
|
||||
description:
|
||||
'You either do not have the required permissions or used an invalid URL.',
|
||||
},
|
||||
...getStandardResponses(401, 404),
|
||||
...getStandardResponses(401, 403, 404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -552,8 +586,14 @@ export default class ProjectFeaturesController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['Tags'],
|
||||
operationId: 'addTagToFeatures',
|
||||
summary: 'Adds a tag to the specified features',
|
||||
description:
|
||||
'Add a tag to a list of features. Create tags if needed.',
|
||||
requestBody: createRequestSchema('tagsBulkAddSchema'),
|
||||
responses: { 200: emptyResponse },
|
||||
responses: {
|
||||
200: emptyResponse,
|
||||
...getStandardResponses(401, 403, 404, 415),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@ -640,7 +680,10 @@ export default class ProjectFeaturesController extends Controller {
|
||||
const userName = extractUsername(req);
|
||||
const created = await this.featureService.createFeatureToggle(
|
||||
projectId,
|
||||
req.body,
|
||||
{
|
||||
...req.body,
|
||||
description: req.body.description || undefined,
|
||||
},
|
||||
userName,
|
||||
);
|
||||
|
||||
@ -680,9 +723,15 @@ export default class ProjectFeaturesController extends Controller {
|
||||
const { projectId, featureName } = req.params;
|
||||
const { createdAt, ...data } = req.body;
|
||||
const userName = extractUsername(req);
|
||||
if (data.name && data.name !== featureName) {
|
||||
throw new BadDataError('Cannot change name of feature toggle');
|
||||
}
|
||||
const created = await this.featureService.updateFeatureToggle(
|
||||
projectId,
|
||||
data,
|
||||
{
|
||||
...data,
|
||||
name: featureName,
|
||||
},
|
||||
userName,
|
||||
featureName,
|
||||
);
|
||||
|
@ -20,6 +20,7 @@ import { AccessService } from '../../../services';
|
||||
import { BadDataError, PermissionError } from '../../../../lib/error';
|
||||
import { User } from 'lib/server-impl';
|
||||
import { PushVariantsSchema } from 'lib/openapi/spec/push-variants-schema';
|
||||
import { getStandardResponses } from '../../../openapi';
|
||||
|
||||
const PREFIX = '/:projectId/features/:featureName/variants';
|
||||
const ENV_PREFIX =
|
||||
@ -73,6 +74,7 @@ export default class VariantsController extends Controller {
|
||||
operationId: 'getFeatureVariants',
|
||||
responses: {
|
||||
200: createResponseSchema('featureVariantsSchema'),
|
||||
...getStandardResponses(401, 403, 404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -87,13 +89,14 @@ export default class VariantsController extends Controller {
|
||||
summary:
|
||||
"Apply a patch to a feature's variants (in all environments).",
|
||||
description: `Apply a list of patches patch to the specified feature's variants. The patch objects should conform to the [JSON-patch format (RFC 6902)](https://www.rfc-editor.org/rfc/rfc6902).
|
||||
|
||||
⚠️ **Warning**: This method is not atomic. If something fails in the middle of applying the patch, you can be left with a half-applied patch. We recommend that you instead [patch variants on a per-environment basis](/docs/reference/api/unleash/patch-environments-feature-variants.api.mdx), which **is** an atomic operation.`,
|
||||
|
||||
⚠️ **Warning**: This method is not atomic. If something fails in the middle of applying the patch, you can be left with a half-applied patch. We recommend that you instead [patch variants on a per-environment basis](/docs/reference/api/unleash/patch-environments-feature-variants.api.mdx), which **is** an atomic operation.`,
|
||||
tags: ['Features'],
|
||||
operationId: 'patchFeatureVariants',
|
||||
requestBody: createRequestSchema('patchesSchema'),
|
||||
responses: {
|
||||
200: createResponseSchema('featureVariantsSchema'),
|
||||
...getStandardResponses(400, 401, 403, 404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -109,17 +112,18 @@ export default class VariantsController extends Controller {
|
||||
'Create (overwrite) variants for a feature toggle in all environments',
|
||||
description: `This overwrites the current variants for the feature specified in the :featureName parameter in all environments.
|
||||
|
||||
The backend will validate the input for the following invariants
|
||||
The backend will validate the input for the following invariants
|
||||
|
||||
* If there are variants, there needs to be at least one variant with \`weightType: variable\`
|
||||
* The sum of the weights of variants with \`weightType: fix\` must be strictly less than 1000 (< 1000)
|
||||
* If there are variants, there needs to be at least one variant with \`weightType: variable\`
|
||||
* The sum of the weights of variants with \`weightType: fix\` must be strictly less than 1000 (< 1000)
|
||||
|
||||
The backend will also distribute remaining weight up to 1000 after adding the variants with \`weightType: fix\` together amongst the variants of \`weightType: variable\``,
|
||||
The backend will also distribute remaining weight up to 1000 after adding the variants with \`weightType: fix\` together amongst the variants of \`weightType: variable\``,
|
||||
tags: ['Features'],
|
||||
operationId: 'overwriteFeatureVariants',
|
||||
requestBody: createRequestSchema('variantsSchema'),
|
||||
responses: {
|
||||
200: createResponseSchema('featureVariantsSchema'),
|
||||
...getStandardResponses(400, 401, 403, 404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -137,6 +141,7 @@ export default class VariantsController extends Controller {
|
||||
operationId: 'getEnvironmentFeatureVariants',
|
||||
responses: {
|
||||
200: createResponseSchema('featureVariantsSchema'),
|
||||
...getStandardResponses(401, 403, 404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -155,6 +160,7 @@ export default class VariantsController extends Controller {
|
||||
requestBody: createRequestSchema('patchesSchema'),
|
||||
responses: {
|
||||
200: createResponseSchema('featureVariantsSchema'),
|
||||
...getStandardResponses(400, 401, 403, 404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -169,18 +175,19 @@ export default class VariantsController extends Controller {
|
||||
summary:
|
||||
'Create (overwrite) variants for a feature in an environment',
|
||||
description: `This overwrites the current variants for the feature toggle in the :featureName parameter for the :environment parameter.
|
||||
|
||||
The backend will validate the input for the following invariants:
|
||||
|
||||
* If there are variants, there needs to be at least one variant with \`weightType: variable\`
|
||||
* The sum of the weights of variants with \`weightType: fix\` must be strictly less than 1000 (< 1000)
|
||||
|
||||
The backend will also distribute remaining weight up to 1000 after adding the variants with \`weightType: fix\` together amongst the variants of \`weightType: variable\``,
|
||||
The backend will validate the input for the following invariants:
|
||||
|
||||
* If there are variants, there needs to be at least one variant with \`weightType: variable\`
|
||||
* The sum of the weights of variants with \`weightType: fix\` must be strictly less than 1000 (< 1000)
|
||||
|
||||
The backend will also distribute remaining weight up to 1000 after adding the variants with \`weightType: fix\` together amongst the variants of \`weightType: variable\``,
|
||||
tags: ['Features'],
|
||||
operationId: 'overwriteEnvironmentFeatureVariants',
|
||||
requestBody: createRequestSchema('variantsSchema'),
|
||||
responses: {
|
||||
200: createResponseSchema('featureVariantsSchema'),
|
||||
...getStandardResponses(400, 401, 403),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -194,9 +201,14 @@ export default class VariantsController extends Controller {
|
||||
openApiService.validPath({
|
||||
tags: ['Features'],
|
||||
operationId: 'overwriteFeatureVariantsOnEnvironments',
|
||||
summary:
|
||||
'Create (overwrite) variants for a feature toggle in multiple environments',
|
||||
description:
|
||||
'This overwrites the current variants for the feature toggle in the :featureName parameter for the :environment parameter.',
|
||||
requestBody: createRequestSchema('pushVariantsSchema'),
|
||||
responses: {
|
||||
200: createResponseSchema('featureVariantsSchema'),
|
||||
...getStandardResponses(400, 401, 403),
|
||||
},
|
||||
}),
|
||||
],
|
||||
@ -230,7 +242,7 @@ export default class VariantsController extends Controller {
|
||||
);
|
||||
res.status(200).json({
|
||||
version: 1,
|
||||
variants: updatedFeature.variants,
|
||||
variants: updatedFeature.variants || [],
|
||||
});
|
||||
}
|
||||
|
||||
@ -248,7 +260,7 @@ export default class VariantsController extends Controller {
|
||||
);
|
||||
res.status(200).json({
|
||||
version: 1,
|
||||
variants: updatedFeature.variants,
|
||||
variants: updatedFeature.variants || [],
|
||||
});
|
||||
}
|
||||
|
||||
@ -275,7 +287,7 @@ export default class VariantsController extends Controller {
|
||||
UPDATE_FEATURE_ENVIRONMENT_VARIANTS,
|
||||
);
|
||||
|
||||
const variantsWithDefaults = variants.map((variant) => ({
|
||||
const variantsWithDefaults = (variants || []).map((variant) => ({
|
||||
weightType: WeightType.VARIABLE,
|
||||
stickiness: 'default',
|
||||
...variant,
|
||||
|
@ -1127,6 +1127,7 @@ class FeatureToggleService {
|
||||
segments: [],
|
||||
title: strategy.title,
|
||||
disabled: strategy.disabled,
|
||||
// FIXME: Should we return sortOrder here, or adjust OpenAPI?
|
||||
};
|
||||
|
||||
if (segments && segments.length > 0) {
|
||||
|
@ -120,7 +120,7 @@ export interface IFeatureEnvironment {
|
||||
export interface IVariant {
|
||||
name: string;
|
||||
weight: number;
|
||||
weightType: string;
|
||||
weightType: 'variable' | 'fix';
|
||||
payload?: {
|
||||
type: string;
|
||||
value: string;
|
||||
|
@ -663,7 +663,7 @@ describe('Playground API E2E', () => {
|
||||
{
|
||||
name: 'a',
|
||||
weight: 1000,
|
||||
weightType: 'variable',
|
||||
weightType: 'variable' as const,
|
||||
stickiness: 'default',
|
||||
overrides: [],
|
||||
},
|
||||
|
@ -2370,7 +2370,7 @@ test('should handle strategy variants', async () => {
|
||||
const variant = {
|
||||
name: 'variantName',
|
||||
weight: 1,
|
||||
weightType: 'variable',
|
||||
weightType: 'variable' as const,
|
||||
stickiness: 'default',
|
||||
};
|
||||
const updatedVariant1 = {
|
||||
|
@ -179,7 +179,7 @@ test('Can patch variants for a feature patches all environments independently',
|
||||
path: '/1',
|
||||
value: {
|
||||
name: addedVariantName,
|
||||
weightType: 'fix',
|
||||
weightType: WeightType.FIX,
|
||||
weight: 50,
|
||||
},
|
||||
},
|
||||
@ -509,7 +509,7 @@ test('PUTing an invalid variant throws 400 exception', async () => {
|
||||
.expect((res) => {
|
||||
expect(res.body.details).toHaveLength(1);
|
||||
expect(res.body.details[0].description).toMatch(
|
||||
/.*weightType" must be one of/,
|
||||
/.*weightType property should be equal to one of the allowed values/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1002,13 +1002,13 @@ test('PUT endpoint validates uniqueness of variant names', async () => {
|
||||
.send([
|
||||
{
|
||||
name: 'variant1',
|
||||
weightType: 'variable',
|
||||
weightType: WeightType.VARIABLE,
|
||||
weight: 500,
|
||||
stickiness: 'default',
|
||||
},
|
||||
{
|
||||
name: 'variant1',
|
||||
weightType: 'variable',
|
||||
weightType: WeightType.VARIABLE,
|
||||
weight: 500,
|
||||
stickiness: 'default',
|
||||
},
|
||||
@ -1038,25 +1038,25 @@ test('Variants should be sorted by their name when PUT', async () => {
|
||||
.send([
|
||||
{
|
||||
name: 'zvariant',
|
||||
weightType: 'variable',
|
||||
weightType: WeightType.VARIABLE,
|
||||
weight: 500,
|
||||
stickiness: 'default',
|
||||
},
|
||||
{
|
||||
name: 'variant-a',
|
||||
weightType: 'variable',
|
||||
weightType: WeightType.VARIABLE,
|
||||
weight: 500,
|
||||
stickiness: 'default',
|
||||
},
|
||||
{
|
||||
name: 'g-variant',
|
||||
weightType: 'variable',
|
||||
weightType: WeightType.VARIABLE,
|
||||
weight: 500,
|
||||
stickiness: 'default',
|
||||
},
|
||||
{
|
||||
name: 'variant-g',
|
||||
weightType: 'variable',
|
||||
weightType: WeightType.VARIABLE,
|
||||
weight: 500,
|
||||
stickiness: 'default',
|
||||
},
|
||||
@ -1086,13 +1086,13 @@ test('Variants should be sorted by name when PATCHed as well', async () => {
|
||||
const observer = jsonpatch.observe(variants);
|
||||
variants.push({
|
||||
name: 'g-variant',
|
||||
weightType: 'variable',
|
||||
weightType: WeightType.VARIABLE,
|
||||
weight: 500,
|
||||
stickiness: 'default',
|
||||
});
|
||||
variants.push({
|
||||
name: 'a-variant',
|
||||
weightType: 'variable',
|
||||
weightType: WeightType.VARIABLE,
|
||||
weight: 500,
|
||||
stickiness: 'default',
|
||||
});
|
||||
@ -1107,13 +1107,13 @@ test('Variants should be sorted by name when PATCHed as well', async () => {
|
||||
});
|
||||
variants.push({
|
||||
name: '00-variant',
|
||||
weightType: 'variable',
|
||||
weightType: WeightType.VARIABLE,
|
||||
weight: 500,
|
||||
stickiness: 'default',
|
||||
});
|
||||
variants.push({
|
||||
name: 'z-variant',
|
||||
weightType: 'variable',
|
||||
weightType: WeightType.VARIABLE,
|
||||
weight: 500,
|
||||
stickiness: 'default',
|
||||
});
|
||||
|
@ -564,7 +564,7 @@ test('If CRs are protected for any environment in the project stops bulk update
|
||||
{
|
||||
name: 'cr-enabled-2',
|
||||
weight: 500,
|
||||
weightType: 'fix',
|
||||
weightType: 'fix' as const,
|
||||
stickiness: 'default',
|
||||
},
|
||||
];
|
||||
|
Loading…
Reference in New Issue
Block a user