mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-01 13:47:27 +02:00
chore(adminapi)!: Remove deprecated project overview (#10069)
BREAKING CHANGE: As part of the preparation for a new major (7.0) this removes /api/admin/projects/{projectId} endpoint. It has been deprecated since 5.8, and we don't use it anymore in our frontend.
This commit is contained in:
parent
cf87f8cfe1
commit
8d7a0fdd7f
@ -203,30 +203,6 @@ test('Trying to add a strategy configuration to environment not connected to fla
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Can get project overview', async () => {
|
|
||||||
await app.request
|
|
||||||
.post('/api/admin/projects/default/features')
|
|
||||||
.send({
|
|
||||||
name: 'project-overview',
|
|
||||||
enabled: false,
|
|
||||||
strategies: [{ name: 'default' }],
|
|
||||||
})
|
|
||||||
.set('Content-Type', 'application/json')
|
|
||||||
.expect(201)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body.name).toBe('project-overview');
|
|
||||||
expect(res.body.createdAt).toBeTruthy();
|
|
||||||
});
|
|
||||||
await app.request
|
|
||||||
.get('/api/admin/projects/default')
|
|
||||||
.expect(200)
|
|
||||||
.expect((r) => {
|
|
||||||
expect(r.body.name).toBe('Default');
|
|
||||||
expect(r.body.features).toHaveLength(2);
|
|
||||||
expect(r.body.members).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should list dependencies and children', async () => {
|
test('should list dependencies and children', async () => {
|
||||||
const parent = uuidv4();
|
const parent = uuidv4();
|
||||||
const child = uuidv4();
|
const child = uuidv4();
|
||||||
@ -390,76 +366,6 @@ test('Can get features for project', async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Project overview includes environment connected to feature', async () => {
|
|
||||||
await app.request
|
|
||||||
.post('/api/admin/projects/default/features')
|
|
||||||
.send({
|
|
||||||
name: 'com.test.environment',
|
|
||||||
enabled: false,
|
|
||||||
strategies: [{ name: 'default' }],
|
|
||||||
})
|
|
||||||
.set('Content-Type', 'application/json')
|
|
||||||
.expect(201)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body.name).toBe('com.test.environment');
|
|
||||||
expect(res.body.createdAt).toBeTruthy();
|
|
||||||
});
|
|
||||||
await db.stores.environmentStore.create({
|
|
||||||
name: 'project-overview',
|
|
||||||
type: 'production',
|
|
||||||
});
|
|
||||||
await app.request
|
|
||||||
.post('/api/admin/projects/default/environments')
|
|
||||||
.send({ environment: 'project-overview' })
|
|
||||||
.expect(200);
|
|
||||||
return app.request
|
|
||||||
.get('/api/admin/projects/default')
|
|
||||||
.expect(200)
|
|
||||||
.expect((r) => {
|
|
||||||
expect(r.body.features[0].environments[0].name).toBe(DEFAULT_ENV);
|
|
||||||
expect(r.body.features[0].environments[1].name).toBe(
|
|
||||||
'project-overview',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Disconnecting environment from project, removes environment from features in project overview', async () => {
|
|
||||||
await app.request
|
|
||||||
.post('/api/admin/projects/default/features')
|
|
||||||
.send({
|
|
||||||
name: 'com.test.disconnect.environment',
|
|
||||||
enabled: false,
|
|
||||||
strategies: [{ name: 'default' }],
|
|
||||||
})
|
|
||||||
.set('Content-Type', 'application/json')
|
|
||||||
.expect(201)
|
|
||||||
.expect((res) => {
|
|
||||||
expect(res.body.name).toBe('com.test.disconnect.environment');
|
|
||||||
expect(res.body.createdAt).toBeTruthy();
|
|
||||||
});
|
|
||||||
await db.stores.environmentStore.create({
|
|
||||||
name: 'dis-project-overview',
|
|
||||||
type: 'production',
|
|
||||||
});
|
|
||||||
await app.request
|
|
||||||
.post('/api/admin/projects/default/environments')
|
|
||||||
.send({ environment: 'dis-project-overview' })
|
|
||||||
.expect(200);
|
|
||||||
await app.request
|
|
||||||
.delete('/api/admin/projects/default/environments/dis-project-overview')
|
|
||||||
.expect(200);
|
|
||||||
return app.request
|
|
||||||
.get('/api/admin/projects/default')
|
|
||||||
.expect(200)
|
|
||||||
.expect((r) => {
|
|
||||||
expect(
|
|
||||||
r.body.features.some(
|
|
||||||
(e) => e.environment === 'dis-project-overview',
|
|
||||||
),
|
|
||||||
).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Can enable/disable environment for feature with strategies', async () => {
|
test('Can enable/disable environment for feature with strategies', async () => {
|
||||||
const envName = 'enable-feature-environment';
|
const envName = 'enable-feature-environment';
|
||||||
const featureName = 'com.test.enable.environment';
|
const featureName = 'com.test.enable.environment';
|
||||||
@ -756,7 +662,7 @@ describe('Interacting with features using project IDs that belong to other proje
|
|||||||
|
|
||||||
// ensure the new project has been created
|
// ensure the new project has been created
|
||||||
await app.request
|
await app.request
|
||||||
.get(`/api/admin/projects/${otherProject}`)
|
.get(`/api/admin/projects/${otherProject}/health-report`)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
// create flag in default project
|
// create flag in default project
|
||||||
|
@ -15,8 +15,6 @@ import type ProjectService from './project-service.js';
|
|||||||
import VariantsController from '../../routes/admin-api/project/variants.js';
|
import VariantsController from '../../routes/admin-api/project/variants.js';
|
||||||
import {
|
import {
|
||||||
createResponseSchema,
|
createResponseSchema,
|
||||||
type DeprecatedProjectOverviewSchema,
|
|
||||||
deprecatedProjectOverviewSchema,
|
|
||||||
outdatedSdksSchema,
|
outdatedSdksSchema,
|
||||||
type OutdatedSdksSchema,
|
type OutdatedSdksSchema,
|
||||||
type ProjectDoraMetricsSchema,
|
type ProjectDoraMetricsSchema,
|
||||||
@ -95,29 +93,6 @@ export default class ProjectController extends Controller {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route({
|
|
||||||
method: 'get',
|
|
||||||
path: '/:projectId',
|
|
||||||
handler: this.getDeprecatedProjectOverview,
|
|
||||||
permission: NONE,
|
|
||||||
middleware: [
|
|
||||||
this.openApiService.validPath({
|
|
||||||
tags: ['Projects'],
|
|
||||||
operationId: 'getDeprecatedProjectOverview',
|
|
||||||
summary: 'Get an overview of a project. (deprecated)',
|
|
||||||
deprecated: true,
|
|
||||||
description:
|
|
||||||
'This endpoint returns an overview of the specified projects stats, project health, number of members, which environments are configured, and the features in the project.',
|
|
||||||
responses: {
|
|
||||||
200: createResponseSchema(
|
|
||||||
'deprecatedProjectOverviewSchema',
|
|
||||||
),
|
|
||||||
...getStandardResponses(401, 403, 404),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
this.route({
|
this.route({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
path: '/:projectId/overview',
|
path: '/:projectId/overview',
|
||||||
@ -260,27 +235,6 @@ export default class ProjectController extends Controller {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDeprecatedProjectOverview(
|
|
||||||
req: IAuthRequest<IProjectParam, unknown, unknown, IArchivedQuery>,
|
|
||||||
res: Response<DeprecatedProjectOverviewSchema>,
|
|
||||||
): Promise<void> {
|
|
||||||
const { projectId } = req.params;
|
|
||||||
const { archived } = req.query;
|
|
||||||
const { user } = req;
|
|
||||||
const overview = await this.projectService.getProjectHealth(
|
|
||||||
projectId,
|
|
||||||
archived,
|
|
||||||
user.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.openApiService.respondWithValidation(
|
|
||||||
200,
|
|
||||||
res,
|
|
||||||
deprecatedProjectOverviewSchema.$id,
|
|
||||||
serializeDates(overview),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProjectOverview(
|
async getProjectOverview(
|
||||||
req: IAuthRequest<IProjectParam, unknown, unknown, IArchivedQuery>,
|
req: IAuthRequest<IProjectParam, unknown, unknown, IArchivedQuery>,
|
||||||
res: Response<ProjectOverviewSchema>,
|
res: Response<ProjectOverviewSchema>,
|
||||||
|
@ -2,15 +2,12 @@ import dbInit, {
|
|||||||
type ITestDb,
|
type ITestDb,
|
||||||
} from '../../../test/e2e/helpers/database-init.js';
|
} from '../../../test/e2e/helpers/database-init.js';
|
||||||
import {
|
import {
|
||||||
insertFeatureEnvironmentsLastSeen,
|
|
||||||
insertLastSeenAt,
|
|
||||||
type IUnleashTest,
|
type IUnleashTest,
|
||||||
setupAppWithCustomConfig,
|
setupAppWithCustomConfig,
|
||||||
} from '../../../test/e2e/helpers/test-helper.js';
|
} from '../../../test/e2e/helpers/test-helper.js';
|
||||||
import getLogger from '../../../test/fixtures/no-logger.js';
|
import getLogger from '../../../test/fixtures/no-logger.js';
|
||||||
|
|
||||||
import type { IProjectStore } from '../../types/index.js';
|
import type { IProjectStore } from '../../types/index.js';
|
||||||
import { DEFAULT_ENV } from '../../util/index.js';
|
|
||||||
|
|
||||||
let app: IUnleashTest;
|
let app: IUnleashTest;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
@ -45,76 +42,6 @@ afterAll(async () => {
|
|||||||
await db.destroy();
|
await db.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should report has strategies and enabled strategies', async () => {
|
|
||||||
const app = await setupAppWithCustomConfig(
|
|
||||||
db.stores,
|
|
||||||
{
|
|
||||||
experimental: {
|
|
||||||
flags: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
db.rawDatabase,
|
|
||||||
);
|
|
||||||
await app.createFeature('featureWithStrategies');
|
|
||||||
await app.createFeature('featureWithoutStrategies');
|
|
||||||
await app.createFeature('featureWithDisabledStrategies');
|
|
||||||
await app.addStrategyToFeatureEnv(
|
|
||||||
{
|
|
||||||
name: 'default',
|
|
||||||
},
|
|
||||||
DEFAULT_ENV,
|
|
||||||
'featureWithStrategies',
|
|
||||||
);
|
|
||||||
await app.addStrategyToFeatureEnv(
|
|
||||||
{
|
|
||||||
name: 'default',
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
DEFAULT_ENV,
|
|
||||||
'featureWithDisabledStrategies',
|
|
||||||
);
|
|
||||||
|
|
||||||
const { body } = await app.request
|
|
||||||
.get('/api/admin/projects/default')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
name: 'featureWithStrategies',
|
|
||||||
environments: [
|
|
||||||
{
|
|
||||||
name: 'default',
|
|
||||||
hasStrategies: true,
|
|
||||||
hasEnabledStrategies: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'featureWithoutStrategies',
|
|
||||||
environments: [
|
|
||||||
{
|
|
||||||
name: 'default',
|
|
||||||
hasStrategies: false,
|
|
||||||
hasEnabledStrategies: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'featureWithDisabledStrategies',
|
|
||||||
environments: [
|
|
||||||
{
|
|
||||||
name: 'default',
|
|
||||||
hasStrategies: true,
|
|
||||||
hasEnabledStrategies: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Should ONLY return default project', async () => {
|
test('Should ONLY return default project', async () => {
|
||||||
projectStore.create({
|
projectStore.create({
|
||||||
id: 'test2',
|
id: 'test2',
|
||||||
@ -140,13 +67,6 @@ test('response should include created_at', async () => {
|
|||||||
expect(body.projects[0].createdAt).toBeDefined();
|
expect(body.projects[0].createdAt).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('response for default project should include created_at', async () => {
|
|
||||||
const { body } = await app.request
|
|
||||||
.get('/api/admin/projects/default')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(200);
|
|
||||||
expect(body.createdAt).toBeDefined();
|
|
||||||
});
|
|
||||||
test('response for project overview should include feature type counts', async () => {
|
test('response for project overview should include feature type counts', async () => {
|
||||||
await app.createFeature({
|
await app.createFeature({
|
||||||
name: 'my-new-release-toggle',
|
name: 'my-new-release-toggle',
|
||||||
@ -173,120 +93,3 @@ test('response for project overview should include feature type counts', async (
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('response should include last seen at per environment', async () => {
|
|
||||||
await app.createFeature('my-new-feature-toggle');
|
|
||||||
|
|
||||||
await insertLastSeenAt(
|
|
||||||
'my-new-feature-toggle',
|
|
||||||
db.rawDatabase,
|
|
||||||
'default',
|
|
||||||
testDate,
|
|
||||||
);
|
|
||||||
await insertFeatureEnvironmentsLastSeen(
|
|
||||||
'my-new-feature-toggle',
|
|
||||||
db.rawDatabase,
|
|
||||||
'default',
|
|
||||||
);
|
|
||||||
|
|
||||||
const { body } = await app.request
|
|
||||||
.get('/api/admin/projects/default')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(body.features[0].environments[0].lastSeenAt).toEqual(testDate);
|
|
||||||
expect(body.features[0].lastSeenAt).toEqual(testDate);
|
|
||||||
|
|
||||||
const appWithLastSeenRefactor = await setupAppWithCustomConfig(
|
|
||||||
db.stores,
|
|
||||||
{},
|
|
||||||
db.rawDatabase,
|
|
||||||
);
|
|
||||||
|
|
||||||
const response = await appWithLastSeenRefactor.request
|
|
||||||
.get('/api/admin/projects/default')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.features[0].environments[0].lastSeenAt).toEqual(
|
|
||||||
'2023-10-01T12:34:56.000Z',
|
|
||||||
);
|
|
||||||
expect(response.body.features[0].lastSeenAt).toEqual(
|
|
||||||
'2023-10-01T12:34:56.000Z',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('response should include last seen at per environment for multiple environments', async () => {
|
|
||||||
const appWithLastSeenRefactor = await setupAppWithCustomConfig(
|
|
||||||
db.stores,
|
|
||||||
{},
|
|
||||||
db.rawDatabase,
|
|
||||||
);
|
|
||||||
await app.createFeature('my-new-feature-toggle');
|
|
||||||
|
|
||||||
await db.stores.environmentStore.create({
|
|
||||||
name: 'development',
|
|
||||||
type: 'development',
|
|
||||||
sortOrder: 1,
|
|
||||||
enabled: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.stores.environmentStore.create({
|
|
||||||
name: 'production',
|
|
||||||
type: 'production',
|
|
||||||
sortOrder: 2,
|
|
||||||
enabled: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await appWithLastSeenRefactor.services.projectService.addEnvironmentToProject(
|
|
||||||
'default',
|
|
||||||
'development',
|
|
||||||
);
|
|
||||||
await appWithLastSeenRefactor.services.projectService.addEnvironmentToProject(
|
|
||||||
'default',
|
|
||||||
'production',
|
|
||||||
);
|
|
||||||
|
|
||||||
await appWithLastSeenRefactor.createFeature(
|
|
||||||
'multiple-environment-last-seen-at',
|
|
||||||
);
|
|
||||||
|
|
||||||
await insertLastSeenAt(
|
|
||||||
'multiple-environment-last-seen-at',
|
|
||||||
db.rawDatabase,
|
|
||||||
'default',
|
|
||||||
'2023-10-01T12:32:56.000Z',
|
|
||||||
);
|
|
||||||
await insertLastSeenAt(
|
|
||||||
'multiple-environment-last-seen-at',
|
|
||||||
db.rawDatabase,
|
|
||||||
'development',
|
|
||||||
'2023-10-01T12:34:56.000Z',
|
|
||||||
);
|
|
||||||
await insertLastSeenAt(
|
|
||||||
'multiple-environment-last-seen-at',
|
|
||||||
db.rawDatabase,
|
|
||||||
'production',
|
|
||||||
'2023-10-01T12:33:56.000Z',
|
|
||||||
);
|
|
||||||
|
|
||||||
const { body } = await appWithLastSeenRefactor.request
|
|
||||||
.get('/api/admin/projects/default')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const featureEnvironments = body.features[1].environments;
|
|
||||||
|
|
||||||
const [def, development, production] = featureEnvironments;
|
|
||||||
|
|
||||||
expect(def.name).toBe('default');
|
|
||||||
expect(def.lastSeenAt).toEqual('2023-10-01T12:32:56.000Z');
|
|
||||||
|
|
||||||
expect(development.name).toBe('development');
|
|
||||||
expect(development.lastSeenAt).toEqual('2023-10-01T12:34:56.000Z');
|
|
||||||
|
|
||||||
expect(production.name).toBe('production');
|
|
||||||
expect(production.lastSeenAt).toEqual('2023-10-01T12:33:56.000Z');
|
|
||||||
|
|
||||||
expect(body.features[1].lastSeenAt).toBe('2023-10-01T12:34:56.000Z');
|
|
||||||
});
|
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
import type { DeprecatedProjectOverviewSchema } from './deprecated-project-overview-schema.js';
|
|
||||||
import { validateSchema } from '../validate.js';
|
|
||||||
|
|
||||||
test('deprecatedProjectOverviewSchema', () => {
|
|
||||||
const data: DeprecatedProjectOverviewSchema = {
|
|
||||||
name: 'project',
|
|
||||||
version: 3,
|
|
||||||
featureNaming: {
|
|
||||||
description: 'naming description',
|
|
||||||
example: 'a',
|
|
||||||
pattern: '[aZ]',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(
|
|
||||||
validateSchema(
|
|
||||||
'#/components/schemas/deprecatedProjectOverviewSchema',
|
|
||||||
data,
|
|
||||||
),
|
|
||||||
).toBeUndefined();
|
|
||||||
|
|
||||||
expect(
|
|
||||||
validateSchema(
|
|
||||||
'#/components/schemas/deprecatedProjectOverviewSchema',
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
).toMatchSnapshot();
|
|
||||||
});
|
|
@ -1,155 +0,0 @@
|
|||||||
import type { FromSchema } from 'json-schema-to-ts';
|
|
||||||
import { parametersSchema } from './parameters-schema.js';
|
|
||||||
import { variantSchema } from './variant-schema.js';
|
|
||||||
import { overrideSchema } from './override-schema.js';
|
|
||||||
import { featureStrategySchema } from './feature-strategy-schema.js';
|
|
||||||
import { featureSchema } from './feature-schema.js';
|
|
||||||
import { constraintSchema } from './constraint-schema.js';
|
|
||||||
import { environmentSchema } from './environment-schema.js';
|
|
||||||
import { featureEnvironmentSchema } from './feature-environment-schema.js';
|
|
||||||
import { projectStatsSchema } from './project-stats-schema.js';
|
|
||||||
import { createFeatureStrategySchema } from './create-feature-strategy-schema.js';
|
|
||||||
import { projectEnvironmentSchema } from './project-environment-schema.js';
|
|
||||||
import { createStrategyVariantSchema } from './create-strategy-variant-schema.js';
|
|
||||||
import { strategyVariantSchema } from './strategy-variant-schema.js';
|
|
||||||
import { createFeatureNamingPatternSchema } from './create-feature-naming-pattern-schema.js';
|
|
||||||
import { tagSchema } from './tag-schema.js';
|
|
||||||
|
|
||||||
export const deprecatedProjectOverviewSchema = {
|
|
||||||
$id: '#/components/schemas/deprecatedProjectOverviewSchema',
|
|
||||||
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: 'integer',
|
|
||||||
example: 1,
|
|
||||||
description:
|
|
||||||
'The schema version used to describe the project overview',
|
|
||||||
},
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
defaultStickiness: {
|
|
||||||
type: 'string',
|
|
||||||
example: 'userId',
|
|
||||||
description:
|
|
||||||
'A default stickiness for the project affecting the default stickiness value for variants and Gradual Rollout strategy',
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: 'string',
|
|
||||||
enum: ['open', 'protected', 'private'],
|
|
||||||
example: 'open',
|
|
||||||
description:
|
|
||||||
"The project's [collaboration mode](https://docs.getunleash.io/reference/project-collaboration-mode). Determines whether non-project members can submit change requests or not.",
|
|
||||||
},
|
|
||||||
featureLimit: {
|
|
||||||
type: 'number',
|
|
||||||
nullable: true,
|
|
||||||
example: 100,
|
|
||||||
description:
|
|
||||||
'A limit on the number of features allowed in the project. Null if no limit.',
|
|
||||||
},
|
|
||||||
featureNaming: {
|
|
||||||
$ref: '#/components/schemas/createFeatureNamingPatternSchema',
|
|
||||||
},
|
|
||||||
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#project-status) on a scale from 0 to 100",
|
|
||||||
},
|
|
||||||
environments: {
|
|
||||||
type: 'array',
|
|
||||||
items: {
|
|
||||||
$ref: '#/components/schemas/projectEnvironmentSchema',
|
|
||||||
},
|
|
||||||
example: [
|
|
||||||
{ environment: 'development' },
|
|
||||||
{
|
|
||||||
environment: 'production',
|
|
||||||
defaultStrategy: {
|
|
||||||
name: 'flexibleRollout',
|
|
||||||
constraints: [],
|
|
||||||
parameters: {
|
|
||||||
rollout: '50',
|
|
||||||
stickiness: 'customAppName',
|
|
||||||
groupId: 'stickytoggle',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
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',
|
|
||||||
description: 'When the project was last updated.',
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
type: 'string',
|
|
||||||
format: 'date-time',
|
|
||||||
nullable: true,
|
|
||||||
example: '2023-02-10T08:36:35.262Z',
|
|
||||||
description: 'When the project was created.',
|
|
||||||
},
|
|
||||||
favorite: {
|
|
||||||
type: 'boolean',
|
|
||||||
example: true,
|
|
||||||
description:
|
|
||||||
'`true` if the project was favorited, otherwise `false`.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
schemas: {
|
|
||||||
environmentSchema,
|
|
||||||
projectEnvironmentSchema,
|
|
||||||
createFeatureStrategySchema,
|
|
||||||
createStrategyVariantSchema,
|
|
||||||
constraintSchema,
|
|
||||||
featureSchema,
|
|
||||||
featureEnvironmentSchema,
|
|
||||||
overrideSchema,
|
|
||||||
parametersSchema,
|
|
||||||
featureStrategySchema,
|
|
||||||
strategyVariantSchema,
|
|
||||||
variantSchema,
|
|
||||||
projectStatsSchema,
|
|
||||||
createFeatureNamingPatternSchema,
|
|
||||||
tagSchema,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type DeprecatedProjectOverviewSchema = FromSchema<
|
|
||||||
typeof deprecatedProjectOverviewSchema
|
|
||||||
>;
|
|
@ -63,7 +63,6 @@ export * from './custom-metrics-schema.js';
|
|||||||
export * from './date-schema.js';
|
export * from './date-schema.js';
|
||||||
export * from './dependencies-exist-schema.js';
|
export * from './dependencies-exist-schema.js';
|
||||||
export * from './dependent-feature-schema.js';
|
export * from './dependent-feature-schema.js';
|
||||||
export * from './deprecated-project-overview-schema.js';
|
|
||||||
export * from './dora-features-schema.js';
|
export * from './dora-features-schema.js';
|
||||||
export * from './edge-token-schema.js';
|
export * from './edge-token-schema.js';
|
||||||
export * from './email-schema.js';
|
export * from './email-schema.js';
|
||||||
|
@ -71,7 +71,7 @@ const unfavoriteProject = async (projectName = 'default') => {
|
|||||||
|
|
||||||
const getProject = async (projectName = 'default') => {
|
const getProject = async (projectName = 'default') => {
|
||||||
return app.request
|
return app.request
|
||||||
.get(`/api/admin/projects/${projectName}`)
|
.get(`/api/admin/projects/${projectName}/overview`)
|
||||||
.set('Content-Type', 'application/json')
|
.set('Content-Type', 'application/json')
|
||||||
.expect(200);
|
.expect(200);
|
||||||
};
|
};
|
||||||
@ -122,28 +122,6 @@ beforeEach(async () => {
|
|||||||
await loginRegularUser();
|
await loginRegularUser();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should be favorited in project endpoint', async () => {
|
|
||||||
const featureName = 'test-feature';
|
|
||||||
await createFeature(featureName);
|
|
||||||
await favoriteFeature(featureName);
|
|
||||||
await favoriteProject();
|
|
||||||
|
|
||||||
const { body } = await app.request
|
|
||||||
.get('/api/admin/projects/default')
|
|
||||||
.set('Content-Type', 'application/json')
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
favorite: true,
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
name: featureName,
|
|
||||||
favorite: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('feature should not be favorited by default', async () => {
|
test('feature should not be favorited by default', async () => {
|
||||||
const featureName = 'test-feature';
|
const featureName = 'test-feature';
|
||||||
await createFeature(featureName);
|
await createFeature(featureName);
|
||||||
|
@ -65,15 +65,11 @@ test('Project with no stale toggles should have 100% health rating', async () =>
|
|||||||
})
|
})
|
||||||
.expect(201);
|
.expect(201);
|
||||||
await app.request
|
await app.request
|
||||||
.get('/api/admin/projects/fresh')
|
.get('/api/admin/projects/fresh/health-report')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect((res) => {
|
.expect((res) => {
|
||||||
expect(res.body.health).toBe(100);
|
expect(res.body.health).toBe(100);
|
||||||
expect(res.body.environments).toHaveLength(1);
|
|
||||||
expect(res.body.environments).toStrictEqual([
|
|
||||||
{ environment: 'default' },
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -251,93 +247,6 @@ test('Health report for non-existing project yields 404', async () => {
|
|||||||
.expect(404);
|
.expect(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sorts environments by sort order', async () => {
|
|
||||||
const envOne = 'my-sorted-env1';
|
|
||||||
const envTwo = 'my-sorted-env2';
|
|
||||||
const featureName = 'My-new-toggle';
|
|
||||||
const defaultEnvName = 'default';
|
|
||||||
await db.stores.environmentStore.create({
|
|
||||||
name: envOne,
|
|
||||||
type: 'production',
|
|
||||||
sortOrder: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.stores.environmentStore.create({
|
|
||||||
name: envTwo,
|
|
||||||
type: 'production',
|
|
||||||
sortOrder: 500,
|
|
||||||
});
|
|
||||||
|
|
||||||
await app.request
|
|
||||||
.post('/api/admin/projects/default/environments')
|
|
||||||
.send({
|
|
||||||
environment: envOne,
|
|
||||||
})
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
await app.request
|
|
||||||
.post('/api/admin/projects/default/environments')
|
|
||||||
.send({
|
|
||||||
environment: envTwo,
|
|
||||||
})
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
await app.request
|
|
||||||
.post('/api/admin/projects/default/features')
|
|
||||||
.send({ name: featureName })
|
|
||||||
.expect(201);
|
|
||||||
|
|
||||||
await app.request.get('/api/admin/projects/default').expect((res) => {
|
|
||||||
const feature = res.body.features[0];
|
|
||||||
expect(feature.environments[0].name).toBe(envOne);
|
|
||||||
expect(feature.environments[1].name).toBe(defaultEnvName);
|
|
||||||
expect(feature.environments[2].name).toBe(envTwo);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Sorts environments correctly if sort order is equal', async () => {
|
|
||||||
const envOne = 'my-sorted-env3';
|
|
||||||
const envTwo = 'my-sorted-env4';
|
|
||||||
const featureName = 'My-new-toggle-2';
|
|
||||||
|
|
||||||
await db.stores.environmentStore.create({
|
|
||||||
name: envOne,
|
|
||||||
type: 'production',
|
|
||||||
sortOrder: -5,
|
|
||||||
});
|
|
||||||
|
|
||||||
await db.stores.environmentStore.create({
|
|
||||||
name: envTwo,
|
|
||||||
type: 'production',
|
|
||||||
sortOrder: -5,
|
|
||||||
});
|
|
||||||
|
|
||||||
await app.request
|
|
||||||
.post('/api/admin/projects/default/environments')
|
|
||||||
.send({
|
|
||||||
environment: envOne,
|
|
||||||
})
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
await app.request
|
|
||||||
.post('/api/admin/projects/default/environments')
|
|
||||||
.send({
|
|
||||||
environment: envTwo,
|
|
||||||
})
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
await app.request
|
|
||||||
.post('/api/admin/projects/default/features')
|
|
||||||
.send({ name: featureName })
|
|
||||||
.expect(201);
|
|
||||||
|
|
||||||
await app.request.get('/api/admin/projects/default').expect((res) => {
|
|
||||||
const feature = res.body.features[0];
|
|
||||||
expect(feature.environments[0].name).toBe(envOne);
|
|
||||||
expect(feature.environments[1].name).toBe(envTwo);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Update update_at when setHealth runs', async () => {
|
test('Update update_at when setHealth runs', async () => {
|
||||||
await app.services.projectHealthService.setProjectHealthRating('default');
|
await app.services.projectHealthService.setProjectHealthRating('default');
|
||||||
await app.request
|
await app.request
|
||||||
|
Loading…
Reference in New Issue
Block a user