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

Feat: OpenAPI controller - Bootstrap UI (#1773)

* rename bootstrap ui controller

* sort openapi schema imports

* add bootstrap ui json schema

* test bootstrap ui schema

* openapi bootstrap ui route

* fix: bootstrap ui schema type

* bootstrap ui e2e test

* simplify bootstrap-ui testing mock

* fix: update after review
This commit is contained in:
Tymoteusz Czech 2022-06-30 14:21:40 +02:00 committed by GitHub
parent a607dea284
commit 2729999bed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 564 additions and 93 deletions

View File

@ -1,118 +1,120 @@
import { OpenAPIV3 } from 'openapi-types';
import { addonParameterSchema } from './spec/addon-parameter-schema';
import { addonSchema } from './spec/addon-schema';
import { addonsSchema } from './spec/addons-schema';
import { addonTypeSchema } from './spec/addon-type-schema';
import { apiTokenSchema } from './spec/api-token-schema';
import { apiTokensSchema } from './spec/api-tokens-schema';
import { applicationSchema } from './spec/application-schema';
import { applicationsSchema } from './spec/applications-schema';
import { bootstrapUiSchema } from './spec/bootstrap-ui-schema';
import { changePasswordSchema } from './spec/change-password-schema';
import { clientApplicationSchema } from './spec/client-application-schema';
import { clientFeatureSchema } from './spec/client-feature-schema';
import { clientFeaturesQuerySchema } from './spec/client-features-query-schema';
import { clientFeaturesSchema } from './spec/client-features-schema';
import { clientMetricsSchema } from './spec/client-metrics-schema';
import { clientVariantSchema } from './spec/client-variant-schema';
import { cloneFeatureSchema } from './spec/clone-feature-schema';
import { constraintSchema } from './spec/constraint-schema';
import { contextFieldSchema } from './spec/context-field-schema';
import { contextFieldsSchema } from './spec/context-fields-schema';
import { createApiTokenSchema } from './spec/create-api-token-schema';
import { createFeatureSchema } from './spec/create-feature-schema';
import { createUserSchema } from './spec/create-user-schema';
import { createFeatureStrategySchema } from './spec/create-feature-strategy-schema';
import { createUserSchema } from './spec/create-user-schema';
import { dateSchema } from './spec/date-schema';
import { emailSchema } from './spec/email-schema';
import { environmentSchema } from './spec/environment-schema';
import { environmentsSchema } from './spec/environments-schema';
import { eventSchema } from './spec/event-schema';
import { eventsSchema } from './spec/events-schema';
import { exportParametersSchema } from './spec/export-parameters-schema';
import { featureEnvironmentMetricsSchema } from './spec/feature-environment-metrics-schema';
import { featureEnvironmentSchema } from './spec/feature-environment-schema';
import { featureEventsSchema } from './spec/feature-events-schema';
import { featureMetricsSchema } from './spec/feature-metrics-schema';
import { featureSchema } from './spec/feature-schema';
import { featuresSchema } from './spec/features-schema';
import { featureStrategySchema } from './spec/feature-strategy-schema';
import { featureStrategySegmentSchema } from './spec/feature-strategy-segment-schema';
import { featureTagSchema } from './spec/feature-tag-schema';
import { featureTypeSchema } from './spec/feature-type-schema';
import { featureTypesSchema } from './spec/feature-types-schema';
import { featureUsageSchema } from './spec/feature-usage-schema';
import { featureVariantsSchema } from './spec/feature-variants-schema';
import { featuresSchema } from './spec/features-schema';
import { feedbackSchema } from './spec/feedback-schema';
import { healthCheckSchema } from './spec/health-check-schema';
import { healthOverviewSchema } from './spec/health-overview-schema';
import { healthReportSchema } from './spec/health-report-schema';
import { idSchema } from './spec/id-schema';
import { legalValueSchema } from './spec/legal-value-schema';
import { loginSchema } from './spec/login-schema';
import { idSchema } from './spec/id-schema';
import { mapValues } from '../util/map-values';
import { nameSchema } from './spec/name-schema';
import { meSchema } from './spec/me-schema';
import { nameSchema } from './spec/name-schema';
import { omitKeys } from '../util/omit-keys';
import { overrideSchema } from './spec/override-schema';
import { parametersSchema } from './spec/parameters-schema';
import { passwordSchema } from './spec/password-schema';
import { patchSchema } from './spec/patch-schema';
import { patchesSchema } from './spec/patches-schema';
import { patchSchema } from './spec/patch-schema';
import { permissionSchema } from './spec/permission-schema';
import { projectEnvironmentSchema } from './spec/project-environment-schema';
import { projectSchema } from './spec/project-schema';
import { projectsSchema } from './spec/projects-schema';
import { resetPasswordSchema } from './spec/reset-password-schema';
import { roleSchema } from './spec/role-schema';
import { segmentSchema } from './spec/segment-schema';
import { sortOrderSchema } from './spec/sort-order-schema';
import { splashSchema } from './spec/splash-schema';
import { stateSchema } from './spec/state-schema';
import { strategiesSchema } from './spec/strategies-schema';
import { strategySchema } from './spec/strategy-schema';
import { tagSchema } from './spec/tag-schema';
import { tagsSchema } from './spec/tags-schema';
import { tagTypeSchema } from './spec/tag-type-schema';
import { tagTypesSchema } from './spec/tag-types-schema';
import { tagWithVersionSchema } from './spec/tag-with-version-schema';
import { tokenUserSchema } from './spec/token-user-schema';
import { uiConfigSchema } from './spec/ui-config-schema';
import { updateApiTokenSchema } from './spec/update-api-token-schema';
import { updateFeatureSchema } from './spec/update-feature-schema';
import { updateFeatureStrategySchema } from './spec/update-feature-strategy-schema';
import { updateApiTokenSchema } from './spec/update-api-token-schema';
import { updateTagTypeSchema } from './spec/update-tag-type-schema';
import { upsertContextFieldSchema } from './spec/upsert-context-field-schema';
import { updateUserSchema } from './spec/update-user-schema';
import { upsertContextFieldSchema } from './spec/upsert-context-field-schema';
import { upsertStrategySchema } from './spec/upsert-strategy-schema';
import { userSchema } from './spec/user-schema';
import { usersSchema } from './spec/users-schema';
import { usersSearchSchema } from './spec/users-search-schema';
import { validatePasswordSchema } from './spec/validate-password-schema';
import { validateTagTypeSchema } from './spec/validate-tag-type-schema';
import { variantSchema } from './spec/variant-schema';
import { variantsSchema } from './spec/variants-schema';
import { versionSchema } from './spec/version-schema';
import { featureEnvironmentMetricsSchema } from './spec/feature-environment-metrics-schema';
import { featureUsageSchema } from './spec/feature-usage-schema';
import { featureMetricsSchema } from './spec/feature-metrics-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 { applicationsSchema } from './spec/applications-schema';
import { tagWithVersionSchema } from './spec/tag-with-version-schema';
import { tokenUserSchema } from './spec/token-user-schema';
import { changePasswordSchema } from './spec/change-password-schema';
import { validatePasswordSchema } from './spec/validate-password-schema';
import { resetPasswordSchema } from './spec/reset-password-schema';
import { featureStrategySegmentSchema } from './spec/feature-strategy-segment-schema';
import { segmentSchema } from './spec/segment-schema';
import { stateSchema } from './spec/state-schema';
import { featureTagSchema } from './spec/feature-tag-schema';
import { exportParametersSchema } from './spec/export-parameters-schema';
import { emailSchema } from './spec/email-schema';
import { strategySchema } from './spec/strategy-schema';
import { strategiesSchema } from './spec/strategies-schema';
import { upsertStrategySchema } from './spec/upsert-strategy-schema';
import { clientFeaturesQuerySchema } from './spec/client-features-query-schema';
import { clientFeatureSchema } from './spec/client-feature-schema';
import { clientFeaturesSchema } from './spec/client-features-schema';
import { eventSchema } from './spec/event-schema';
import { eventsSchema } from './spec/events-schema';
import { featureEventsSchema } from './spec/feature-events-schema';
import { clientApplicationSchema } from './spec/client-application-schema';
import { clientMetricsSchema } from './spec/client-metrics-schema';
import { dateSchema } from './spec/date-schema';
import { clientVariantSchema } from './spec/client-variant-schema';
import { IServerOption } from '../types';
import { URL } from 'url';
// All schemas in `openapi/spec` should be listed here.
export const schemas = {
addonParameterSchema,
addonSchema,
addonsSchema,
addonTypeSchema,
addonParameterSchema,
apiTokenSchema,
apiTokensSchema,
applicationSchema,
applicationsSchema,
clientApplicationSchema,
clientMetricsSchema,
cloneFeatureSchema,
clientFeatureSchema,
clientFeaturesSchema,
clientVariantSchema,
clientFeaturesQuerySchema,
bootstrapUiSchema,
changePasswordSchema,
clientApplicationSchema,
clientFeatureSchema,
clientFeaturesQuerySchema,
clientFeaturesSchema,
clientMetricsSchema,
clientVariantSchema,
cloneFeatureSchema,
constraintSchema,
contextFieldSchema,
contextFieldsSchema,
@ -127,33 +129,33 @@ export const schemas = {
eventSchema,
eventsSchema,
exportParametersSchema,
featureEnvironmentSchema,
featureEnvironmentMetricsSchema,
featureEnvironmentSchema,
featureEventsSchema,
featureSchema,
featureMetricsSchema,
featureUsageSchema,
featureSchema,
featuresSchema,
featureStrategySchema,
featureStrategySegmentSchema,
featureTagSchema,
featureTypeSchema,
featureTypesSchema,
featureUsageSchema,
featureVariantsSchema,
featuresSchema,
feedbackSchema,
healthCheckSchema,
healthOverviewSchema,
healthReportSchema,
idSchema,
legalValueSchema,
loginSchema,
nameSchema,
idSchema,
meSchema,
nameSchema,
overrideSchema,
parametersSchema,
passwordSchema,
patchSchema,
patchesSchema,
patchSchema,
permissionSchema,
projectEnvironmentSchema,
projectSchema,
@ -167,24 +169,24 @@ export const schemas = {
strategiesSchema,
strategySchema,
tagSchema,
tagWithVersionSchema,
tagsSchema,
tagTypeSchema,
tagTypesSchema,
tagWithVersionSchema,
tokenUserSchema,
uiConfigSchema,
updateApiTokenSchema,
updateFeatureSchema,
updateFeatureStrategySchema,
updateApiTokenSchema,
updateTagTypeSchema,
updateUserSchema,
upsertContextFieldSchema,
upsertStrategySchema,
validatePasswordSchema,
validateTagTypeSchema,
updateUserSchema,
userSchema,
usersSchema,
usersSearchSchema,
validatePasswordSchema,
validateTagTypeSchema,
variantSchema,
variantsSchema,
versionSchema,

View File

@ -0,0 +1,157 @@
import { validateSchema } from '../validate';
import { BootstrapUiSchema } from './bootstrap-ui-schema';
test('bootstrapUiSchema', () => {
const data: BootstrapUiSchema = {
uiConfig: {
flags: { E: true },
authenticationType: 'open-source',
unleashUrl: 'http://localhost:4242',
version: '4.14.0-beta.0',
baseUriPath: '',
versionInfo: {
current: { oss: '4.14.0-beta.0', enterprise: '' },
latest: {},
isLatest: true,
instanceId: '51c9190a-4ff5-4f47-b73a-7aebe06f9331',
},
},
user: {
isAPI: false,
id: 1,
username: 'admin',
imageUrl:
'https://gravatar.com/avatar/21232f297a57a5a743894a0e4a801fc3?size=42&default=retro',
seenAt: '2022-06-27T12:19:15.838Z',
loginAttempts: 0,
createdAt: '2022-04-08T10:59:25.072Z',
permissions: [
{ permission: 'READ_API_TOKEN' },
{
project: 'default',
environment: 'staging',
permission: 'CREATE_FEATURE_STRATEGY',
},
{
project: 'default',
environment: 'staging',
permission: 'UPDATE_FEATURE_STRATEGY',
},
{ project: 'default', permission: 'UPDATE_FEATURE' },
],
},
email: false,
context: [
{
name: 'appName',
description: 'Allows you to constrain on application name',
stickiness: false,
sortOrder: 2,
legalValues: [],
createdAt: '2022-04-08T10:59:24.374Z',
},
{
name: 'currentTime',
description: '',
stickiness: false,
sortOrder: 10,
legalValues: [],
createdAt: '2022-05-18T08:15:18.917Z',
},
{
name: 'environment',
description:
'Allows you to constrain on application environment',
stickiness: false,
sortOrder: 0,
legalValues: [],
createdAt: '2022-04-08T10:59:24.374Z',
},
{
name: 'userId',
description: 'Allows you to constrain on userId',
stickiness: false,
sortOrder: 1,
legalValues: [],
createdAt: '2022-04-08T10:59:24.374Z',
},
],
featureTypes: [
{
id: 'release',
name: 'Release',
description:
'Release feature toggles are used to release new features.',
lifetimeDays: 40,
},
{
id: 'experiment',
name: 'Experiment',
description:
'Experiment feature toggles are used to test and verify multiple different versions of a feature.',
lifetimeDays: 40,
},
{
id: 'operational',
name: 'Operational',
description:
'Operational feature toggles are used to control aspects of a rollout.',
lifetimeDays: 7,
},
],
tagTypes: [
{
name: 'simple',
description: 'Used to simplify filtering of features',
icon: '#',
},
{ name: 'hashtag', description: '', icon: null },
],
strategies: [
{
displayName: 'Standard',
name: 'default',
editable: false,
description:
'The standard strategy is strictly on / off for your entire userbase.',
parameters: [],
deprecated: false,
},
{
displayName: null,
name: 'gradualRolloutRandom',
editable: true,
description:
'Randomly activate the feature toggle. No stickiness.',
parameters: [
{
name: 'percentage',
type: 'percentage',
description: '',
required: false,
},
],
deprecated: true,
},
],
projects: [
{
name: 'Default',
id: 'default',
description: 'Default project',
health: 74,
featureCount: 10,
memberCount: 3,
updatedAt: '2022-06-28T17:33:53.963Z',
},
],
};
expect(
validateSchema('#/components/schemas/bootstrapUiSchema', {}),
).not.toBeUndefined();
expect(
validateSchema('#/components/schemas/bootstrapUiSchema', data),
).toBeUndefined();
});

View File

@ -0,0 +1,94 @@
import { FromSchema } from 'json-schema-to-ts';
import { uiConfigSchema } from './ui-config-schema';
import { userSchema } from './user-schema';
import { permissionSchema } from './permission-schema';
import { featureTypeSchema } from './feature-type-schema';
import { tagTypeSchema } from './tag-type-schema';
import { contextFieldSchema } from './context-field-schema';
import { strategySchema } from './strategy-schema';
import { projectSchema } from './project-schema';
import { versionSchema } from './version-schema';
import { legalValueSchema } from './legal-value-schema';
export const bootstrapUiSchema = {
$id: '#/components/schemas/bootstrapUiSchema',
type: 'object',
additionalProperties: false,
required: [
'uiConfig',
'user',
'email',
'context',
'featureTypes',
'tagTypes',
'strategies',
'projects',
],
properties: {
uiConfig: {
$ref: '#/components/schemas/uiConfigSchema',
},
user: {
type: 'object',
required: [...userSchema.required],
properties: {
...userSchema.properties,
permissions: {
type: 'array',
items: {
$ref: '#/components/schemas/permissionSchema',
},
},
},
},
email: {
type: 'boolean',
},
context: {
type: 'array',
items: {
$ref: '#/components/schemas/contextFieldSchema',
},
},
featureTypes: {
type: 'array',
items: {
$ref: '#/components/schemas/featureTypeSchema',
},
},
tagTypes: {
type: 'array',
items: {
$ref: '#/components/schemas/tagTypeSchema',
},
},
strategies: {
type: 'array',
items: {
$ref: '#/components/schemas/strategySchema',
},
},
projects: {
type: 'array',
items: {
$ref: '#/components/schemas/projectSchema',
},
},
},
components: {
schemas: {
uiConfigSchema,
userSchema,
permissionSchema,
contextFieldSchema,
featureTypeSchema,
tagTypeSchema,
strategySchema,
projectSchema,
versionSchema,
legalValueSchema,
},
},
} as const;
export type BootstrapUiSchema = FromSchema<typeof bootstrapUiSchema>;

View File

@ -14,6 +14,7 @@ export const tagTypeSchema = {
},
icon: {
type: 'string',
nullable: true,
},
},
components: {},

View File

@ -5,15 +5,7 @@ export const uiConfigSchema = {
$id: '#/components/schemas/uiConfigSchema',
type: 'object',
additionalProperties: false,
required: [
'version',
'unleashUrl',
'baseUriPath',
'versionInfo',
'disablePasswordAuth',
'segmentValuesLimit',
'strategySegmentsLimit',
],
required: ['version', 'unleashUrl', 'baseUriPath', 'versionInfo'],
properties: {
slogan: {
type: 'string',

View File

@ -0,0 +1,63 @@
import supertest from 'supertest';
import { createTestConfig } from '../../../test/config/test-config';
import { randomId } from '../../util/random-id';
import createStores from '../../../test/fixtures/store';
import getApp from '../../app';
import { createServices } from '../../services';
const uiConfig = {
headerBackground: 'red',
slogan: 'hello',
};
async function getSetup() {
const base = `/random${randomId()}`;
const config = createTestConfig({
server: { baseUriPath: base },
ui: uiConfig,
});
const stores = createStores();
const services = createServices(stores, config);
const app = await getApp(config, stores, services);
return {
base,
request: supertest(app),
destroy: () => {
services.versionService.destroy();
services.clientInstanceService.destroy();
services.apiTokenService.destroy();
},
};
}
let request;
let base;
let destroy;
beforeEach(async () => {
const setup = await getSetup();
request = setup.request;
base = setup.base;
destroy = setup.destroy;
});
afterEach(() => {
destroy();
});
test('should get ui config', async () => {
const { body } = await request
.get(`${base}/api/admin/ui-bootstrap`)
.expect('Content-Type', /json/)
.expect(200);
expect(body.uiConfig.slogan).toEqual('hello');
expect(body.email).toEqual(false);
expect(body.user).toHaveProperty('permissions');
expect(body.context).toBeInstanceOf(Array);
expect(body.tagTypes).toBeInstanceOf(Array);
expect(body.strategies).toBeInstanceOf(Array);
expect(body.projects).toBeInstanceOf(Array);
});

View File

@ -19,8 +19,20 @@ import { ITagType } from '../../types/stores/tag-type-store';
import { IStrategy } from '../../types/stores/strategy-store';
import { IProject } from '../../types/model';
import { IUserPermission } from '../../types/stores/access-store';
import { OpenApiService } from '../../services/openapi-service';
import { NONE } from '../../types/permissions';
import { createResponseSchema } from '../../openapi';
import {
BootstrapUiSchema,
bootstrapUiSchema,
} from '../../openapi/spec/bootstrap-ui-schema';
import { serializeDates } from '../../types/serialize-dates';
class BootstrapController extends Controller {
/**
* Provides admin UI configuration.
* Not to be confused with SDK bootstrapping.
*/
class BootstrapUIController extends Controller {
private logger: Logger;
private accessService: AccessService;
@ -39,6 +51,8 @@ class BootstrapController extends Controller {
private versionService: VersionService;
private openApiService: OpenApiService;
constructor(
config: IUnleashConfig,
{
@ -50,6 +64,7 @@ class BootstrapController extends Controller {
emailService,
versionService,
featureTypeService,
openApiService,
}: Pick<
IUnleashServices,
| 'contextService'
@ -60,6 +75,7 @@ class BootstrapController extends Controller {
| 'emailService'
| 'versionService'
| 'featureTypeService'
| 'openApiService'
>,
) {
super(config);
@ -71,15 +87,30 @@ class BootstrapController extends Controller {
this.featureTypeService = featureTypeService;
this.emailService = emailService;
this.versionService = versionService;
this.openApiService = openApiService;
this.logger = config.getLogger(
'routes/admin-api/bootstrap-controller.ts',
);
this.get('/', this.bootstrap);
this.logger = config.getLogger('routes/admin-api/bootstrap-ui.ts');
this.route({
method: 'get',
path: '',
handler: this.bootstrap,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['other'],
operationId: 'getBootstrapUiData',
responses: {
202: createResponseSchema('bootstrapUiSchema'),
},
}),
],
});
}
async bootstrap(req: AuthedRequest, res: Response): Promise<void> {
async bootstrap(
req: AuthedRequest,
res: Response<BootstrapUiSchema>,
): Promise<void> {
const jobs: [
Promise<IContextField[]>,
Promise<IFeatureType[]>,
@ -117,18 +148,26 @@ class BootstrapController extends Controller {
versionInfo,
};
res.json({
uiConfig,
user: { ...req.user, permissions: userPermissions },
email: this.emailService.isEnabled(),
context,
featureTypes,
tagTypes,
strategies,
projects,
});
this.openApiService.respondWithValidation(
200,
res,
bootstrapUiSchema.$id,
{
uiConfig,
user: {
...serializeDates(req.user),
permissions: userPermissions,
},
email: this.emailService.isEnabled(),
context: serializeDates(context),
featureTypes,
tagTypes,
strategies,
projects: serializeDates(projects),
},
);
}
}
export default BootstrapController;
module.exports = BootstrapController;
export default BootstrapUIController;
module.exports = BootstrapUIController;

View File

@ -12,7 +12,7 @@ import UserController from './user';
import ConfigController from './config';
import { ContextController } from './context';
import ClientMetricsController from './client-metrics';
import BootstrapController from './bootstrap';
import BootstrapUIController from './bootstrap-ui';
import StateController from './state';
import TagController from './tag';
import TagTypeController from './tag-type';
@ -65,7 +65,7 @@ class AdminApi extends Controller {
);
this.app.use(
'/ui-bootstrap',
new BootstrapController(config, services).router,
new BootstrapUIController(config, services).router,
);
this.app.use(
'/context',

View File

@ -293,6 +293,111 @@ Object {
},
"type": "object",
},
"bootstrapUiSchema": Object {
"additionalProperties": false,
"properties": Object {
"context": Object {
"items": Object {
"$ref": "#/components/schemas/contextFieldSchema",
},
"type": "array",
},
"email": Object {
"type": "boolean",
},
"featureTypes": Object {
"items": Object {
"$ref": "#/components/schemas/featureTypeSchema",
},
"type": "array",
},
"projects": Object {
"items": Object {
"$ref": "#/components/schemas/projectSchema",
},
"type": "array",
},
"strategies": Object {
"items": Object {
"$ref": "#/components/schemas/strategySchema",
},
"type": "array",
},
"tagTypes": Object {
"items": Object {
"$ref": "#/components/schemas/tagTypeSchema",
},
"type": "array",
},
"uiConfig": Object {
"$ref": "#/components/schemas/uiConfigSchema",
},
"user": Object {
"properties": Object {
"createdAt": Object {
"format": "date-time",
"type": "string",
},
"email": Object {
"type": "string",
},
"emailSent": Object {
"type": "boolean",
},
"id": Object {
"type": "number",
},
"imageUrl": Object {
"type": "string",
},
"inviteLink": Object {
"type": "string",
},
"isAPI": Object {
"type": "boolean",
},
"loginAttempts": Object {
"type": "number",
},
"name": Object {
"type": "string",
},
"permissions": Object {
"items": Object {
"$ref": "#/components/schemas/permissionSchema",
},
"type": "array",
},
"rootRole": Object {
"type": "number",
},
"seenAt": Object {
"format": "date-time",
"nullable": true,
"type": "string",
},
"username": Object {
"type": "string",
},
},
"required": Array [
"id",
],
"type": "object",
},
},
"required": Array [
"uiConfig",
"user",
"email",
"context",
"featureTypes",
"tagTypes",
"strategies",
"projects",
],
"type": "object",
},
"changePasswordSchema": Object {
"additionalProperties": false,
"properties": Object {
@ -1962,6 +2067,7 @@ Object {
"type": "string",
},
"icon": Object {
"nullable": true,
"type": "string",
},
"name": Object {
@ -2122,9 +2228,6 @@ Object {
"unleashUrl",
"baseUriPath",
"versionInfo",
"disablePasswordAuth",
"segmentValuesLimit",
"strategySegmentsLimit",
],
"type": "object",
},
@ -5167,6 +5270,26 @@ If the provided project does not exist, the list of events will be empty.",
],
},
},
"/api/admin/ui-bootstrap": Object {
"get": Object {
"operationId": "getBootstrapUiData",
"responses": Object {
"202": Object {
"content": Object {
"application/json": Object {
"schema": Object {
"$ref": "#/components/schemas/bootstrapUiSchema",
},
},
},
"description": "bootstrapUiSchema",
},
},
"tags": Array [
"other",
],
},
},
"/api/admin/ui-config": Object {
"get": Object {
"operationId": "getUIConfig",