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:
parent
4693f7c598
commit
1f3cc3917e
@ -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) },
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
102
src/lib/openapi/spec/archived-feature-schema.ts
Normal file
102
src/lib/openapi/spec/archived-feature-schema.ts
Normal 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>;
|
31
src/lib/openapi/spec/archived-features-schema.ts
Normal file
31
src/lib/openapi/spec/archived-features-schema.ts
Normal 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>;
|
@ -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();
|
||||
});
|
@ -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>;
|
@ -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';
|
||||
|
68
src/lib/openapi/spec/project-feature-environment-schema.ts
Normal file
68
src/lib/openapi/spec/project-feature-environment-schema.ts
Normal 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
|
||||
>;
|
98
src/lib/openapi/spec/project-feature-schema.ts
Normal file
98
src/lib/openapi/spec/project-feature-schema.ts
Normal 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>;
|
35
src/lib/openapi/spec/project-features-schema.ts
Normal file
35
src/lib/openapi/spec/project-features-schema.ts
Normal 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>;
|
Loading…
Reference in New Issue
Block a user