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

feat: project status backend structure (#8630)

Adding project status schema definition, controller, service, e2e test.

Next PR will add functionality for activity object.

---------

Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
Jaanus Sellin 2024-11-01 14:17:20 +02:00 committed by GitHub
parent c9a564a556
commit c9dc5267a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 224 additions and 0 deletions

View File

@ -0,0 +1,17 @@
import type { Db, IUnleashConfig } from '../../server-impl';
import { ProjectStatusService } from './project-status-service';
export const createProjectStatusService = (
db: Db,
config: IUnleashConfig,
): ProjectStatusService => {
return new ProjectStatusService();
};
export const createFakeProjectStatusService = () => {
const projectStatusService = new ProjectStatusService();
return {
projectStatusService,
};
};

View File

@ -0,0 +1,71 @@
import type { Response } from 'express';
import Controller from '../../routes/controller';
import {
type IFlagResolver,
type IProjectParam,
type IUnleashConfig,
type IUnleashServices,
NONE,
serializeDates,
} from '../../types';
import { getStandardResponses } from '../../openapi/util/standard-responses';
import type { OpenApiService } from '../../services';
import type { IAuthRequest } from '../../routes/unleash-types';
import {
createResponseSchema,
projectStatusSchema,
type ProjectStatusSchema,
} from '../../openapi';
import type { ProjectStatusService } from './project-status-service';
export default class ProjectStatusController extends Controller {
private projectStatusService: ProjectStatusService;
private openApiService: OpenApiService;
private flagResolver: IFlagResolver;
constructor(config: IUnleashConfig, services: IUnleashServices) {
super(config);
this.projectStatusService = services.projectStatusService;
this.openApiService = services.openApiService;
this.flagResolver = config.flagResolver;
this.route({
method: 'get',
path: '/:projectId/status',
handler: this.getProjectStatus,
permission: NONE,
middleware: [
this.openApiService.validPath({
tags: ['Projects'],
operationId: 'getProjectStatus',
summary: 'Get project status',
description:
'This endpoint returns information on the status the project, including activities, health, resources, and aggregated flag lifecycle data.',
responses: {
200: createResponseSchema('projectStatusSchema'),
...getStandardResponses(401, 403, 404),
},
}),
],
});
}
async getProjectStatus(
req: IAuthRequest<IProjectParam, unknown, unknown, unknown>,
res: Response<ProjectStatusSchema>,
): Promise<void> {
const { projectId } = req.params;
const status: ProjectStatusSchema =
await this.projectStatusService.getProjectStatus(projectId);
this.openApiService.respondWithValidation(
200,
res,
projectStatusSchema.$id,
serializeDates(status),
);
}
}

View File

@ -0,0 +1,9 @@
import type { ProjectStatusSchema } from '../../openapi';
export class ProjectStatusService {
constructor() {}
async getProjectStatus(projectId: string): Promise<ProjectStatusSchema> {
return { activityCountByDate: [{ date: '2024-09-11', count: 0 }] };
}
}

View File

@ -0,0 +1,40 @@
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
import {
type IUnleashTest,
setupAppWithCustomConfig,
} from '../../../test/e2e/helpers/test-helper';
import getLogger from '../../../test/fixtures/no-logger';
let app: IUnleashTest;
let db: ITestDb;
beforeAll(async () => {
db = await dbInit('projects_status', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{
experimental: {
flags: {
strictSchemaValidation: true,
},
},
},
db.rawDatabase,
);
});
afterAll(async () => {
await app.destroy();
await db.destroy();
});
test('project insights happy path', async () => {
const { body } = await app.request
.get('/api/admin/projects/default/status')
.expect('Content-Type', /json/)
.expect(200);
expect(body).toMatchObject({
activityCountByDate: [{ date: '2024-09-11', count: 0 }],
});
});

View File

@ -48,6 +48,7 @@ import {
projectFlagCreatorsSchema,
type ProjectFlagCreatorsSchema,
} from '../../openapi/spec/project-flag-creators-schema';
import ProjectStatusController from '../project-status/project-status-controller';
export default class ProjectController extends Controller {
private projectService: ProjectService;
@ -242,6 +243,7 @@ export default class ProjectController extends Controller {
).router,
);
this.use('/', new ProjectInsightsController(config, services).router);
this.use('/', new ProjectStatusController(config, services).router);
this.use('/', new FeatureLifecycleController(config, services).router);
}

