1
0
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:
Jaanus Sellin 2024-02-09 13:18:26 +02:00 committed by GitHub
parent 1b1bde8aec
commit 4972b9686c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 122 additions and 34 deletions

View File

@ -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,
);
}
} }

View File

@ -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([]);
});

View File

@ -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';

View File

@ -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,

View File

@ -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: {