mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-12 13:48:35 +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,
|
projectFlagCreatorsSchema,
|
||||||
type ProjectFlagCreatorsSchema,
|
type ProjectFlagCreatorsSchema,
|
||||||
} from '../../openapi/spec/project-flag-creators-schema';
|
} from '../../openapi/spec/project-flag-creators-schema';
|
||||||
|
import ProjectStatusController from '../project-status/project-status-controller';
|
||||||
|
|
||||||
export default class ProjectController extends Controller {
|
export default class ProjectController extends Controller {
|
||||||
private projectService: ProjectService;
|
private projectService: ProjectService;
|
||||||
@ -242,6 +243,7 @@ export default class ProjectController extends Controller {
|
|||||||
).router,
|
).router,
|
||||||
);
|
);
|
||||||
this.use('/', new ProjectInsightsController(config, services).router);
|
this.use('/', new ProjectInsightsController(config, services).router);
|
||||||
|
this.use('/', new ProjectStatusController(config, services).router);
|
||||||
this.use('/', new FeatureLifecycleController(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-segment-schema';
|
||||||
export * from './playground-strategy-schema';
|
export * from './playground-strategy-schema';
|
||||||
export * from './profile-schema';
|
export * from './profile-schema';
|
||||||
|
export * from './project-activity-schema';
|
||||||
export * from './project-application-schema';
|
export * from './project-application-schema';
|
||||||
export * from './project-application-sdk-schema';
|
export * from './project-application-sdk-schema';
|
||||||
export * from './project-applications-schema';
|
export * from './project-applications-schema';
|
||||||
@ -158,6 +159,7 @@ export * from './project-insights-schema';
|
|||||||
export * from './project-overview-schema';
|
export * from './project-overview-schema';
|
||||||
export * from './project-schema';
|
export * from './project-schema';
|
||||||
export * from './project-stats-schema';
|
export * from './project-stats-schema';
|
||||||
|
export * from './project-status-schema';
|
||||||
export * from './projects-schema';
|
export * from './projects-schema';
|
||||||
export * from './public-signup-token-create-schema';
|
export * from './public-signup-token-create-schema';
|
||||||
export * from './public-signup-token-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,
|
createFakePersonalDashboardService,
|
||||||
createPersonalDashboardService,
|
createPersonalDashboardService,
|
||||||
} from '../features/personal-dashboard/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 = (
|
export const createServices = (
|
||||||
stores: IUnleashStores,
|
stores: IUnleashStores,
|
||||||
@ -324,6 +329,10 @@ export const createServices = (
|
|||||||
? createProjectInsightsService(db, config)
|
? createProjectInsightsService(db, config)
|
||||||
: createFakeProjectInsightsService().projectInsightsService;
|
: createFakeProjectInsightsService().projectInsightsService;
|
||||||
|
|
||||||
|
const projectStatusService = db
|
||||||
|
? createProjectStatusService(db, config)
|
||||||
|
: createFakeProjectStatusService().projectStatusService;
|
||||||
|
|
||||||
const projectHealthService = new ProjectHealthService(
|
const projectHealthService = new ProjectHealthService(
|
||||||
stores,
|
stores,
|
||||||
config,
|
config,
|
||||||
@ -482,6 +491,7 @@ export const createServices = (
|
|||||||
integrationEventsService,
|
integrationEventsService,
|
||||||
onboardingService,
|
onboardingService,
|
||||||
personalDashboardService,
|
personalDashboardService,
|
||||||
|
projectStatusService,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -533,4 +543,5 @@ export {
|
|||||||
IntegrationEventsService,
|
IntegrationEventsService,
|
||||||
OnboardingService,
|
OnboardingService,
|
||||||
PersonalDashboardService,
|
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 { IntegrationEventsService } from '../features/integration-events/integration-events-service';
|
||||||
import type { OnboardingService } from '../features/onboarding/onboarding-service';
|
import type { OnboardingService } from '../features/onboarding/onboarding-service';
|
||||||
import type { PersonalDashboardService } from '../features/personal-dashboard/personal-dashboard-service';
|
import type { PersonalDashboardService } from '../features/personal-dashboard/personal-dashboard-service';
|
||||||
|
import type { ProjectStatusService } from '../features/project-status/project-status-service';
|
||||||
|
|
||||||
export interface IUnleashServices {
|
export interface IUnleashServices {
|
||||||
transactionalAccessService: WithTransactional<AccessService>;
|
transactionalAccessService: WithTransactional<AccessService>;
|
||||||
@ -126,4 +127,5 @@ export interface IUnleashServices {
|
|||||||
integrationEventsService: IntegrationEventsService;
|
integrationEventsService: IntegrationEventsService;
|
||||||
onboardingService: OnboardingService;
|
onboardingService: OnboardingService;
|
||||||
personalDashboardService: PersonalDashboardService;
|
personalDashboardService: PersonalDashboardService;
|
||||||
|
projectStatusService: ProjectStatusService;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user