mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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