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

refactor/clean-up-get-project (#2925)

This PR moves the getProjectOverview method out from the project health
controller. It doesn't make sense that this method lives here anymore,
as over time it has grown into method that relays all information about
a single project. It makes more sense that this now lives on the root of
the project api. Also removes unwanted duplication of getProjectOverview
from the project-service and the project-health-service.
This commit is contained in:
Fredrik Strand Oseberg 2023-01-18 13:22:58 +01:00 committed by GitHub
parent d63b3c69fe
commit 89163b8719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 87 additions and 101 deletions

View File

@ -4,20 +4,15 @@ import { IUnleashServices } from '../../../types/services';
import { IUnleashConfig } from '../../../types/option';
import ProjectHealthService from '../../../services/project-health-service';
import { Logger } from '../../../logger';
import { IArchivedQuery, IProjectParam } from '../../../types/model';
import { IProjectParam } from '../../../types/model';
import { NONE } from '../../../types/permissions';
import { OpenApiService } from '../../../services/openapi-service';
import { createResponseSchema } from '../../../openapi/util/create-response-schema';
import {
healthOverviewSchema,
HealthOverviewSchema,
} from '../../../openapi/spec/health-overview-schema';
import { serializeDates } from '../../../types/serialize-dates';
import {
healthReportSchema,
HealthReportSchema,
} from '../../../openapi/spec/health-report-schema';
import { IAuthRequest } from '../../unleash-types';
export default class ProjectHealthReport extends Controller {
private projectHealthService: ProjectHealthService;
@ -38,22 +33,6 @@ export default class ProjectHealthReport extends Controller {
this.projectHealthService = projectHealthService;
this.openApiService = openApiService;
this.route({
method: 'get',
path: '/:projectId',
handler: this.getProjectHealthOverview,
permission: NONE,
middleware: [
openApiService.validPath({
tags: ['Projects'],
operationId: 'getProjectHealthOverview',
responses: {
200: createResponseSchema('healthOverviewSchema'),
},
}),
],
});
this.route({
method: 'get',
path: '/:projectId/health-report',
@ -71,26 +50,6 @@ export default class ProjectHealthReport extends Controller {
});
}
async getProjectHealthOverview(
req: IAuthRequest<IProjectParam, unknown, unknown, IArchivedQuery>,
res: Response<HealthOverviewSchema>,
): Promise<void> {
const { projectId } = req.params;
const { archived } = req.query;
const { user } = req;
const overview = await this.projectHealthService.getProjectOverview(
projectId,
archived,
user.id,
);
this.openApiService.respondWithValidation(
200,
res,
healthOverviewSchema.$id,
serializeDates(overview),
);
}
async getProjectHealthReport(
req: Request<IProjectParam>,
res: Response<HealthReportSchema>,

View File

@ -16,6 +16,11 @@ import { OpenApiService } from '../../../services/openapi-service';
import { serializeDates } from '../../../types/serialize-dates';
import { createResponseSchema } from '../../../openapi/util/create-response-schema';
import { IAuthRequest } from '../../unleash-types';
import {
HealthOverviewSchema,
healthOverviewSchema,
} from '../../../../lib/openapi';
import { IArchivedQuery, IProjectParam } from '../../../types/model';
export default class ProjectApi extends Controller {
private projectService: ProjectService;
@ -43,6 +48,22 @@ export default class ProjectApi extends Controller {
],
});
this.route({
method: 'get',
path: '/:projectId',
handler: this.getProjectOverview,
permission: NONE,
middleware: [
services.openApiService.validPath({
tags: ['Projects'],
operationId: 'getProjectOverview',
responses: {
200: createResponseSchema('healthOverviewSchema'),
},
}),
],
});
this.use('/', new ProjectFeaturesController(config, services).router);
this.use('/', new EnvironmentsController(config, services).router);
this.use('/', new ProjectHealthReport(config, services).router);
@ -68,4 +89,24 @@ export default class ProjectApi extends Controller {
{ version: 1, projects: serializeDates(projects) },
);
}
async getProjectOverview(
req: IAuthRequest<IProjectParam, unknown, unknown, IArchivedQuery>,
res: Response<HealthOverviewSchema>,
): Promise<void> {
const { projectId } = req.params;
const { archived } = req.query;
const { user } = req;
const overview = await this.projectService.getProjectOverview(
projectId,
archived,
user.id,
);
this.openApiService.respondWithValidation(
200,
res,
healthOverviewSchema.$id,
serializeDates(overview),
);
}
}

View File

@ -92,18 +92,18 @@ export const createServices = (
const environmentService = new EnvironmentService(stores, config);
const featureTagService = new FeatureTagService(stores, config);
const favoritesService = new FavoritesService(stores, config);
const projectHealthService = new ProjectHealthService(
stores,
config,
featureToggleServiceV2,
favoritesService,
);
const projectService = new ProjectService(
stores,
config,
accessService,
featureToggleServiceV2,
groupService,
favoritesService,
);
const projectHealthService = new ProjectHealthService(
stores,
config,
projectService,
);
const userSplashService = new UserSplashService(stores, config);
const openApiService = new OpenApiService(config);

View File

@ -6,15 +6,13 @@ import {
IFeatureOverview,
IProject,
IProjectHealthReport,
IProjectOverview,
} from '../types/model';
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
import { IFeatureTypeStore } from '../types/stores/feature-type-store';
import { IProjectStore } from '../types/stores/project-store';
import FeatureToggleService from './feature-toggle-service';
import { hoursToMilliseconds } from 'date-fns';
import Timer = NodeJS.Timer;
import { FavoritesService } from './favorites-service';
import ProjectService from './project-service';
export default class ProjectHealthService {
private logger: Logger;
@ -29,9 +27,7 @@ export default class ProjectHealthService {
private healthRatingTimer: Timer;
private featureToggleService: FeatureToggleService;
private favoritesService: FavoritesService;
private projectService: ProjectService;
constructor(
{
@ -43,8 +39,7 @@ export default class ProjectHealthService {
'projectStore' | 'featureTypeStore' | 'featureToggleStore'
>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
featureToggleService: FeatureToggleService,
favoritesService: FavoritesService,
projectService: ProjectService,
) {
this.logger = getLogger('services/project-health-service.ts');
this.projectStore = projectStore;
@ -55,50 +50,14 @@ export default class ProjectHealthService {
() => this.setHealthRating(),
hoursToMilliseconds(1),
).unref();
this.featureToggleService = featureToggleService;
this.favoritesService = favoritesService;
}
// TODO: duplicate from project-service.
async getProjectOverview(
projectId: string,
archived: boolean = false,
userId?: number,
): Promise<IProjectOverview> {
const project = await this.projectStore.get(projectId);
const environments = await this.projectStore.getEnvironmentsForProject(
projectId,
);
const features = await this.featureToggleService.getFeatureOverview({
projectId,
archived,
userId,
});
const members = await this.projectStore.getMembersCountByProject(
projectId,
);
const favorite = await this.favoritesService.isFavoriteProject({
project: projectId,
userId,
});
return {
name: project.name,
description: project.description,
health: project.health,
favorite: favorite,
updatedAt: project.updatedAt,
environments,
features,
members,
version: 1,
};
this.projectService = projectService;
}
async getProjectHealthReport(
projectId: string,
): Promise<IProjectHealthReport> {
const overview = await this.getProjectOverview(
const overview = await this.projectService.getProjectOverview(
projectId,
false,
undefined,

View File

@ -46,6 +46,7 @@ import { IUserStore } from 'lib/types/stores/user-store';
import { arraysHaveSameItems } from '../util/arraysHaveSameItems';
import { GroupService } from './group-service';
import { IGroupModelWithProjectRole, IGroupRole } from 'lib/types/group';
import { FavoritesService } from './favorites-service';
const getCreatedBy = (user: IUser) => user.email || user.username;
@ -80,6 +81,8 @@ export default class ProjectService {
private userStore: IUserStore;
private favoritesService: FavoritesService;
constructor(
{
projectStore,
@ -105,6 +108,7 @@ export default class ProjectService {
accessService: AccessService,
featureToggleService: FeatureToggleService,
groupService: GroupService,
favoriteService: FavoritesService,
) {
this.store = projectStore;
this.environmentStore = environmentStore;
@ -114,6 +118,7 @@ export default class ProjectService {
this.featureToggleStore = featureToggleStore;
this.featureTypeStore = featureTypeStore;
this.featureToggleService = featureToggleService;
this.favoritesService = favoriteService;
this.tagStore = featureTagStore;
this.userStore = userStore;
this.groupService = groupService;
@ -590,6 +595,7 @@ export default class ProjectService {
async getProjectOverview(
projectId: string,
archived: boolean = false,
userId?: number,
): Promise<IProjectOverview> {
const project = await this.store.get(projectId);
const environments = await this.store.getEnvironmentsForProject(
@ -598,13 +604,21 @@ export default class ProjectService {
const features = await this.featureToggleService.getFeatureOverview({
projectId,
archived,
userId,
});
const members = await this.store.getMembersCountByProject(projectId);
const favorite = await this.favoritesService.isFavoriteProject({
project: projectId,
userId,
});
return {
name: project.name,
environments,
description: project.description,
health: project.health,
favorite: favorite,
updatedAt: project.updatedAt,
environments,
features,
members,
version: 1,

View File

@ -23,7 +23,7 @@ test('should require authenticated user', async () => {
const preHook = (app) => {
app.use('/api/admin/', (req, res) =>
res
.status('401')
.status(401)
.json(
new AuthenticationRequired({
path: '/auth/demo/login',

View File

@ -5291,7 +5291,7 @@ If the provided project does not exist, the list of events will be empty.",
},
"/api/admin/projects/{projectId}": {
"get": {
"operationId": "getProjectHealthOverview",
"operationId": "getProjectOverview",
"parameters": [
{
"in": "path",

View File

@ -14,12 +14,14 @@ import { DEFAULT_PROJECT } from '../../../lib/types/project';
import { ALL_PROJECTS } from '../../../lib/util/constants';
import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service';
import { FavoritesService } from '../../../lib/services';
let db: ITestDb;
let stores: IUnleashStores;
let accessService;
let groupService;
let featureToggleService;
let favoritesService;
let projectService;
let editorUser;
let superUser;
@ -220,12 +222,14 @@ beforeAll(async () => {
new SegmentService(stores, config),
accessService,
);
favoritesService = new FavoritesService(stores, config);
projectService = new ProjectService(
stores,
config,
accessService,
featureToggleService,
groupService,
favoritesService,
);
editorUser = await createUserEditorAccess('Bob Test', 'bob@getunleash.io');

View File

@ -10,11 +10,13 @@ import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import { AccessService } from '../../../lib/services/access-service';
import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service';
import { FavoritesService } from '../../../lib/services';
let db;
let stores;
let apiTokenService: ApiTokenService;
let projectService: ProjectService;
let favoritesService: FavoritesService;
beforeAll(async () => {
const config = createTestConfig({
@ -39,13 +41,14 @@ beforeAll(async () => {
name: 'Some Name',
email: 'test@getunleash.io',
});
favoritesService = new FavoritesService(stores, config);
projectService = new ProjectService(
stores,
config,
accessService,
featureToggleService,
groupService,
favoritesService,
);
await projectService.createProject(project, user);

View File

@ -37,19 +37,20 @@ beforeAll(async () => {
new SegmentService(stores, config),
accessService,
);
favoritesService = new FavoritesService(stores, config);
projectService = new ProjectService(
stores,
config,
accessService,
featureToggleService,
groupService,
favoritesService,
);
favoritesService = new FavoritesService(stores, config);
projectHealthService = new ProjectHealthService(
stores,
config,
featureToggleService,
favoritesService,
projectService,
);
});

View File

@ -11,6 +11,7 @@ import EnvironmentService from '../../../lib/services/environment-service';
import IncompatibleProjectError from '../../../lib/error/incompatible-project-error';
import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service';
import { FavoritesService } from '../../../lib/services';
let stores;
let db: ITestDb;
@ -20,6 +21,7 @@ let groupService: GroupService;
let accessService: AccessService;
let environmentService: EnvironmentService;
let featureToggleService: FeatureToggleService;
let favoritesService: FavoritesService;
let user;
beforeAll(async () => {
@ -42,6 +44,8 @@ beforeAll(async () => {
new SegmentService(stores, config),
accessService,
);
favoritesService = new FavoritesService(stores, config);
environmentService = new EnvironmentService(stores, config);
projectService = new ProjectService(
stores,
@ -49,6 +53,7 @@ beforeAll(async () => {
accessService,
featureToggleService,
groupService,
favoritesService,
);
});