1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-12 01:17:04 +02:00

feat: Gradual schema improvements (#3075)

This commit is contained in:
Jaanus Sellin 2023-02-10 16:05:57 +02:00 committed by GitHub
parent 465e98564f
commit df3ce70c0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 293 additions and 20 deletions

View File

@ -5,33 +5,51 @@ export const environmentSchema = {
type: 'object',
additionalProperties: false,
required: ['name', 'type', 'enabled'],
description: 'A definition of the project 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 environment is enabled for the project, otherwise `false`.',
},
protected: {
type: 'boolean',
},
sortOrder: {
type: 'number',
example: 3,
description:
'The sort order of the environment in the environments list',
},
projectCount: {
type: 'number',
nullable: true,
example: 10,
description: 'The number of projects with this environment',
},
apiTokenCount: {
type: 'number',
nullable: true,
example: 6,
description: 'The number of API tokens for the project environment',
},
enabledToggleCount: {
type: 'number',
nullable: true,
example: 10,
description:
'The number of enabled toggles for the project environment',
},
},
components: {},

View File

@ -9,24 +9,36 @@ export const featureEnvironmentSchema = {
type: 'object',
additionalProperties: false,
required: ['name', 'enabled'],
description: 'A detailed description of the feature environment',
properties: {
name: {
type: 'string',
example: 'my-dev-env',
description: 'The name of the environment',
},
featureName: {
type: 'string',
example: 'disable-comments',
},
environment: {
type: 'string',
},
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',
@ -36,12 +48,15 @@ export const featureEnvironmentSchema = {
items: {
$ref: '#/components/schemas/featureStrategySchema',
},
description:
'A list of activation strategies for the feature environment',
},
variants: {
type: 'array',
items: {
$ref: '#/components/schemas/variantSchema',
},
description: 'A list of variants for the feature environment',
},
},
components: {

View File

@ -15,58 +15,87 @@ export const featureSchema = {
properties: {
name: {
type: 'string',
example: 'disable-comments',
description: 'Unique feature name',
},
type: {
type: 'string',
example: 'kill-switch',
description:
'Type of the toggle 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',
},
archived: {
type: 'boolean',
example: true,
description:
'`true` if the feature is archived, otherwise `false`.',
},
project: {
type: 'string',
example: 'dx-squad',
description: 'Name of the project the feature belongs to',
},
enabled: {
type: 'boolean',
example: true,
},
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',
nullable: true,
example: '2023-01-28T15:21:39.975Z',
},
archivedAt: {
type: 'string',
format: 'date-time',
nullable: true,
example: '2023-01-29T15:21:39.975Z',
},
lastSeenAt: {
type: 'string',
format: 'date-time',
nullable: true,
example: '2023-01-28T16:21:39.975Z',
},
environments: {
type: 'array',
items: {
$ref: '#/components/schemas/featureEnvironmentSchema',
},
description:
'The list of environments where the feature can be used',
},
variants: {
type: 'array',
items: {
$ref: '#/components/schemas/variantSchema',
},
description: 'The list of feature variants',
},
tags: {
type: 'array',
@ -74,6 +103,7 @@ export const featureSchema = {
$ref: '#/components/schemas/tagSchema',
},
nullable: true,
description: 'The list of feature tags',
},
},
components: {

View File

@ -14,44 +14,66 @@ export const projectOverviewSchema = {
type: 'object',
additionalProperties: false,
required: ['version', 'name'],
description:
'A high-level overview of a project. It contains information such as project statistics, the name of the project, what members and what features it contains, etc.',
properties: {
stats: {
$ref: '#/components/schemas/projectStatsSchema',
description: 'Project statistics',
},
version: {
type: 'number',
example: 1,
},
name: {
type: 'string',
example: 'dx-squad',
description: 'The name of this project',
},
description: {
type: 'string',
nullable: true,
example: 'DX squad feature release',
description: 'Additional information about the project',
},
members: {
type: 'number',
example: 4,
description: 'The number of members this project has',
},
health: {
type: 'number',
example: 50,
description:
"An indicator of the [project's health](https://docs.getunleash.io/reference/technical-debt#health-rating) on a scale from 0 to 100",
},
environments: {
type: 'array',
items: {
type: 'string',
},
example: ['development', 'production'],
description: 'The environments that are enabled for this project',
},
features: {
type: 'array',
items: {
$ref: '#/components/schemas/featureSchema',
},
description:
'The full list of features in this project (excluding archived features)',
},
updatedAt: {
type: 'string',
format: 'date-time',
nullable: true,
example: '2023-02-10T08:36:35.262Z',
},
favorite: {
type: 'boolean',
example: true,
description:
'`true` if the project was favorited, otherwise `false`.',
},
},
components: {

View File

@ -10,7 +10,6 @@ test('projectSchema', () => {
featureCount: 10,
memberCount: 3,
updatedAt: '2022-06-28T17:33:53.963Z',
changeRequestsEnabled: false,
};
expect(

View File

@ -5,24 +5,40 @@ export const projectSchema = {
type: 'object',
additionalProperties: false,
required: ['id', 'name'],
description:
'A definition of the project used for projects listing purposes',
properties: {
id: {
type: 'string',
example: 'dx-squad',
description: 'The id of this project',
},
name: {
type: 'string',
example: 'DX-Squad',
description: 'The name of this project',
},
description: {
type: 'string',
nullable: true,
example: 'DX squad feature release',
description: 'Additional information about the project',
},
health: {
type: 'number',
example: 50,
description:
"An indicator of the [project's health](https://docs.getunleash.io/reference/technical-debt#health-rating) on a scale from 0 to 100",
},
featureCount: {
type: 'number',
example: 10,
description: 'The number of features this project has',
},
memberCount: {
type: 'number',
example: 4,
description: 'The number of members this project has',
},
createdAt: {
type: 'string',
@ -33,11 +49,11 @@ export const projectSchema = {
format: 'date-time',
nullable: true,
},
changeRequestsEnabled: {
type: 'boolean',
},
favorite: {
type: 'boolean',
example: true,
description:
'`true` if the project was favorited, otherwise `false`.',
},
},
components: {},

View File

@ -15,33 +15,65 @@ export const projectStatsSchema = {
'projectActivityPastWindow',
'projectMembersAddedCurrentWindow',
],
description: `Statistics for a project, including the average time to production, number of features created, the project activity and more.
Stats are divided into current and previous **windows**.
- The **current window** is the past 30 days.
- The **previous window** is the 30 days **before** the current window (from 60 to 30 days ago)`,
properties: {
avgTimeToProdCurrentWindow: {
type: 'number',
example: 10,
description:
'The average time from when a feature was created to when it was enabled in the "production" environment during the current window',
},
avgTimeToProdPastWindow: {
type: 'number',
example: 10,
description:
'The average time from when a feature was created to when it was enabled in the "production" environment during the previous window',
},
createdCurrentWindow: {
type: 'number',
example: 15,
description:
'The number of feature toggles created during the current window',
},
createdPastWindow: {
type: 'number',
example: 15,
description:
'The number of feature toggles created during the previous window',
},
archivedCurrentWindow: {
type: 'number',
example: 5,
description:
'The number of feature toggles that were archived during the current window',
},
archivedPastWindow: {
type: 'number',
example: 5,
description:
'The number of feature toggles that were archived during the previous window',
},
projectActivityCurrentWindow: {
type: 'number',
example: 100,
description:
'The number of project events that occurred during the current window',
},
projectActivityPastWindow: {
type: 'number',
example: 100,
description:
'The number of project events that occurred during the previous window',
},
projectMembersAddedCurrentWindow: {
type: 'number',
example: 1,
description:
'The number of members that were added to the project during the current window',
},
},
components: {},

View File

@ -6,6 +6,7 @@ export const projectsSchema = {
type: 'object',
additionalProperties: false,
required: ['version', 'projects'],
description: 'An overview of all the projects in the Unleash instance',
properties: {
version: {
type: 'integer',
@ -15,6 +16,7 @@ export const projectsSchema = {
items: {
$ref: '#/components/schemas/projectSchema',
},
description: 'A list of projects in the Unleash instance',
},
},
components: {

View File

@ -17,8 +17,8 @@ import { serializeDates } from '../../../types/serialize-dates';
import { createResponseSchema } from '../../../openapi/util/create-response-schema';
import { IAuthRequest } from '../../unleash-types';
import {
HealthOverviewSchema,
healthOverviewSchema,
ProjectOverviewSchema,
projectOverviewSchema,
} from '../../../../lib/openapi';
import { IArchivedQuery, IProjectParam } from '../../../types/model';
@ -92,7 +92,7 @@ export default class ProjectApi extends Controller {
async getProjectOverview(
req: IAuthRequest<IProjectParam, unknown, unknown, IArchivedQuery>,
res: Response<HealthOverviewSchema>,
res: Response<ProjectOverviewSchema>,
): Promise<void> {
const { projectId } = req.params;
const { archived } = req.query;
@ -105,7 +105,7 @@ export default class ProjectApi extends Controller {
this.openApiService.respondWithValidation(
200,
res,
healthOverviewSchema.$id,
projectOverviewSchema.$id,
serializeDates(overview),
);
}

View File

@ -183,7 +183,7 @@ export interface IProjectOverview {
health: number;
favorite?: boolean;
updatedAt?: Date;
stats: IProjectStats | {};
stats?: IProjectStats;
}
export interface IProjectHealthReport extends IProjectOverview {

View File

@ -1,5 +1,8 @@
import dbInit, { ITestDb } from '../../../helpers/database-init';
import { IUnleashTest, setupApp } from '../../../helpers/test-helper';
import {
IUnleashTest,
setupAppWithCustomConfig,
} from '../../../helpers/test-helper';
import getLogger from '../../../../fixtures/no-logger';
import { DEFAULT_ENV } from '../../../../../lib/util/constants';
@ -8,7 +11,13 @@ let db: ITestDb;
beforeAll(async () => {
db = await dbInit('project_environments_api_serial', getLogger);
app = await setupApp(db.stores);
app = await setupAppWithCustomConfig(db.stores, {
experimental: {
flags: {
strictSchemaValidation: true,
},
},
});
});
afterEach(async () => {

View File

@ -1,5 +1,8 @@
import dbInit, { ITestDb } from '../../../helpers/database-init';
import { IUnleashTest, setupApp } from '../../../helpers/test-helper';
import {
IUnleashTest,
setupAppWithCustomConfig,
} from '../../../helpers/test-helper';
import getLogger from '../../../../fixtures/no-logger';
import { DEFAULT_ENV } from '../../../../../lib/util/constants';
import {
@ -85,7 +88,13 @@ const updateStrategy = async (
beforeAll(async () => {
db = await dbInit('feature_strategy_api_serial', getLogger);
app = await setupApp(db.stores);
app = await setupAppWithCustomConfig(db.stores, {
experimental: {
flags: {
strictSchemaValidation: true,
},
},
});
});
afterEach(async () => {

View File

@ -1,5 +1,5 @@
import dbInit from '../../../helpers/database-init';
import { setupApp } from '../../../helpers/test-helper';
import { setupAppWithCustomConfig } from '../../../helpers/test-helper';
import getLogger from '../../../../fixtures/no-logger';
import ProjectStore from '../../../../../lib/db/project-store';
@ -10,7 +10,13 @@ let projectStore: ProjectStore;
beforeAll(async () => {
db = await dbInit('projects_api_serial', getLogger);
app = await setupApp(db.stores);
app = await setupAppWithCustomConfig(db.stores, {
experimental: {
flags: {
strictSchemaValidation: true,
},
},
});
projectStore = db.stores.projectStore;
});

View File

@ -1,5 +1,5 @@
import dbInit from '../../helpers/database-init';
import { setupApp } from '../../helpers/test-helper';
import { setupAppWithCustomConfig } from '../../helpers/test-helper';
import getLogger from '../../../fixtures/no-logger';
let app;
@ -7,7 +7,13 @@ let db;
beforeAll(async () => {
db = await dbInit('strategy_api_serial', getLogger);
app = await setupApp(db.stores);
app = await setupAppWithCustomConfig(db.stores, {
experimental: {
flags: {
strictSchemaValidation: true,
},
},
});
});
afterAll(async () => {

View File

@ -883,22 +883,33 @@ exports[`should serve the OpenAPI spec 1`] = `
},
"environmentSchema": {
"additionalProperties": false,
"description": "A definition of the project environment",
"properties": {
"apiTokenCount": {
"description": "The number of API tokens for the project environment",
"example": 6,
"nullable": true,
"type": "number",
},
"enabled": {
"description": "\`true\` if the environment is enabled for the project, otherwise \`false\`.",
"example": true,
"type": "boolean",
},
"enabledToggleCount": {
"description": "The number of enabled toggles for the project environment",
"example": 10,
"nullable": true,
"type": "number",
},
"name": {
"description": "The name of the environment",
"example": "my-dev-env",
"type": "string",
},
"projectCount": {
"description": "The number of projects with this environment",
"example": 10,
"nullable": true,
"type": "number",
},
@ -906,9 +917,13 @@ exports[`should serve the OpenAPI spec 1`] = `
"type": "boolean",
},
"sortOrder": {
"description": "The sort order of the environment in the environments list",
"example": 3,
"type": "number",
},
"type": {
"description": "The type of the environment",
"example": "development",
"type": "string",
},
},
@ -1143,35 +1158,47 @@ exports[`should serve the OpenAPI spec 1`] = `
},
"featureEnvironmentSchema": {
"additionalProperties": false,
"description": "A detailed description of the feature environment",
"properties": {
"enabled": {
"description": "\`true\` if the feature is enabled for the environment, otherwise \`false\`.",
"example": true,
"type": "boolean",
},
"environment": {
"type": "string",
},
"featureName": {
"example": "disable-comments",
"type": "string",
},
"name": {
"description": "The name of the environment",
"example": "my-dev-env",
"type": "string",
},
"sortOrder": {
"description": "The sort order of the feature environment in the feature environments list",
"example": 3,
"type": "number",
},
"strategies": {
"description": "A list of activation strategies for the feature environment",
"items": {
"$ref": "#/components/schemas/featureStrategySchema",
},
"type": "array",
},
"type": {
"description": "The type of the environment",
"example": "development",
"type": "string",
},
"variantCount": {
"type": "number",
},
"variants": {
"description": "A list of variants for the feature environment",
"items": {
"$ref": "#/components/schemas/variantSchema",
},
@ -1232,52 +1259,72 @@ exports[`should serve the OpenAPI spec 1`] = `
"additionalProperties": false,
"properties": {
"archived": {
"description": "\`true\` if the feature is archived, otherwise \`false\`.",
"example": true,
"type": "boolean",
},
"archivedAt": {
"example": "2023-01-29T15:21:39.975Z",
"format": "date-time",
"nullable": true,
"type": "string",
},
"createdAt": {
"example": "2023-01-28T15:21:39.975Z",
"format": "date-time",
"nullable": true,
"type": "string",
},
"description": {
"description": "Detailed description of the feature",
"example": "Controls disabling of the comments section in case of an incident",
"nullable": true,
"type": "string",
},
"enabled": {
"example": true,
"type": "boolean",
},
"environments": {
"description": "The list of environments where the feature can be used",
"items": {
"$ref": "#/components/schemas/featureEnvironmentSchema",
},
"type": "array",
},
"favorite": {
"description": "\`true\` if the feature was favorited, otherwise \`false\`.",
"example": true,
"type": "boolean",
},
"impressionData": {
"description": "\`true\` if the impression data collection is enabled for the feature, otherwise \`false\`.",
"example": false,
"type": "boolean",
},
"lastSeenAt": {
"example": "2023-01-28T16:21:39.975Z",
"format": "date-time",
"nullable": true,
"type": "string",
},
"name": {
"description": "Unique feature name",
"example": "disable-comments",
"type": "string",
},
"project": {
"description": "Name of the project the feature belongs to",
"example": "dx-squad",
"type": "string",
},
"stale": {
"description": "\`true\` if the feature is stale based on the age and feature type, otherwise \`false\`.",
"example": false,
"type": "boolean",
},
"tags": {
"description": "The list of feature tags",
"items": {
"$ref": "#/components/schemas/tagSchema",
},
@ -1285,9 +1332,12 @@ exports[`should serve the OpenAPI spec 1`] = `
"type": "array",
},
"type": {
"description": "Type of the toggle e.g. experiment, kill-switch, release, operational, permission",
"example": "kill-switch",
"type": "string",
},
"variants": {
"description": "The list of feature variants",
"items": {
"$ref": "#/components/schemas/variantSchema",
},
@ -2428,43 +2478,64 @@ exports[`should serve the OpenAPI spec 1`] = `
},
"projectOverviewSchema": {
"additionalProperties": false,
"description": "A high-level overview of a project. It contains information such as project statistics, the name of the project, what members and what features it contains, etc.",
"properties": {
"description": {
"description": "Additional information about the project",
"example": "DX squad feature release",
"nullable": true,
"type": "string",
},
"environments": {
"description": "The environments that are enabled for this project",
"example": [
"development",
"production",
],
"items": {
"type": "string",
},
"type": "array",
},
"favorite": {
"description": "\`true\` if the project was favorited, otherwise \`false\`.",
"example": true,
"type": "boolean",
},
"features": {
"description": "The full list of features in this project (excluding archived features)",
"items": {
"$ref": "#/components/schemas/featureSchema",
},
"type": "array",
},
"health": {
"description": "An indicator of the [project's health](https://docs.getunleash.io/reference/technical-debt#health-rating) on a scale from 0 to 100",
"example": 50,
"type": "number",
},
"members": {
"description": "The number of members this project has",
"example": 4,
"type": "number",
},
"name": {
"description": "The name of this project",
"example": "dx-squad",
"type": "string",
},
"stats": {
"$ref": "#/components/schemas/projectStatsSchema",
"description": "Project statistics",
},
"updatedAt": {
"example": "2023-02-10T08:36:35.262Z",
"format": "date-time",
"nullable": true,
"type": "string",
},
"version": {
"example": 1,
"type": "number",
},
},
@ -2476,33 +2547,46 @@ exports[`should serve the OpenAPI spec 1`] = `
},
"projectSchema": {
"additionalProperties": false,
"description": "A definition of the project used for projects listing purposes",
"properties": {
"changeRequestsEnabled": {
"type": "boolean",
},
"createdAt": {
"format": "date-time",
"type": "string",
},
"description": {
"description": "Additional information about the project",
"example": "DX squad feature release",
"nullable": true,
"type": "string",
},
"favorite": {
"description": "\`true\` if the project was favorited, otherwise \`false\`.",
"example": true,
"type": "boolean",
},
"featureCount": {
"description": "The number of features this project has",
"example": 10,
"type": "number",
},
"health": {
"description": "An indicator of the [project's health](https://docs.getunleash.io/reference/technical-debt#health-rating) on a scale from 0 to 100",
"example": 50,
"type": "number",
},
"id": {
"description": "The id of this project",
"example": "dx-squad",
"type": "string",
},
"memberCount": {
"description": "The number of members this project has",
"example": 4,
"type": "number",
},
"name": {
"description": "The name of this project",
"example": "DX-Squad",
"type": "string",
},
"updatedAt": {
@ -2519,32 +2603,55 @@ exports[`should serve the OpenAPI spec 1`] = `
},
"projectStatsSchema": {
"additionalProperties": false,
"description": "Statistics for a project, including the average time to production, number of features created, the project activity and more.
Stats are divided into current and previous **windows**.
- The **current window** is the past 30 days.
- The **previous window** is the 30 days **before** the current window (from 60 to 30 days ago)",
"properties": {
"archivedCurrentWindow": {
"description": "The number of feature toggles that were archived during the current window",
"example": 5,
"type": "number",
},
"archivedPastWindow": {
"description": "The number of feature toggles that were archived during the previous window",
"example": 5,
"type": "number",
},
"avgTimeToProdCurrentWindow": {
"description": "The average time from when a feature was created to when it was enabled in the "production" environment during the current window",
"example": 10,
"type": "number",
},
"avgTimeToProdPastWindow": {
"description": "The average time from when a feature was created to when it was enabled in the "production" environment during the previous window",
"example": 10,
"type": "number",
},
"createdCurrentWindow": {
"description": "The number of feature toggles created during the current window",
"example": 15,
"type": "number",
},
"createdPastWindow": {
"description": "The number of feature toggles created during the previous window",
"example": 15,
"type": "number",
},
"projectActivityCurrentWindow": {
"description": "The number of project events that occurred during the current window",
"example": 100,
"type": "number",
},
"projectActivityPastWindow": {
"description": "The number of project events that occurred during the previous window",
"example": 100,
"type": "number",
},
"projectMembersAddedCurrentWindow": {
"description": "The number of members that were added to the project during the current window",
"example": 1,
"type": "number",
},
},
@ -2563,8 +2670,10 @@ exports[`should serve the OpenAPI spec 1`] = `
},
"projectsSchema": {
"additionalProperties": false,
"description": "An overview of all the projects in the Unleash instance",
"properties": {
"projects": {
"description": "A list of projects in the Unleash instance",
"items": {
"$ref": "#/components/schemas/projectSchema",
},