View File

@ -145,6 +145,7 @@ export * from './playground-response-schema';
export * from './playground-segment-schema';
export * from './playground-strategy-schema';
export * from './profile-schema';
export * from './project-activity-schema';
export * from './project-application-schema';
export * from './project-application-sdk-schema';
export * from './project-applications-schema';
@ -158,6 +159,7 @@ export * from './project-insights-schema';
export * from './project-overview-schema';
export * from './project-schema';
export * from './project-stats-schema';
export * from './project-status-schema';
export * from './projects-schema';
export * from './public-signup-token-create-schema';
export * from './public-signup-token-schema';

View File

@ -0,0 +1,30 @@
import type { FromSchema } from 'json-schema-to-ts';
export const projectActivitySchema = {
$id: '#/components/schemas/projectActivitySchema',
type: 'array',
description:
'An array of project activity information. Each item contains a date and the total number of activities for that date.',
items: {
type: 'object',
additionalProperties: false,
required: ['date', 'count'],
properties: {
date: {
type: 'string',
example: '2022-12-14',
description: 'Activity date',
},
count: {
type: 'integer',
minimum: 0,
description: 'Activity count',
},
},
},
components: {
schemas: {},
},
} as const;
export type ProjectActivitySchema = FromSchema<typeof projectActivitySchema>;

View File

@ -0,0 +1,15 @@
import { validateSchema } from '../validate';
import type { ProjectStatusSchema } from './project-status-schema';
test('projectStatusSchema', () => {
const data: ProjectStatusSchema = {
activityCountByDate: [
{ date: '2022-12-14', count: 2 },
{ date: '2022-12-15', count: 5 },
],
};
expect(
validateSchema('#/components/schemas/projectStatusSchema', data),
).toBeUndefined();
});

View File

@ -0,0 +1,25 @@
import type { FromSchema } from 'json-schema-to-ts';
import { projectActivitySchema } from './project-activity-schema';
export const projectStatusSchema = {
$id: '#/components/schemas/projectStatusSchema',
type: 'object',
additionalProperties: false,
required: ['activityCountByDate'],
description:
'Schema representing the overall status of a project, including an array of activity records. Each record in the activity array contains a date and a count, providing a snapshot of the projects activity level over time.',
properties: {
activityCountByDate: {
$ref: '#/components/schemas/projectActivitySchema',
description:
'Array of activity records with date and count, representing the projects daily activity statistics.',
},
},
components: {
schemas: {
projectActivitySchema,
},
},
} as const;
export type ProjectStatusSchema = FromSchema<typeof projectStatusSchema>;

View File

@ -153,6 +153,11 @@ import {
createFakePersonalDashboardService,
createPersonalDashboardService,
} from '../features/personal-dashboard/createPersonalDashboardService';
import {
createFakeProjectStatusService,
createProjectStatusService,
} from '../features/project-status/createProjectStatusService';
import { ProjectStatusService } from '../features/project-status/project-status-service';
export const createServices = (
stores: IUnleashStores,
@ -324,6 +329,10 @@ export const createServices = (
? createProjectInsightsService(db, config)
: createFakeProjectInsightsService().projectInsightsService;
const projectStatusService = db
? createProjectStatusService(db, config)
: createFakeProjectStatusService().projectStatusService;
const projectHealthService = new ProjectHealthService(
stores,
config,
@ -482,6 +491,7 @@ export const createServices = (
integrationEventsService,
onboardingService,
personalDashboardService,
projectStatusService,
};
};
@ -533,4 +543,5 @@ export {
IntegrationEventsService,
OnboardingService,
PersonalDashboardService,
ProjectStatusService,
};

View File

@ -57,6 +57,7 @@ import type { FeatureLifecycleService } from '../features/feature-lifecycle/feat
import type { IntegrationEventsService } from '../features/integration-events/integration-events-service';
import type { OnboardingService } from '../features/onboarding/onboarding-service';
import type { PersonalDashboardService } from '../features/personal-dashboard/personal-dashboard-service';
import type { ProjectStatusService } from '../features/project-status/project-status-service';
export interface IUnleashServices {
transactionalAccessService: WithTransactional<AccessService>;
@ -126,4 +127,5 @@ export interface IUnleashServices {
integrationEventsService: IntegrationEventsService;
onboardingService: OnboardingService;
personalDashboardService: PersonalDashboardService;
projectStatusService: ProjectStatusService;
}