1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

fix: split features schema into archived and project features (#7973)

This commit is contained in:
Mateusz Kwasniewski 2024-08-23 12:59:39 +02:00 committed by GitHub
parent 4693f7c598
commit 1f3cc3917e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 354 additions and 89 deletions

View File

@ -9,10 +9,6 @@ import {
import { DELETE_FEATURE, NONE, UPDATE_FEATURE } from '../../types/permissions';
import type FeatureToggleService from './feature-toggle-service';
import type { IAuthRequest } from '../../routes/unleash-types';
import {
featuresSchema,
type FeaturesSchema,
} from '../../openapi/spec/features-schema';
import { serializeDates } from '../../types/serialize-dates';
import type { OpenApiService } from '../../services/openapi-service';
import { createResponseSchema } from '../../openapi/util/create-response-schema';
@ -24,6 +20,10 @@ import type {
TransactionCreator,
UnleashTransaction,
} from '../../db/transaction';
import {
archivedFeaturesSchema,
type ArchivedFeaturesSchema,
} from '../../openapi';
export default class ArchiveController extends Controller {
private featureService: FeatureToggleService;
@ -67,7 +67,7 @@ export default class ArchiveController extends Controller {
'Retrieve a list of all [archived feature flags](https://docs.getunleash.io/reference/archived-toggles).',
operationId: 'getArchivedFeatures',
responses: {
200: createResponseSchema('featuresSchema'),
200: createResponseSchema('archivedFeaturesSchema'),
...getStandardResponses(401, 403),
},
@ -89,7 +89,7 @@ export default class ArchiveController extends Controller {
description:
'Retrieves a list of archived features that belong to the provided project.',
responses: {
200: createResponseSchema('featuresSchema'),
200: createResponseSchema('archivedFeaturesSchema'),
...getStandardResponses(401, 403),
},
@ -143,7 +143,7 @@ export default class ArchiveController extends Controller {
async getArchivedFeatures(
req: IAuthRequest,
res: Response<FeaturesSchema>,
res: Response<ArchivedFeaturesSchema>,
): Promise<void> {
const { user } = req;
const features = await this.featureService.getAllArchivedFeatures(
@ -153,14 +153,14 @@ export default class ArchiveController extends Controller {
this.openApiService.respondWithValidation(
200,
res,
featuresSchema.$id,
archivedFeaturesSchema.$id,
{ version: 2, features: serializeDates(features) },
);
}
async getArchivedFeaturesByProjectId(
req: Request<{ projectId: string }, any, any, any>,
res: Response<FeaturesSchema>,
res: Response<ArchivedFeaturesSchema>,
): Promise<void> {
const { projectId } = req.params;
const features =
@ -171,7 +171,7 @@ export default class ArchiveController extends Controller {
this.openApiService.respondWithValidation(
200,
res,
featuresSchema.$id,
archivedFeaturesSchema.$id,
{ version: 2, features: serializeDates(features) },
);
}

View File

@ -30,12 +30,11 @@ import {
type FeatureEnvironmentSchema,
featureSchema,
type FeatureSchema,
featuresSchema,
type FeaturesSchema,
featureStrategySchema,
type FeatureStrategySchema,
getStandardResponses,
type ParametersSchema,
projectFeaturesSchema,
type ProjectFeaturesSchema,
type SetStrategySortOrderSchema,
type TagsBulkAddSchema,
type TagSchema,
@ -421,7 +420,7 @@ export default class ProjectFeaturesController extends Controller {
tags: ['Features'],
operationId: 'getFeatures',
responses: {
200: createResponseSchema('featuresSchema'),
200: createResponseSchema('projectFeaturesSchema'),
...getStandardResponses(400, 401, 403),
},
}),
@ -606,7 +605,7 @@ export default class ProjectFeaturesController extends Controller {
async getFeatures(
req: IAuthRequest<ProjectParam, any, any, AdminFeaturesQuerySchema>,
res: Response<FeaturesSchema>,
res: Response<ProjectFeaturesSchema>,
): Promise<void> {
const { projectId } = req.params;
const query = await this.prepQuery(req.query, projectId);
@ -617,7 +616,7 @@ export default class ProjectFeaturesController extends Controller {
this.openApiService.respondWithValidation(
200,
res,
featuresSchema.$id,
projectFeaturesSchema.$id,
{ version: 2, features: serializeDates(features) },
);
}
@ -1155,14 +1154,4 @@ export default class ProjectFeaturesController extends Controller {
);
res.status(200).end();
}
async getStrategyParameters(
req: Request<StrategyIdParams, any, any, any>,
res: Response<ParametersSchema>,
): Promise<void> {
this.logger.info('Getting strategy parameters');
const { strategyId } = req.params;
const strategy = await this.featureService.getStrategy(strategyId);
res.status(200).json(strategy.parameters);
}
}

View File

@ -0,0 +1,102 @@
import type { FromSchema } from 'json-schema-to-ts';
export const archivedFeatureSchema = {
$id: '#/components/schemas/archivedFeatureSchema',
type: 'object',
additionalProperties: false,
required: ['name', 'project'],
description: 'An archived project feature flag definition',
properties: {
name: {
type: 'string',
example: 'disable-comments',
description: 'Unique feature name',
},
type: {
type: 'string',
example: 'kill-switch',
description:
'Type of the flag e.g. experiment, kill-switch, release, operational, permission',
},
description: {
type: 'string',
nullable: true,
example:
'Controls disabling of the comments section in case of an incident',
description: 'Detailed description of the feature',
},
project: {
type: 'string',
example: 'dx-squad',
description: 'Name of the project the feature belongs to',
},
stale: {
type: 'boolean',
example: false,
description:
'`true` if the feature is stale based on the age and feature type, otherwise `false`.',
},
impressionData: {
type: 'boolean',
example: false,
description:
'`true` if the impression data collection is enabled for the feature, otherwise `false`.',
},
createdAt: {
type: 'string',
format: 'date-time',
example: '2023-01-28T15:21:39.975Z',
description: 'The date the feature was created',
},
archivedAt: {
type: 'string',
format: 'date-time',
example: '2023-01-29T15:21:39.975Z',
description: 'The date the feature was archived',
},
lastSeenAt: {
type: 'string',
format: 'date-time',
nullable: true,
deprecated: true,
example: '2023-01-28T16:21:39.975Z',
description:
'The date when metrics where last collected for the feature. This field was deprecated in v5, use the one in featureEnvironmentSchema',
},
environments: {
type: 'array',
deprecated: true,
description:
'The list of environments where the feature can be used',
items: {
type: 'object',
properties: {
name: {
type: 'string',
example: 'my-dev-env',
description: 'The name of the environment',
},
lastSeenAt: {
type: 'string',
format: 'date-time',
nullable: true,
example: '2023-01-28T16:21:39.975Z',
description:
'The date when metrics where last collected for the feature environment',
},
enabled: {
type: 'boolean',
example: true,
description:
'`true` if the feature is enabled for the environment, otherwise `false`.',
},
},
},
},
},
components: {
schemas: {},
},
} as const;
export type ArchivedFeatureSchema = FromSchema<typeof archivedFeatureSchema>;

View File

@ -0,0 +1,31 @@
import type { FromSchema } from 'json-schema-to-ts';
import { archivedFeatureSchema } from './archived-feature-schema';
export const archivedFeaturesSchema = {
$id: '#/components/schemas/archivedFeaturesSchema',
type: 'object',
additionalProperties: false,
required: ['version', 'features'],
description: 'A list of archived features',
deprecated: true,
properties: {
version: {
type: 'integer',
description: "The version of the feature's schema",
},
features: {
type: 'array',
items: {
$ref: '#/components/schemas/archivedFeatureSchema',
},
description: 'A list of features',
},
},
components: {
schemas: {
archivedFeatureSchema,
},
},
} as const;
export type ArchivedFeaturesSchema = FromSchema<typeof archivedFeaturesSchema>;

View File

@ -1,13 +0,0 @@
import { validateSchema } from '../validate';
import type { FeaturesSchema } from './features-schema';
test('featuresSchema', () => {
const data: FeaturesSchema = {
version: 1,
features: [],
};
expect(
validateSchema('#/components/schemas/featuresSchema', data),
).toBeUndefined();
});

View File

@ -1,49 +0,0 @@
import type { FromSchema } from 'json-schema-to-ts';
import { featureSchema } from './feature-schema';
import { parametersSchema } from './parameters-schema';
import { variantSchema } from './variant-schema';
import { overrideSchema } from './override-schema';
import { constraintSchema } from './constraint-schema';
import { featureStrategySchema } from './feature-strategy-schema';
import { environmentSchema } from './environment-schema';
import { featureEnvironmentSchema } from './feature-environment-schema';
import { strategyVariantSchema } from './strategy-variant-schema';
import { tagSchema } from './tag-schema';
export const featuresSchema = {
$id: '#/components/schemas/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: {
schemas: {
constraintSchema,
environmentSchema,
featureSchema,
overrideSchema,
featureEnvironmentSchema,
featureStrategySchema,
strategyVariantSchema,
parametersSchema,
variantSchema,
tagSchema,
},
},
} as const;
export type FeaturesSchema = FromSchema<typeof featuresSchema>;

View File

@ -24,6 +24,8 @@ export * from './application-overview-schema';
export * from './application-schema';
export * from './application-usage-schema';
export * from './applications-schema';
export * from './archived-feature-schema';
export * from './archived-features-schema';
export * from './batch-features-schema';
export * from './batch-stale-schema';
export * from './bulk-metrics-schema';
@ -93,7 +95,6 @@ export * from './feature-type-schema';
export * from './feature-types-schema';
export * from './feature-usage-schema';
export * from './feature-variants-schema';
export * from './features-schema';
export * from './feedback-create-schema';
export * from './feedback-response-schema';
export * from './feedback-update-schema';
@ -144,6 +145,9 @@ export * from './project-application-sdk-schema';
export * from './project-applications-schema';
export * from './project-dora-metrics-schema';
export * from './project-environment-schema';
export * from './project-feature-environment-schema';
export * from './project-feature-schema';
export * from './project-features-schema';
export * from './project-flag-creators-schema';
export * from './project-insights-schema';
export * from './project-overview-schema';

View File

@ -0,0 +1,68 @@
import type { FromSchema } from 'json-schema-to-ts';
export const projectFeatureEnvironmentSchema = {
$id: '#/components/schemas/projectFeatureEnvironmentSchema',
type: 'object',
additionalProperties: false,
required: [
'name',
'type',
'enabled',
'sortOrder',
'variantCount',
'lastSeenAt',
],
description: 'A detailed description of the feature environment',
properties: {
name: {
type: 'string',
example: 'my-dev-env',
description: 'The name of the environment',
},
type: {
type: 'string',
example: 'development',
description: 'The type of the environment',
},
enabled: {
type: 'boolean',
example: true,
description:
'`true` if the feature is enabled for the environment, otherwise `false`.',
},
sortOrder: {
type: 'number',
example: 3,
description:
'The sort order of the feature environment in the feature environments list',
},
variantCount: {
type: 'number',
description: 'The number of defined variants',
},
lastSeenAt: {
type: 'string',
format: 'date-time',
nullable: true,
example: '2023-01-28T16:21:39.975Z',
description:
'The date when metrics where last collected for the feature environment',
},
hasStrategies: {
type: 'boolean',
description: 'Whether the feature has any strategies defined.',
},
hasEnabledStrategies: {
type: 'boolean',
description:
'Whether the feature has any enabled strategies defined.',
},
},
components: {
schemas: {},
},
} as const;
export type ProjectFeatureEnvironmentSchema = FromSchema<
typeof projectFeatureEnvironmentSchema
>;

View File

@ -0,0 +1,98 @@
import type { FromSchema } from 'json-schema-to-ts';
import { tagSchema } from './tag-schema';
import { projectFeatureEnvironmentSchema } from './project-feature-environment-schema';
export const projectFeatureSchema = {
$id: '#/components/schemas/projectFeatureSchema',
type: 'object',
additionalProperties: false,
required: [
'name',
'type',
'description',
'stale',
'favorite',
'impressionData',
'createdAt',
'lastSeenAt',
'environments',
],
description: 'A project feature flag definition',
properties: {
name: {
type: 'string',
example: 'disable-comments',
description: 'Unique feature name',
},
type: {
type: 'string',
example: 'kill-switch',
description:
'Type of the flag e.g. experiment, kill-switch, release, operational, permission',
},
description: {
type: 'string',
nullable: true,
example:
'Controls disabling of the comments section in case of an incident',
description: 'Detailed description of the feature',
},
stale: {
type: 'boolean',
example: false,
description:
'`true` if the feature is stale based on the age and feature type, otherwise `false`.',
},
favorite: {
type: 'boolean',
example: true,
description:
'`true` if the feature was favorited, otherwise `false`.',
},
impressionData: {
type: 'boolean',
example: false,
description:
'`true` if the impression data collection is enabled for the feature, otherwise `false`.',
},
createdAt: {
type: 'string',
format: 'date-time',
example: '2023-01-28T15:21:39.975Z',
description: 'The date the feature was created',
},
lastSeenAt: {
type: 'string',
format: 'date-time',
nullable: true,
deprecated: true,
example: '2023-01-28T16:21:39.975Z',
description:
'The date and time when metrics where last collected for this flag in any environment. This field was deprecated in v5. You should instead use the `lastSeenAt` property on the individual environments listed under the `environments` property.',
},
environments: {
type: 'array',
items: {
$ref: '#/components/schemas/projectFeatureEnvironmentSchema',
},
description:
'The list of environments where the feature can be used',
},
tags: {
type: 'array',
items: {
$ref: '#/components/schemas/tagSchema',
},
nullable: true,
description: 'The list of feature tags',
},
},
components: {
schemas: {
projectFeatureEnvironmentSchema,
tagSchema,
},
},
} as const;
export type ProjectFeatureSchema = FromSchema<typeof projectFeatureSchema>;

View File

@ -0,0 +1,35 @@
import type { FromSchema } from 'json-schema-to-ts';
import { tagSchema } from './tag-schema';
import { projectFeatureSchema } from './project-feature-schema';
import { projectFeatureEnvironmentSchema } from './project-feature-environment-schema';
export const projectFeaturesSchema = {
$id: '#/components/schemas/projectFeaturesSchema',
type: 'object',
additionalProperties: false,
required: ['version', 'features'],
description: 'A list of features in a project',
deprecated: true,
properties: {
version: {
type: 'integer',
description: "The version of the feature's schema",
},
features: {
type: 'array',
items: {
$ref: '#/components/schemas/projectFeatureSchema',
},
description: 'A list of features',
},
},
components: {
schemas: {
projectFeatureSchema,
projectFeatureEnvironmentSchema,
tagSchema,
},
},
} as const;
export type ProjectFeaturesSchema = FromSchema<typeof projectFeaturesSchema>;