mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
feat: project applications controller/service layer (#6184)
Just adding controller/service layer, connecting with schema. Next PR will implement store and e2e tests.
This commit is contained in:
parent
1b1bde8aec
commit
4972b9686c
@ -1,5 +1,5 @@
|
|||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import Controller from '../../controller';
|
import Controller from '../../routes/controller';
|
||||||
import {
|
import {
|
||||||
IArchivedQuery,
|
IArchivedQuery,
|
||||||
IProjectParam,
|
IProjectParam,
|
||||||
@ -7,12 +7,12 @@ import {
|
|||||||
IUnleashServices,
|
IUnleashServices,
|
||||||
NONE,
|
NONE,
|
||||||
serializeDates,
|
serializeDates,
|
||||||
} from '../../../types';
|
} from '../../types';
|
||||||
import ProjectFeaturesController from '../../../features/feature-toggle/feature-toggle-controller';
|
import ProjectFeaturesController from '../feature-toggle/feature-toggle-controller';
|
||||||
import EnvironmentsController from '../../../features/project-environments/environments';
|
import EnvironmentsController from '../project-environments/environments';
|
||||||
import ProjectHealthReport from './health-report';
|
import ProjectHealthReport from '../../routes/admin-api/project/health-report';
|
||||||
import ProjectService from '../../../services/project-service';
|
import ProjectService from '../../services/project-service';
|
||||||
import VariantsController from './variants';
|
import VariantsController from '../../routes/admin-api/project/variants';
|
||||||
import {
|
import {
|
||||||
createResponseSchema,
|
createResponseSchema,
|
||||||
ProjectDoraMetricsSchema,
|
ProjectDoraMetricsSchema,
|
||||||
@ -22,18 +22,22 @@ import {
|
|||||||
projectsSchema,
|
projectsSchema,
|
||||||
ProjectsSchema,
|
ProjectsSchema,
|
||||||
projectOverviewSchema,
|
projectOverviewSchema,
|
||||||
} from '../../../openapi';
|
} from '../../openapi';
|
||||||
import { getStandardResponses } from '../../../openapi/util/standard-responses';
|
import { getStandardResponses } from '../../openapi/util/standard-responses';
|
||||||
import { OpenApiService, SettingService } from '../../../services';
|
import { OpenApiService, SettingService } from '../../services';
|
||||||
import { IAuthRequest } from '../../unleash-types';
|
import { IAuthRequest } from '../../routes/unleash-types';
|
||||||
import { ProjectApiTokenController } from './api-token';
|
import { ProjectApiTokenController } from '../../routes/admin-api/project/api-token';
|
||||||
import ProjectArchiveController from './project-archive';
|
import ProjectArchiveController from '../../routes/admin-api/project/project-archive';
|
||||||
import { createKnexTransactionStarter } from '../../../db/transaction';
|
import { createKnexTransactionStarter } from '../../db/transaction';
|
||||||
import { Db } from '../../../db/db';
|
import { Db } from '../../db/db';
|
||||||
import DependentFeaturesController from '../../../features/dependent-features/dependent-features-controller';
|
import DependentFeaturesController from '../dependent-features/dependent-features-controller';
|
||||||
import { ProjectOverviewSchema } from '../../../openapi/spec/project-overview-schema';
|
import { ProjectOverviewSchema } from '../../openapi/spec/project-overview-schema';
|
||||||
|
import {
|
||||||
|
projectApplicationsSchema,
|
||||||
|
ProjectApplicationsSchema,
|
||||||
|
} from '../../openapi/spec/project-applications-schema';
|
||||||
|
|
||||||
export default class ProjectApi extends Controller {
|
export default class ProjectController extends Controller {
|
||||||
private projectService: ProjectService;
|
private projectService: ProjectService;
|
||||||
|
|
||||||
private settingService: SettingService;
|
private settingService: SettingService;
|
||||||
@ -129,6 +133,26 @@ export default class ProjectApi extends Controller {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'get',
|
||||||
|
path: '/:projectId/applications',
|
||||||
|
handler: this.getProjectApplications,
|
||||||
|
permission: NONE,
|
||||||
|
middleware: [
|
||||||
|
services.openApiService.validPath({
|
||||||
|
tags: ['Unstable'],
|
||||||
|
operationId: 'getProjectApplications',
|
||||||
|
summary: 'Get a list of all applications for a project.',
|
||||||
|
description:
|
||||||
|
'This endpoint returns an list of all the applications for a project.',
|
||||||
|
responses: {
|
||||||
|
200: createResponseSchema('projectApplicationsSchema'),
|
||||||
|
...getStandardResponses(401, 403, 404),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
this.use(
|
this.use(
|
||||||
'/',
|
'/',
|
||||||
new ProjectFeaturesController(
|
new ProjectFeaturesController(
|
||||||
@ -229,4 +253,21 @@ export default class ProjectApi extends Controller {
|
|||||||
dora,
|
dora,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getProjectApplications(
|
||||||
|
req: IAuthRequest,
|
||||||
|
res: Response<ProjectApplicationsSchema>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { projectId } = req.params;
|
||||||
|
|
||||||
|
const applications =
|
||||||
|
await this.projectService.getApplications(projectId);
|
||||||
|
|
||||||
|
this.openApiService.respondWithValidation(
|
||||||
|
200,
|
||||||
|
res,
|
||||||
|
projectApplicationsSchema.$id,
|
||||||
|
applications,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,14 +1,14 @@
|
|||||||
import dbInit, { ITestDb } from '../../../helpers/database-init';
|
import dbInit, { ITestDb } from '../../../test/e2e/helpers/database-init';
|
||||||
import {
|
import {
|
||||||
IUnleashTest,
|
IUnleashTest,
|
||||||
insertFeatureEnvironmentsLastSeen,
|
insertFeatureEnvironmentsLastSeen,
|
||||||
insertLastSeenAt,
|
insertLastSeenAt,
|
||||||
setupAppWithCustomConfig,
|
setupAppWithCustomConfig,
|
||||||
} from '../../../helpers/test-helper';
|
} from '../../../test/e2e/helpers/test-helper';
|
||||||
import getLogger from '../../../../fixtures/no-logger';
|
import getLogger from '../../../test/fixtures/no-logger';
|
||||||
|
|
||||||
import { IProjectStore } from '../../../../../lib/types';
|
import { IProjectStore } from '../../types';
|
||||||
import { DEFAULT_ENV } from '../../../../../lib/util';
|
import { DEFAULT_ENV } from '../../util';
|
||||||
|
|
||||||
let app: IUnleashTest;
|
let app: IUnleashTest;
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
@ -143,9 +143,11 @@ test('response for default project should include created_at', async () => {
|
|||||||
.expect(200);
|
.expect(200);
|
||||||
expect(body.createdAt).toBeDefined();
|
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({ name: 'my-new-release-toggle', type: 'release' });
|
await app.createFeature({
|
||||||
|
name: 'my-new-release-toggle',
|
||||||
|
type: 'release',
|
||||||
|
});
|
||||||
await app.createFeature({
|
await app.createFeature({
|
||||||
name: 'my-new-development-toggle',
|
name: 'my-new-development-toggle',
|
||||||
type: 'development',
|
type: 'development',
|
||||||
@ -156,8 +158,14 @@ test('response for project overview should include feature type counts', async (
|
|||||||
.expect(200);
|
.expect(200);
|
||||||
expect(body).toMatchObject({
|
expect(body).toMatchObject({
|
||||||
featureTypeCounts: [
|
featureTypeCounts: [
|
||||||
{ type: 'development', count: 1 },
|
{
|
||||||
{ type: 'release', count: 1 },
|
type: 'development',
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'release',
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -278,3 +286,12 @@ test('response should include last seen at per environment for multiple environm
|
|||||||
|
|
||||||
expect(body.features[1].lastSeenAt).toBe('2023-10-01T12:34:56.000Z');
|
expect(body.features[1].lastSeenAt).toBe('2023-10-01T12:34:56.000Z');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should return empty list of applications', async () => {
|
||||||
|
const { body } = await app.request
|
||||||
|
.get('/api/admin/projects/default/applications')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(body).toMatchObject([]);
|
||||||
|
});
|
@ -175,3 +175,5 @@ export * from './feature-type-count-schema';
|
|||||||
export * from './feature-search-response-schema';
|
export * from './feature-search-response-schema';
|
||||||
export * from './inactive-user-schema';
|
export * from './inactive-user-schema';
|
||||||
export * from './inactive-users-schema';
|
export * from './inactive-users-schema';
|
||||||
|
export * from './project-application-schema';
|
||||||
|
export * from './project-applications-schema';
|
||||||
|
@ -20,7 +20,7 @@ import UserAdminController from './user-admin';
|
|||||||
import EmailController from './email';
|
import EmailController from './email';
|
||||||
import UserFeedbackController from './user-feedback';
|
import UserFeedbackController from './user-feedback';
|
||||||
import UserSplashController from './user-splash';
|
import UserSplashController from './user-splash';
|
||||||
import ProjectApi from './project/project-api';
|
import ProjectController from '../../features/project/project-controller';
|
||||||
import { EnvironmentsController } from './environments';
|
import { EnvironmentsController } from './environments';
|
||||||
import ConstraintsController from './constraints';
|
import ConstraintsController from './constraints';
|
||||||
import PatController from './user/pat';
|
import PatController from './user/pat';
|
||||||
@ -117,7 +117,10 @@ class AdminApi extends Controller {
|
|||||||
'/feedback',
|
'/feedback',
|
||||||
new UserFeedbackController(config, services).router,
|
new UserFeedbackController(config, services).router,
|
||||||
);
|
);
|
||||||
this.app.use('/projects', new ProjectApi(config, services, db).router);
|
this.app.use(
|
||||||
|
'/projects',
|
||||||
|
new ProjectController(config, services, db).router,
|
||||||
|
);
|
||||||
this.app.use(
|
this.app.use(
|
||||||
'/environments',
|
'/environments',
|
||||||
new EnvironmentsController(config, services).router,
|
new EnvironmentsController(config, services).router,
|
||||||
|
@ -63,7 +63,10 @@ import { calculateAverageTimeToProd } from '../features/feature-toggle/time-to-p
|
|||||||
import { IProjectStatsStore } from '../types/stores/project-stats-store-type';
|
import { IProjectStatsStore } from '../types/stores/project-stats-store-type';
|
||||||
import { uniqueByKey } from '../util/unique';
|
import { uniqueByKey } from '../util/unique';
|
||||||
import { BadDataError, PermissionError } from '../error';
|
import { BadDataError, PermissionError } from '../error';
|
||||||
import { ProjectDoraMetricsSchema } from '../openapi';
|
import {
|
||||||
|
ProjectDoraMetricsSchema,
|
||||||
|
ProjectApplicationsSchema,
|
||||||
|
} from '../openapi';
|
||||||
import { checkFeatureNamingData } from '../features/feature-naming-pattern/feature-naming-validation';
|
import { checkFeatureNamingData } from '../features/feature-naming-pattern/feature-naming-validation';
|
||||||
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
|
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
|
||||||
import EventService from '../features/events/event-service';
|
import EventService from '../features/events/event-service';
|
||||||
@ -89,7 +92,14 @@ interface ICalculateStatus {
|
|||||||
updates: IProjectStats;
|
updates: IProjectStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
function includes(list: number[], { id }: { id: number }): boolean {
|
function includes(
|
||||||
|
list: number[],
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
}: {
|
||||||
|
id: number;
|
||||||
|
},
|
||||||
|
): boolean {
|
||||||
return list.some((l) => l === id);
|
return list.some((l) => l === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -887,7 +897,16 @@ export default class ProjectService {
|
|||||||
featureToggleNames,
|
featureToggleNames,
|
||||||
);
|
);
|
||||||
|
|
||||||
return { features: toggleAverage, projectAverage: projectAverage };
|
return {
|
||||||
|
features: toggleAverage,
|
||||||
|
projectAverage: projectAverage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getApplications(
|
||||||
|
projectId: string,
|
||||||
|
): Promise<ProjectApplicationsSchema> {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async changeRole(
|
async changeRole(
|
||||||
@ -1091,7 +1110,10 @@ export default class ProjectService {
|
|||||||
const [projectActivityCurrentWindow, projectActivityPastWindow] =
|
const [projectActivityCurrentWindow, projectActivityPastWindow] =
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.eventStore.queryCount([
|
this.eventStore.queryCount([
|
||||||
{ op: 'where', parameters: { project: projectId } },
|
{
|
||||||
|
op: 'where',
|
||||||
|
parameters: { project: projectId },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
op: 'beforeDate',
|
op: 'beforeDate',
|
||||||
parameters: {
|
parameters: {
|
||||||
@ -1101,7 +1123,10 @@ export default class ProjectService {
|
|||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
this.eventStore.queryCount([
|
this.eventStore.queryCount([
|
||||||
{ op: 'where', parameters: { project: projectId } },
|
{
|
||||||
|
op: 'where',
|
||||||
|
parameters: { project: projectId },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
op: 'betweenDate',
|
op: 'betweenDate',
|
||||||
parameters: {
|
parameters: {
|
||||||
|
Loading…
Reference in New Issue
Block a user