2021-11-24 13:08:04 +01:00
|
|
|
import FeatureToggleService from '../../../services/feature-toggle-service';
|
|
|
|
import { Logger } from '../../../logger';
|
|
|
|
import Controller from '../../controller';
|
|
|
|
import { IUnleashConfig } from '../../../types/option';
|
|
|
|
import { IUnleashServices } from '../../../types';
|
|
|
|
import { Request, Response } from 'express';
|
|
|
|
import { Operation } from 'fast-json-patch';
|
2022-11-22 10:54:04 +01:00
|
|
|
import {
|
|
|
|
NONE,
|
|
|
|
UPDATE_FEATURE_ENVIRONMENT_VARIANTS,
|
|
|
|
UPDATE_FEATURE_VARIANTS,
|
|
|
|
} from '../../../types/permissions';
|
2023-01-20 10:30:20 +01:00
|
|
|
import { IVariant, WeightType } from '../../../types/model';
|
2021-12-16 11:07:19 +01:00
|
|
|
import { extractUsername } from '../../../util/extract-user';
|
|
|
|
import { IAuthRequest } from '../../unleash-types';
|
2022-06-03 13:16:59 +02:00
|
|
|
import { FeatureVariantsSchema } from '../../../openapi/spec/feature-variants-schema';
|
2022-07-01 08:06:33 +02:00
|
|
|
import { createRequestSchema } from '../../../openapi/util/create-request-schema';
|
|
|
|
import { createResponseSchema } from '../../../openapi/util/create-response-schema';
|
2023-01-20 10:30:20 +01:00
|
|
|
import { AccessService } from '../../../services';
|
|
|
|
import { BadDataError, NoAccessError } from '../../../../lib/error';
|
|
|
|
import { User } from 'lib/server-impl';
|
|
|
|
import { PushVariantsSchema } from 'lib/openapi/spec/push-variants-schema';
|
2021-11-24 13:08:04 +01:00
|
|
|
|
|
|
|
const PREFIX = '/:projectId/features/:featureName/variants';
|
2022-11-21 10:37:16 +01:00
|
|
|
const ENV_PREFIX =
|
|
|
|
'/:projectId/features/:featureName/environments/:environment/variants';
|
|
|
|
|
|
|
|
interface FeatureEnvironmentParams extends FeatureParams {
|
|
|
|
environment: string;
|
|
|
|
}
|
2021-11-24 13:08:04 +01:00
|
|
|
|
|
|
|
interface FeatureParams extends ProjectParam {
|
|
|
|
featureName: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ProjectParam {
|
|
|
|
projectId: string;
|
|
|
|
}
|
|
|
|
export default class VariantsController extends Controller {
|
|
|
|
private logger: Logger;
|
|
|
|
|
|
|
|
private featureService: FeatureToggleService;
|
|
|
|
|
2023-01-20 10:30:20 +01:00
|
|
|
private accessService: AccessService;
|
|
|
|
|
2021-11-24 13:08:04 +01:00
|
|
|
constructor(
|
|
|
|
config: IUnleashConfig,
|
|
|
|
{
|
|
|
|
featureToggleService,
|
2022-06-03 13:16:59 +02:00
|
|
|
openApiService,
|
2023-01-20 10:30:20 +01:00
|
|
|
accessService,
|
|
|
|
}: Pick<
|
|
|
|
IUnleashServices,
|
|
|
|
'featureToggleService' | 'openApiService' | 'accessService'
|
|
|
|
>,
|
2021-11-24 13:08:04 +01:00
|
|
|
) {
|
|
|
|
super(config);
|
|
|
|
this.logger = config.getLogger('admin-api/project/variants.ts');
|
|
|
|
this.featureService = featureToggleService;
|
2023-01-20 10:30:20 +01:00
|
|
|
this.accessService = accessService;
|
2022-06-03 13:16:59 +02:00
|
|
|
this.route({
|
|
|
|
method: 'get',
|
|
|
|
path: PREFIX,
|
|
|
|
permission: NONE,
|
|
|
|
handler: this.getVariants,
|
|
|
|
middleware: [
|
|
|
|
openApiService.validPath({
|
2022-08-12 11:37:57 +02:00
|
|
|
tags: ['Features'],
|
2022-06-03 13:16:59 +02:00
|
|
|
operationId: 'getFeatureVariants',
|
2022-06-08 08:01:14 +02:00
|
|
|
responses: {
|
|
|
|
200: createResponseSchema('featureVariantsSchema'),
|
|
|
|
},
|
2022-06-03 13:16:59 +02:00
|
|
|
}),
|
|
|
|
],
|
|
|
|
});
|
|
|
|
this.route({
|
|
|
|
method: 'patch',
|
|
|
|
path: PREFIX,
|
|
|
|
permission: UPDATE_FEATURE_VARIANTS,
|
|
|
|
handler: this.patchVariants,
|
|
|
|
middleware: [
|
|
|
|
openApiService.validPath({
|
2022-08-12 11:37:57 +02:00
|
|
|
tags: ['Features'],
|
2022-06-03 13:16:59 +02:00
|
|
|
operationId: 'patchFeatureVariants',
|
2022-06-08 08:01:14 +02:00
|
|
|
requestBody: createRequestSchema('patchesSchema'),
|
|
|
|
responses: {
|
|
|
|
200: createResponseSchema('featureVariantsSchema'),
|
|
|
|
},
|
2022-06-03 13:16:59 +02:00
|
|
|
}),
|
|
|
|
],
|
|
|
|
});
|
|
|
|
this.route({
|
|
|
|
method: 'put',
|
|
|
|
path: PREFIX,
|
|
|
|
permission: UPDATE_FEATURE_VARIANTS,
|
|
|
|
handler: this.overwriteVariants,
|
|
|
|
middleware: [
|
|
|
|
openApiService.validPath({
|
2022-08-12 11:37:57 +02:00
|
|
|
tags: ['Features'],
|
2022-06-03 13:16:59 +02:00
|
|
|
operationId: 'overwriteFeatureVariants',
|
2022-06-08 08:01:14 +02:00
|
|
|
requestBody: createRequestSchema('variantsSchema'),
|
|
|
|
responses: {
|
|
|
|
200: createResponseSchema('featureVariantsSchema'),
|
|
|
|
},
|
2022-06-03 13:16:59 +02:00
|
|
|
}),
|
|
|
|
],
|
|
|
|
});
|
2022-11-21 10:37:16 +01:00
|
|
|
this.route({
|
|
|
|
method: 'get',
|
|
|
|
path: ENV_PREFIX,
|
|
|
|
permission: NONE,
|
|
|
|
handler: this.getVariantsOnEnv,
|
|
|
|
middleware: [
|
|
|
|
openApiService.validPath({
|
|
|
|
tags: ['Features'],
|
|
|
|
operationId: 'getEnvironmentFeatureVariants',
|
|
|
|
responses: {
|
|
|
|
200: createResponseSchema('featureVariantsSchema'),
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
});
|
|
|
|
this.route({
|
|
|
|
method: 'patch',
|
|
|
|
path: ENV_PREFIX,
|
2022-11-22 10:54:04 +01:00
|
|
|
permission: UPDATE_FEATURE_ENVIRONMENT_VARIANTS,
|
2022-11-21 10:37:16 +01:00
|
|
|
handler: this.patchVariantsOnEnv,
|
|
|
|
middleware: [
|
|
|
|
openApiService.validPath({
|
|
|
|
tags: ['Features'],
|
|
|
|
operationId: 'patchEnvironmentsFeatureVariants',
|
|
|
|
requestBody: createRequestSchema('patchesSchema'),
|
|
|
|
responses: {
|
|
|
|
200: createResponseSchema('featureVariantsSchema'),
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
});
|
|
|
|
this.route({
|
|
|
|
method: 'put',
|
|
|
|
path: ENV_PREFIX,
|
2022-11-22 10:54:04 +01:00
|
|
|
permission: UPDATE_FEATURE_ENVIRONMENT_VARIANTS,
|
2022-11-21 10:37:16 +01:00
|
|
|
handler: this.overwriteVariantsOnEnv,
|
|
|
|
middleware: [
|
|
|
|
openApiService.validPath({
|
|
|
|
tags: ['Features'],
|
|
|
|
operationId: 'overwriteEnvironmentFeatureVariants',
|
|
|
|
requestBody: createRequestSchema('variantsSchema'),
|
|
|
|
responses: {
|
|
|
|
200: createResponseSchema('featureVariantsSchema'),
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
});
|
2023-01-20 10:30:20 +01:00
|
|
|
this.route({
|
|
|
|
method: 'put',
|
|
|
|
path: `${PREFIX}-batch`,
|
|
|
|
permission: NONE,
|
|
|
|
handler: this.pushVariantsToEnvironments,
|
|
|
|
middleware: [
|
|
|
|
openApiService.validPath({
|
|
|
|
tags: ['Features'],
|
|
|
|
operationId: 'overwriteFeatureVariantsOnEnvironments',
|
|
|
|
requestBody: createRequestSchema('pushVariantsSchema'),
|
|
|
|
responses: {
|
|
|
|
200: createResponseSchema('featureVariantsSchema'),
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
],
|
|
|
|
});
|
2021-11-24 13:08:04 +01:00
|
|
|
}
|
|
|
|
|
2022-12-06 10:47:54 +01:00
|
|
|
/**
|
|
|
|
* @deprecated - Variants should be fetched from featureService.getVariantsForEnv (since variants are now; since 4.18, connected to environments)
|
|
|
|
* @param req
|
|
|
|
* @param res
|
|
|
|
*/
|
2021-11-24 13:08:04 +01:00
|
|
|
async getVariants(
|
|
|
|
req: Request<FeatureParams, any, any, any>,
|
2022-06-03 13:16:59 +02:00
|
|
|
res: Response<FeatureVariantsSchema>,
|
2021-11-24 13:08:04 +01:00
|
|
|
): Promise<void> {
|
|
|
|
const { featureName } = req.params;
|
|
|
|
const variants = await this.featureService.getVariants(featureName);
|
2022-06-08 08:01:14 +02:00
|
|
|
res.status(200).json({ version: 1, variants: variants || [] });
|
2021-11-24 13:08:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
async patchVariants(
|
2021-12-16 11:07:19 +01:00
|
|
|
req: IAuthRequest<FeatureParams, any, Operation[]>,
|
2022-06-03 13:16:59 +02:00
|
|
|
res: Response<FeatureVariantsSchema>,
|
2021-11-24 13:08:04 +01:00
|
|
|
): Promise<void> {
|
2021-12-16 11:07:19 +01:00
|
|
|
const { projectId, featureName } = req.params;
|
|
|
|
const userName = extractUsername(req);
|
2021-11-24 13:08:04 +01:00
|
|
|
const updatedFeature = await this.featureService.updateVariants(
|
|
|
|
featureName,
|
2021-12-16 11:07:19 +01:00
|
|
|
projectId,
|
2021-11-24 13:08:04 +01:00
|
|
|
req.body,
|
2021-12-16 11:07:19 +01:00
|
|
|
userName,
|
2021-11-24 13:08:04 +01:00
|
|
|
);
|
|
|
|
res.status(200).json({
|
2022-06-08 08:01:14 +02:00
|
|
|
version: 1,
|
2021-11-24 13:08:04 +01:00
|
|
|
variants: updatedFeature.variants,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async overwriteVariants(
|
2021-12-16 11:07:19 +01:00
|
|
|
req: IAuthRequest<FeatureParams, any, IVariant[], any>,
|
2022-06-03 13:16:59 +02:00
|
|
|
res: Response<FeatureVariantsSchema>,
|
2021-11-24 13:08:04 +01:00
|
|
|
): Promise<void> {
|
2021-12-16 11:07:19 +01:00
|
|
|
const { projectId, featureName } = req.params;
|
|
|
|
const userName = extractUsername(req);
|
2021-11-24 13:08:04 +01:00
|
|
|
const updatedFeature = await this.featureService.saveVariants(
|
|
|
|
featureName,
|
2021-12-16 11:07:19 +01:00
|
|
|
projectId,
|
2021-11-24 13:08:04 +01:00
|
|
|
req.body,
|
2021-12-16 11:07:19 +01:00
|
|
|
userName,
|
2021-11-24 13:08:04 +01:00
|
|
|
);
|
|
|
|
res.status(200).json({
|
2022-06-08 08:01:14 +02:00
|
|
|
version: 1,
|
2021-11-24 13:08:04 +01:00
|
|
|
variants: updatedFeature.variants,
|
|
|
|
});
|
|
|
|
}
|
2022-11-21 10:37:16 +01:00
|
|
|
|
2023-01-20 10:30:20 +01:00
|
|
|
async pushVariantsToEnvironments(
|
|
|
|
req: IAuthRequest<
|
|
|
|
FeatureEnvironmentParams,
|
|
|
|
any,
|
|
|
|
PushVariantsSchema,
|
|
|
|
any
|
|
|
|
>,
|
|
|
|
res: Response<FeatureVariantsSchema>,
|
|
|
|
): Promise<void> {
|
|
|
|
const { projectId, featureName } = req.params;
|
|
|
|
const { environments, variants } = req.body;
|
|
|
|
const userName = extractUsername(req);
|
|
|
|
|
|
|
|
if (environments === undefined || environments.length === 0) {
|
|
|
|
throw new BadDataError('No environments provided');
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.checkAccess(
|
|
|
|
req.user,
|
|
|
|
projectId,
|
|
|
|
environments,
|
|
|
|
UPDATE_FEATURE_ENVIRONMENT_VARIANTS,
|
|
|
|
);
|
|
|
|
|
|
|
|
const variantsWithDefaults = variants.map((variant) => ({
|
|
|
|
weightType: WeightType.VARIABLE,
|
|
|
|
stickiness: 'default',
|
|
|
|
...variant,
|
|
|
|
}));
|
|
|
|
|
|
|
|
await this.featureService.setVariantsOnEnvs(
|
|
|
|
projectId,
|
|
|
|
featureName,
|
|
|
|
environments,
|
|
|
|
variantsWithDefaults,
|
|
|
|
userName,
|
|
|
|
);
|
|
|
|
res.status(200).json({
|
|
|
|
version: 1,
|
|
|
|
variants: variantsWithDefaults,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async checkAccess(
|
|
|
|
user: User,
|
|
|
|
projectId: string,
|
|
|
|
environments: string[],
|
|
|
|
permission: string,
|
|
|
|
): Promise<void> {
|
|
|
|
for (const environment of environments) {
|
|
|
|
if (
|
|
|
|
!(await this.accessService.hasPermission(
|
|
|
|
user,
|
|
|
|
permission,
|
|
|
|
projectId,
|
|
|
|
environment,
|
|
|
|
))
|
|
|
|
) {
|
|
|
|
throw new NoAccessError(
|
|
|
|
UPDATE_FEATURE_ENVIRONMENT_VARIANTS,
|
|
|
|
environment,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-21 10:37:16 +01:00
|
|
|
async getVariantsOnEnv(
|
|
|
|
req: Request<FeatureEnvironmentParams, any, any, any>,
|
|
|
|
res: Response<FeatureVariantsSchema>,
|
|
|
|
): Promise<void> {
|
|
|
|
const { featureName, environment } = req.params;
|
|
|
|
const variants = await this.featureService.getVariantsForEnv(
|
|
|
|
featureName,
|
|
|
|
environment,
|
|
|
|
);
|
|
|
|
res.status(200).json({ version: 1, variants: variants || [] });
|
|
|
|
}
|
|
|
|
|
|
|
|
async patchVariantsOnEnv(
|
|
|
|
req: IAuthRequest<FeatureEnvironmentParams, any, Operation[]>,
|
|
|
|
res: Response<FeatureVariantsSchema>,
|
|
|
|
): Promise<void> {
|
|
|
|
const { projectId, featureName, environment } = req.params;
|
|
|
|
const userName = extractUsername(req);
|
|
|
|
|
|
|
|
const variants = await this.featureService.updateVariantsOnEnv(
|
|
|
|
featureName,
|
|
|
|
projectId,
|
|
|
|
environment,
|
|
|
|
req.body,
|
|
|
|
userName,
|
|
|
|
);
|
|
|
|
res.status(200).json({
|
|
|
|
version: 1,
|
|
|
|
variants,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async overwriteVariantsOnEnv(
|
|
|
|
req: IAuthRequest<FeatureEnvironmentParams, any, IVariant[], any>,
|
|
|
|
res: Response<FeatureVariantsSchema>,
|
|
|
|
): Promise<void> {
|
2022-11-22 09:57:12 +01:00
|
|
|
const { featureName, environment, projectId } = req.params;
|
2022-11-21 10:37:16 +01:00
|
|
|
const userName = extractUsername(req);
|
|
|
|
const variants = await this.featureService.saveVariantsOnEnv(
|
2022-11-22 09:57:12 +01:00
|
|
|
projectId,
|
2022-11-21 10:37:16 +01:00
|
|
|
featureName,
|
|
|
|
environment,
|
|
|
|
req.body,
|
|
|
|
userName,
|
|
|
|
);
|
|
|
|
res.status(200).json({
|
|
|
|
version: 1,
|
|
|
|
variants: variants,
|
|
|
|
});
|
|
|
|
}
|
2021-11-24 13:08:04 +01:00
|
|
|
}
|