mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-09 01:17:06 +02:00
open-api addon controller (#1721)
* open-api addon controller * bug fixes * bug fixes * resolve merge conflict * bug fix * bug fix * bug fix * PR comments * PR comments * Resolve merge conflics * Resolve merge conflics * bug and tests
This commit is contained in:
parent
b3320bf74b
commit
66452e2860
@ -49,6 +49,10 @@ import { validateTagTypeSchema } from './spec/validate-tag-type-schema';
|
|||||||
import { variantSchema } from './spec/variant-schema';
|
import { variantSchema } from './spec/variant-schema';
|
||||||
import { variantsSchema } from './spec/variants-schema';
|
import { variantsSchema } from './spec/variants-schema';
|
||||||
import { versionSchema } from './spec/version-schema';
|
import { versionSchema } from './spec/version-schema';
|
||||||
|
import { addonSchema } from './spec/addon-schema';
|
||||||
|
import { addonsSchema } from './spec/addons-schema';
|
||||||
|
import { addonParameterSchema } from './spec/addon-parameter-schema';
|
||||||
|
import { addonTypeSchema } from './spec/addon-type-schema';
|
||||||
import { applicationSchema } from './spec/application-schema';
|
import { applicationSchema } from './spec/application-schema';
|
||||||
import { applicationsSchema } from './spec/applications-schema';
|
import { applicationsSchema } from './spec/applications-schema';
|
||||||
import { tagWithVersionSchema } from './spec/tag-with-version-schema';
|
import { tagWithVersionSchema } from './spec/tag-with-version-schema';
|
||||||
@ -60,6 +64,10 @@ import { exportParametersSchema } from './spec/export-parameters-schema';
|
|||||||
|
|
||||||
// All schemas in `openapi/spec` should be listed here.
|
// All schemas in `openapi/spec` should be listed here.
|
||||||
export const schemas = {
|
export const schemas = {
|
||||||
|
addonSchema,
|
||||||
|
addonsSchema,
|
||||||
|
addonTypeSchema,
|
||||||
|
addonParameterSchema,
|
||||||
apiTokenSchema,
|
apiTokenSchema,
|
||||||
apiTokensSchema,
|
apiTokensSchema,
|
||||||
applicationSchema,
|
applicationSchema,
|
||||||
|
33
src/lib/openapi/spec/addon-parameter-schema.ts
Normal file
33
src/lib/openapi/spec/addon-parameter-schema.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const addonParameterSchema = {
|
||||||
|
$id: '#/components/schemas/addonParameterSchema',
|
||||||
|
type: 'object',
|
||||||
|
required: ['name', 'displayName', 'type', 'required', 'sensitive'],
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
displayName: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
sensitive: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type AddonParameterSchema = FromSchema<typeof addonParameterSchema>;
|
17
src/lib/openapi/spec/addon-schema.test.ts
Normal file
17
src/lib/openapi/spec/addon-schema.test.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { validateSchema } from '../validate';
|
||||||
|
import { AddonSchema } from './addon-schema';
|
||||||
|
|
||||||
|
test('addonSchema', () => {
|
||||||
|
const data: AddonSchema = {
|
||||||
|
provider: 'some-provider',
|
||||||
|
enabled: true,
|
||||||
|
parameters: {
|
||||||
|
someKey: 'some-value',
|
||||||
|
},
|
||||||
|
events: ['some-event'],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/addonSchema', data),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
39
src/lib/openapi/spec/addon-schema.ts
Normal file
39
src/lib/openapi/spec/addon-schema.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const addonSchema = {
|
||||||
|
$id: '#/components/schemas/addonSchema',
|
||||||
|
type: 'object',
|
||||||
|
required: ['provider', 'enabled', 'parameters', 'events'],
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'date-time',
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
|
provider: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: true,
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type AddonSchema = FromSchema<typeof addonSchema>;
|
49
src/lib/openapi/spec/addon-type-schema.ts
Normal file
49
src/lib/openapi/spec/addon-type-schema.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
import { addonParameterSchema } from './addon-parameter-schema';
|
||||||
|
import { tagTypeSchema } from './tag-type-schema';
|
||||||
|
|
||||||
|
export const addonTypeSchema = {
|
||||||
|
$id: '#/components/schemas/addonTypeSchema',
|
||||||
|
type: 'object',
|
||||||
|
required: ['name', 'displayName', 'documentationUrl', 'description'],
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
displayName: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
documentationUrl: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
tagTypes: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/tagTypeSchema',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/addonParameterSchema',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
schemas: {
|
||||||
|
tagTypeSchema,
|
||||||
|
addonParameterSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type AddonTypeSchema = FromSchema<typeof addonTypeSchema>;
|
36
src/lib/openapi/spec/addons-schema.test.ts
Normal file
36
src/lib/openapi/spec/addons-schema.test.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { validateSchema } from '../validate';
|
||||||
|
import { AddonsSchema } from './addons-schema';
|
||||||
|
|
||||||
|
test('addonsSchema', () => {
|
||||||
|
const data: AddonsSchema = {
|
||||||
|
addons: [
|
||||||
|
{
|
||||||
|
parameters: { someKey: 'some-value' },
|
||||||
|
events: ['some-event'],
|
||||||
|
enabled: true,
|
||||||
|
provider: 'some-name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
name: 'some-name',
|
||||||
|
displayName: 'some-display-name',
|
||||||
|
documentationUrl: 'some-url',
|
||||||
|
description: 'some-description',
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
name: 'some-name',
|
||||||
|
displayName: 'some-display-name',
|
||||||
|
type: 'some-type',
|
||||||
|
required: true,
|
||||||
|
sensitive: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/addonsSchema', data),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
35
src/lib/openapi/spec/addons-schema.ts
Normal file
35
src/lib/openapi/spec/addons-schema.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
import { addonSchema } from './addon-schema';
|
||||||
|
import { addonTypeSchema } from './addon-type-schema';
|
||||||
|
import { addonParameterSchema } from './addon-parameter-schema';
|
||||||
|
import { tagTypeSchema } from './tag-type-schema';
|
||||||
|
|
||||||
|
export const addonsSchema = {
|
||||||
|
$id: '#/components/schemas/addonsSchema',
|
||||||
|
type: 'object',
|
||||||
|
required: ['addons', 'providers'],
|
||||||
|
properties: {
|
||||||
|
addons: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/addonSchema',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
providers: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/addonTypeSchema',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
schemas: {
|
||||||
|
addonSchema,
|
||||||
|
addonTypeSchema,
|
||||||
|
tagTypeSchema,
|
||||||
|
addonParameterSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type AddonsSchema = FromSchema<typeof addonsSchema>;
|
@ -1,79 +1,186 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import Controller from '../controller';
|
import Controller from '../controller';
|
||||||
import { IUnleashConfig } from '../../types/option';
|
import { IUnleashConfig, IUnleashServices } from '../../types';
|
||||||
import { IUnleashServices } from '../../types/services';
|
|
||||||
import { Logger } from '../../logger';
|
import { Logger } from '../../logger';
|
||||||
import AddonService from '../../services/addon-service';
|
import AddonService from '../../services/addon-service';
|
||||||
|
|
||||||
import { extractUsername } from '../../util/extract-user';
|
import { extractUsername } from '../../util/extract-user';
|
||||||
import {
|
import {
|
||||||
CREATE_ADDON,
|
CREATE_ADDON,
|
||||||
UPDATE_ADDON,
|
|
||||||
DELETE_ADDON,
|
DELETE_ADDON,
|
||||||
|
NONE,
|
||||||
|
UPDATE_ADDON,
|
||||||
} from '../../types/permissions';
|
} from '../../types/permissions';
|
||||||
import { IAuthRequest } from '../unleash-types';
|
import { IAuthRequest } from '../unleash-types';
|
||||||
|
import { createRequestSchema, createResponseSchema } from '../../openapi';
|
||||||
|
import { OpenApiService } from '../../services/openapi-service';
|
||||||
|
import { emptyResponse } from '../../openapi/spec/empty-response';
|
||||||
|
import { AddonSchema, addonSchema } from '../../openapi/spec/addon-schema';
|
||||||
|
import { serializeDates } from '../../types/serialize-dates';
|
||||||
|
import { AddonsSchema, addonsSchema } from '../../openapi/spec/addons-schema';
|
||||||
|
|
||||||
|
type AddonServices = Pick<IUnleashServices, 'addonService' | 'openApiService'>;
|
||||||
|
|
||||||
|
const PATH = '/';
|
||||||
|
|
||||||
class AddonController extends Controller {
|
class AddonController extends Controller {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
private addonService: AddonService;
|
private addonService: AddonService;
|
||||||
|
|
||||||
|
private openApiService: OpenApiService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
{ addonService }: Pick<IUnleashServices, 'addonService'>,
|
{ addonService, openApiService }: AddonServices,
|
||||||
) {
|
) {
|
||||||
super(config);
|
super(config);
|
||||||
this.logger = config.getLogger('/admin-api/addon.ts');
|
this.logger = config.getLogger('/admin-api/addon.ts');
|
||||||
this.addonService = addonService;
|
this.addonService = addonService;
|
||||||
|
this.openApiService = openApiService;
|
||||||
|
|
||||||
this.get('/', this.getAddons);
|
this.route({
|
||||||
this.post('/', this.createAddon, CREATE_ADDON);
|
method: 'get',
|
||||||
this.get('/:id', this.getAddon);
|
path: '',
|
||||||
this.put('/:id', this.updateAddon, UPDATE_ADDON);
|
permission: NONE,
|
||||||
this.delete('/:id', this.deleteAddon, DELETE_ADDON);
|
handler: this.getAddons,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'getAddons',
|
||||||
|
responses: {
|
||||||
|
200: createResponseSchema('addonsSchema'),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'post',
|
||||||
|
path: '',
|
||||||
|
handler: this.createAddon,
|
||||||
|
permission: CREATE_ADDON,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'createAddon',
|
||||||
|
requestBody: createRequestSchema('addonSchema'),
|
||||||
|
responses: { 200: createResponseSchema('addonSchema') },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'get',
|
||||||
|
path: `${PATH}:id`,
|
||||||
|
handler: this.getAddon,
|
||||||
|
permission: NONE,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'getAddon',
|
||||||
|
responses: { 200: createResponseSchema('addonSchema') },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'put',
|
||||||
|
path: `${PATH}:id`,
|
||||||
|
handler: this.updateAddon,
|
||||||
|
permission: UPDATE_ADDON,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'updateAddon',
|
||||||
|
requestBody: createRequestSchema('addonSchema'),
|
||||||
|
responses: { 200: createResponseSchema('addonSchema') },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'delete',
|
||||||
|
path: `${PATH}:id`,
|
||||||
|
handler: this.deleteAddon,
|
||||||
|
acceptAnyContentType: true,
|
||||||
|
permission: DELETE_ADDON,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'deleteAddon',
|
||||||
|
responses: { 200: emptyResponse },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAddons(req: Request, res: Response): Promise<void> {
|
async getAddons(req: Request, res: Response<AddonsSchema>): Promise<void> {
|
||||||
const addons = await this.addonService.getAddons();
|
const addons = await this.addonService.getAddons();
|
||||||
const providers = this.addonService.getProviderDefinitions();
|
const providers = this.addonService.getProviderDefinitions();
|
||||||
res.json({ addons, providers });
|
|
||||||
|
this.openApiService.respondWithValidation(200, res, addonsSchema.$id, {
|
||||||
|
addons: serializeDates(addons),
|
||||||
|
providers: serializeDates(providers),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAddon(
|
async getAddon(
|
||||||
req: Request<{ id: number }, any, any, any>,
|
req: Request<{ id: number }, any, any, any>,
|
||||||
res: Response,
|
res: Response<AddonSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const addon = await this.addonService.getAddon(id);
|
const addon = await this.addonService.getAddon(id);
|
||||||
res.json(addon);
|
this.openApiService.respondWithValidation(
|
||||||
|
200,
|
||||||
|
res,
|
||||||
|
addonSchema.$id,
|
||||||
|
serializeDates(addon),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAddon(
|
async updateAddon(
|
||||||
req: IAuthRequest<{ id: number }, any, any, any>,
|
req: IAuthRequest<{ id: number }, any, any, any>,
|
||||||
res: Response,
|
res: Response<AddonSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const createdBy = extractUsername(req);
|
const createdBy = extractUsername(req);
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
|
|
||||||
const addon = await this.addonService.updateAddon(id, data, createdBy);
|
const addon = await this.addonService.updateAddon(id, data, createdBy);
|
||||||
res.status(200).json(addon);
|
|
||||||
|
this.openApiService.respondWithValidation(
|
||||||
|
200,
|
||||||
|
res,
|
||||||
|
addonSchema.$id,
|
||||||
|
serializeDates(addon),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createAddon(req: IAuthRequest, res: Response): Promise<void> {
|
async createAddon(
|
||||||
|
req: IAuthRequest<AddonSchema, any, any, any>,
|
||||||
|
res: Response<AddonSchema>,
|
||||||
|
): Promise<void> {
|
||||||
const createdBy = extractUsername(req);
|
const createdBy = extractUsername(req);
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
const addon = await this.addonService.createAddon(data, createdBy);
|
const addon = await this.addonService.createAddon(data, createdBy);
|
||||||
res.status(201).json(addon);
|
|
||||||
|
this.openApiService.respondWithValidation(
|
||||||
|
201,
|
||||||
|
res,
|
||||||
|
addonSchema.$id,
|
||||||
|
serializeDates(addon),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteAddon(
|
async deleteAddon(
|
||||||
req: IAuthRequest<{ id: number }, any, any, any>,
|
req: IAuthRequest<{ id: number }, any, any, any>,
|
||||||
res: Response,
|
res: Response<void>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const username = extractUsername(req);
|
const username = extractUsername(req);
|
||||||
await this.addonService.removeAddon(id, username);
|
await this.addonService.removeAddon(id, username);
|
||||||
|
|
||||||
res.status(200).end();
|
res.status(200).end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,7 @@ import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
|
|||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
import TagTypeService from './tag-type-service';
|
import TagTypeService from './tag-type-service';
|
||||||
import { IAddon, IAddonDto, IAddonStore } from '../types/stores/addon-store';
|
import { IAddon, IAddonDto, IAddonStore } from '../types/stores/addon-store';
|
||||||
import { IUnleashStores } from '../types/stores';
|
import { IUnleashStores, IUnleashConfig } from '../types';
|
||||||
import { IUnleashConfig } from '../types/option';
|
|
||||||
import { IAddonDefinition } from '../types/model';
|
import { IAddonDefinition } from '../types/model';
|
||||||
import { minutesToMilliseconds } from 'date-fns';
|
import { minutesToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
@ -196,7 +195,7 @@ export default class AddonService {
|
|||||||
id: number,
|
id: number,
|
||||||
data: IAddonDto,
|
data: IAddonDto,
|
||||||
userName: string,
|
userName: string,
|
||||||
): Promise<void> {
|
): Promise<IAddon> {
|
||||||
const addonConfig = await addonSchema.validateAsync(data);
|
const addonConfig = await addonSchema.validateAsync(data);
|
||||||
await this.validateRequiredParameters(addonConfig);
|
await this.validateRequiredParameters(addonConfig);
|
||||||
if (this.sensitiveParams[addonConfig.provider].length > 0) {
|
if (this.sensitiveParams[addonConfig.provider].length > 0) {
|
||||||
@ -214,13 +213,14 @@ export default class AddonService {
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await this.addonStore.update(id, addonConfig);
|
const result = await this.addonStore.update(id, addonConfig);
|
||||||
await this.eventStore.store({
|
await this.eventStore.store({
|
||||||
type: events.ADDON_CONFIG_UPDATED,
|
type: events.ADDON_CONFIG_UPDATED,
|
||||||
createdBy: userName,
|
createdBy: userName,
|
||||||
data: { id, provider: addonConfig.provider },
|
data: { id, provider: addonConfig.provider },
|
||||||
});
|
});
|
||||||
this.logger.info(`User ${userName} updated addon ${id}`);
|
this.logger.info(`User ${userName} updated addon ${id}`);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeAddon(id: number, userName: string): Promise<void> {
|
async removeAddon(id: number, userName: string): Promise<void> {
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export * from './services';
|
export * from './services';
|
||||||
export * from './stores';
|
export * from './stores';
|
||||||
|
export * from './option';
|
||||||
|
@ -4,7 +4,7 @@ export interface IAddonDto {
|
|||||||
provider: string;
|
provider: string;
|
||||||
description: string;
|
description: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
parameters: object;
|
parameters: Record<string, unknown>;
|
||||||
events: string[];
|
events: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,139 @@ exports[`should serve the OpenAPI spec 1`] = `
|
|||||||
Object {
|
Object {
|
||||||
"components": Object {
|
"components": Object {
|
||||||
"schemas": Object {
|
"schemas": Object {
|
||||||
|
"addonParameterSchema": Object {
|
||||||
|
"properties": Object {
|
||||||
|
"description": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"displayName": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"name": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"placeholder": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"required": Object {
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
|
"sensitive": Object {
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
|
"type": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"name",
|
||||||
|
"displayName",
|
||||||
|
"type",
|
||||||
|
"required",
|
||||||
|
"sensitive",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
"addonSchema": Object {
|
||||||
|
"properties": Object {
|
||||||
|
"createdAt": Object {
|
||||||
|
"format": "date-time",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"description": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"enabled": Object {
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
|
"events": Object {
|
||||||
|
"items": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
|
"id": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
"parameters": Object {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
"provider": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"provider",
|
||||||
|
"enabled",
|
||||||
|
"parameters",
|
||||||
|
"events",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
"addonTypeSchema": Object {
|
||||||
|
"properties": Object {
|
||||||
|
"description": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"displayName": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"documentationUrl": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"events": Object {
|
||||||
|
"items": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
|
"name": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"parameters": Object {
|
||||||
|
"items": Object {
|
||||||
|
"$ref": "#/components/schemas/addonParameterSchema",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
|
"tagTypes": Object {
|
||||||
|
"items": Object {
|
||||||
|
"$ref": "#/components/schemas/tagTypeSchema",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"name",
|
||||||
|
"displayName",
|
||||||
|
"documentationUrl",
|
||||||
|
"description",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
"addonsSchema": Object {
|
||||||
|
"properties": Object {
|
||||||
|
"addons": Object {
|
||||||
|
"items": Object {
|
||||||
|
"$ref": "#/components/schemas/addonSchema",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
|
"providers": Object {
|
||||||
|
"items": Object {
|
||||||
|
"$ref": "#/components/schemas/addonTypeSchema",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"addons",
|
||||||
|
"providers",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
"apiTokenSchema": Object {
|
"apiTokenSchema": Object {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
@ -1497,6 +1630,145 @@ Object {
|
|||||||
},
|
},
|
||||||
"openapi": "3.0.3",
|
"openapi": "3.0.3",
|
||||||
"paths": Object {
|
"paths": Object {
|
||||||
|
"/api/admin/addons": Object {
|
||||||
|
"get": Object {
|
||||||
|
"operationId": "getAddons",
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/addonsSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "addonsSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"post": Object {
|
||||||
|
"operationId": "createAddon",
|
||||||
|
"requestBody": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/addonSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "addonSchema",
|
||||||
|
"required": true,
|
||||||
|
},
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/addonSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "addonSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/api/admin/addons/{id}": Object {
|
||||||
|
"delete": Object {
|
||||||
|
"operationId": "deleteAddon",
|
||||||
|
"parameters": Array [
|
||||||
|
Object {
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"description": "emptyResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"get": Object {
|
||||||
|
"operationId": "getAddon",
|
||||||
|
"parameters": Array [
|
||||||
|
Object {
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/addonSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "addonSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"put": Object {
|
||||||
|
"operationId": "updateAddon",
|
||||||
|
"parameters": Array [
|
||||||
|
Object {
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"requestBody": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/addonSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "addonSchema",
|
||||||
|
"required": true,
|
||||||
|
},
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/addonSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "addonSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
"/api/admin/api-tokens": Object {
|
"/api/admin/api-tokens": Object {
|
||||||
"get": Object {
|
"get": Object {
|
||||||
"operationId": "getAllApiTokens",
|
"operationId": "getAllApiTokens",
|
||||||
|
@ -8,6 +8,7 @@ title: How to create a feature toggle
|
|||||||
You can perform every action both via the UI and the admin API. This guide includes screenshots to highlight the relevant UI controls and links to the relevant API methods for each step.
|
You can perform every action both via the UI and the admin API. This guide includes screenshots to highlight the relevant UI controls and links to the relevant API methods for each step.
|
||||||
|
|
||||||
This guide is split into three sections:
|
This guide is split into three sections:
|
||||||
|
|
||||||
1. [Prerequisites](#prerequisites): you need these before you can create a toggle.
|
1. [Prerequisites](#prerequisites): you need these before you can create a toggle.
|
||||||
2. [Required steps](#required-steps): all the required steps to create a toggle and activate it in production.
|
2. [Required steps](#required-steps): all the required steps to create a toggle and activate it in production.
|
||||||
3. [Optional steps](#optional-steps): optional steps you can take to further target and configure your feature toggle and its audience.
|
3. [Optional steps](#optional-steps): optional steps you can take to further target and configure your feature toggle and its audience.
|
||||||
@ -15,6 +16,7 @@ This guide is split into three sections:
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
To be able to create a feature toggle in an Unleash system you will need:
|
To be able to create a feature toggle in an Unleash system you will need:
|
||||||
|
|
||||||
- A running Unleash instance
|
- A running Unleash instance
|
||||||
- A project to hold the toggle
|
- A project to hold the toggle
|
||||||
- A user with an **editor** or **admin** role OR a user with the following permissions inside the target project:
|
- A user with an **editor** or **admin** role OR a user with the following permissions inside the target project:
|
||||||
@ -44,7 +46,6 @@ Use the [Admin API endpoint for creating a feature toggle](../api/admin/feature-
|
|||||||
|
|
||||||
In the project that you want to create the toggle in, use the "new feature toggle" button and fill the form out with your desired configuration. Refer to the [feature toggle reference documentation](../reference/feature-toggles.mdx) for the full list of configuration options and explanations.
|
In the project that you want to create the toggle in, use the "new feature toggle" button and fill the form out with your desired configuration. Refer to the [feature toggle reference documentation](../reference/feature-toggles.mdx) for the full list of configuration options and explanations.
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Step 2: Add a strategy {#step-2}
|
### Step 2: Add a strategy {#step-2}
|
||||||
@ -77,14 +78,12 @@ These optional steps allow you to further configure your feature toggles to add
|
|||||||
|
|
||||||
### Add constraints and segmentation
|
### Add constraints and segmentation
|
||||||
|
|
||||||
|
Constraints and segmentation allow you to set filters on your strategies, so that they will only be evaluated for users and applications that match the specified preconditions. Refer to the [strategy constraints](../advanced/strategy-constraints.md 'strategy constraints reference documentation') and [segments reference documentation](../reference/segments.mdx) for more information.
|
||||||
Constraints and segmentation allow you to set filters on your strategies, so that they will only be evaluated for users and applications that match the specified preconditions. Refer to the [strategy constraints](../advanced/strategy-constraints.md "strategy constraints reference documentation") and [segments reference documentation](../reference/segments.mdx) for more information.
|
|
||||||
|
|
||||||
To add constraints and segmentation, use the "edit strategy" button for the desired strategy.
|
To add constraints and segmentation, use the "edit strategy" button for the desired strategy.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
#### Constraints
|
#### Constraints
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
@ -95,13 +94,12 @@ Constraints aren't fixed and can be changed later to further narrow your audienc
|
|||||||
|
|
||||||
:::tip API: Add constraints
|
:::tip API: Add constraints
|
||||||
|
|
||||||
You can either [add constraints when you add the strategy](../api/admin/feature-toggles-api-v2.md#add-strategy) or [PUT](../api/admin/feature-toggles-api-v2.md#update-strategy "PUT an activation strategy") or [PATCH the strategy afterwards](../api/admin/feature-toggles-api-v2.md#put-strategy)
|
You can either [add constraints when you add the strategy](../api/admin/feature-toggles-api-v2.md#add-strategy) or [PUT](../api/admin/feature-toggles-api-v2.md#update-strategy 'PUT an activation strategy') or [PATCH the strategy afterwards](../api/admin/feature-toggles-api-v2.md#put-strategy)
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
In the strategy configuration screen for the strategy that you want to configure, use the "add custom constraint" button to add a custom constraint.
|
In the strategy configuration screen for the strategy that you want to configure, use the "add custom constraint" button to add a custom constraint.
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### Segments
|
#### Segments
|
||||||
@ -118,12 +116,12 @@ Use the [API for adding segments to a strategy](../api/admin/segments.mdx#repla
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|
||||||
In the strategy configuration screen for the strategy that you want to configure, use the "select segments" dropdown to add segments.
|
In the strategy configuration screen for the strategy that you want to configure, use the "select segments" dropdown to add segments.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Add variants
|
### Add variants
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
This can be done at any point after you've created your toggle.
|
This can be done at any point after you've created your toggle.
|
||||||
@ -132,12 +130,10 @@ This can be done at any point after you've created your toggle.
|
|||||||
|
|
||||||
:::tip API: add variants
|
:::tip API: add variants
|
||||||
|
|
||||||
|
|
||||||
Use the [update variants endpoint](../api/admin/feature-toggles-api-v2.md#update-variants). The payload should be your desired variant configuration.
|
Use the [update variants endpoint](../api/admin/feature-toggles-api-v2.md#update-variants). The payload should be your desired variant configuration.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
[Variants](../advanced/feature-toggle-variants.md) give you the ability to further target your users and split them into groups of your choosing, such as for A/B testing.
|
[Variants](../advanced/feature-toggle-variants.md) give you the ability to further target your users and split them into groups of your choosing, such as for A/B testing. On the toggle overview page, select the variants tab. Use the "new variant" button to add the variants that you want.
|
||||||
On the toggle overview page, select the variants tab. Use the "new variant" button to add the variants that you want.
|
|
||||||
|
|
||||||

|

|
||||||
|
Loading…
Reference in New Issue
Block a user