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:
parent
c9a564a556
commit
c9dc5267a6
@ -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,
|
||||
};
|
||||
};
|
71
src/lib/features/project-status/project-status-controller.ts
Normal file
71
src/lib/features/project-status/project-status-controller.ts
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
@ -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 }] };
|
||||
}
|
||||
}
|
40
src/lib/features/project-status/projects-status.e2e.test.ts
Normal file
40
src/lib/features/project-status/projects-status.e2e.test.ts
Normal 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 }],
|
||||
});
|
||||
});
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
30
src/lib/openapi/spec/project-activity-schema.ts
Normal file
30
src/lib/openapi/spec/project-activity-schema.ts
Normal 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>;
|
15
src/lib/openapi/spec/project-status-schema.test.ts
Normal file
15
src/lib/openapi/spec/project-status-schema.test.ts
Normal 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();
|
||||
});
|
25
src/lib/openapi/spec/project-status-schema.ts
Normal file
25
src/lib/openapi/spec/project-status-schema.ts
Normal 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 project’s activity level over time.',
|
||||
properties: {
|
||||
activityCountByDate: {
|
||||
$ref: '#/components/schemas/projectActivitySchema',
|
||||
description:
|
||||
'Array of activity records with date and count, representing the project’s daily activity statistics.',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
projectActivitySchema,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type ProjectStatusSchema = FromSchema<typeof projectStatusSchema>;
|
@ -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,
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user