From e540db4cb3e35dcda53dfcf8983c945e30166935 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Fri, 27 Sep 2024 12:47:21 +0200 Subject: [PATCH] feat: return latest project events (#8287) --- src/lib/addons/feature-event-formatter-md.ts | 2 +- .../createPersonalDashboardService.ts | 15 +++++++++- .../personal-dashboard-controller.e2e.test.ts | 30 +++++++++++++++++++ .../personal-dashboard-controller.ts | 6 ++++ .../personal-dashboard-service.ts | 30 +++++++++++++++++++ ...rsonal-dashboard-project-details-schema.ts | 23 ++++++++++++++ src/lib/services/index.ts | 2 +- 7 files changed, 105 insertions(+), 3 deletions(-) diff --git a/src/lib/addons/feature-event-formatter-md.ts b/src/lib/addons/feature-event-formatter-md.ts index d07beb16ad0a..93852432b33f 100644 --- a/src/lib/addons/feature-event-formatter-md.ts +++ b/src/lib/addons/feature-event-formatter-md.ts @@ -7,7 +7,7 @@ import { } from '../types'; import { EVENT_MAP } from './feature-event-formatter-md-events'; -interface IFormattedEventData { +export interface IFormattedEventData { label: string; text: string; url?: string; diff --git a/src/lib/features/personal-dashboard/createPersonalDashboardService.ts b/src/lib/features/personal-dashboard/createPersonalDashboardService.ts index 1de2f55570ef..b24e9512b982 100644 --- a/src/lib/features/personal-dashboard/createPersonalDashboardService.ts +++ b/src/lib/features/personal-dashboard/createPersonalDashboardService.ts @@ -7,6 +7,9 @@ import { ProjectOwnersReadModel } from '../project/project-owners-read-model'; import { FakeProjectOwnersReadModel } from '../project/fake-project-owners-read-model'; import { ProjectReadModel } from '../project/project-read-model'; import { FakeProjectReadModel } from '../project/fake-project-read-model'; +import EventStore from '../../db/event-store'; +import { FeatureEventFormatterMd } from '../../addons/feature-event-formatter-md'; +import FakeEventStore from '../../../test/fixtures/fake-event-store'; export const createPersonalDashboardService = ( db: Db, @@ -16,13 +19,23 @@ export const createPersonalDashboardService = ( new PersonalDashboardReadModel(db), new ProjectOwnersReadModel(db), new ProjectReadModel(db, config.eventBus, config.flagResolver), + new EventStore(db, config.getLogger), + new FeatureEventFormatterMd({ + unleashUrl: config.server.unleashUrl, + formatStyle: 'markdown', + }), ); }; -export const createFakePersonalDashboardService = () => { +export const createFakePersonalDashboardService = (config: IUnleashConfig) => { return new PersonalDashboardService( new FakePersonalDashboardReadModel(), new FakeProjectOwnersReadModel(), new FakeProjectReadModel(), + new FakeEventStore(), + new FeatureEventFormatterMd({ + unleashUrl: config.server.unleashUrl, + formatStyle: 'markdown', + }), ); }; diff --git a/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts b/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts index efef09eeaf89..e762e4aba84b 100644 --- a/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts +++ b/src/lib/features/personal-dashboard/personal-dashboard-controller.e2e.test.ts @@ -40,6 +40,7 @@ afterAll(async () => { beforeEach(async () => { await db.stores.featureToggleStore.deleteAll(); await db.stores.userStore.deleteAll(); + await db.stores.eventStore.deleteAll(); }); test('should return personal dashboard with own flags and favorited flags', async () => { @@ -178,6 +179,11 @@ test('should return projects where users are part of a group', async () => { test('should return personal dashboard project details', async () => { await loginUser('new_user@test.com'); + + await app.createFeature('log_feature_a'); + await app.createFeature('log_feature_b'); + await app.createFeature('log_feature_c'); + const { body } = await app.request.get( `/api/admin/personal-dashboard/default`, ); @@ -185,5 +191,29 @@ test('should return personal dashboard project details', async () => { expect(body).toMatchObject({ owners: [{}], roles: [{}], + latestEvents: [ + { + createdBy: 'new_user@test.com', + summary: expect.stringContaining( + '**new_user@test.com** created **[log_feature_c]', + ), + }, + { + createdBy: 'new_user@test.com', + summary: expect.stringContaining( + '**new_user@test.com** created **[log_feature_b]', + ), + }, + { + createdBy: 'new_user@test.com', + summary: expect.stringContaining( + '**new_user@test.com** created **[log_feature_a]', + ), + }, + { + createdBy: 'unleash_system_user', + summary: '**unleash_system_user** created user ****', + }, + ], }); }); diff --git a/src/lib/features/personal-dashboard/personal-dashboard-controller.ts b/src/lib/features/personal-dashboard/personal-dashboard-controller.ts index 555af985c53e..dacfc0f81aa9 100644 --- a/src/lib/features/personal-dashboard/personal-dashboard-controller.ts +++ b/src/lib/features/personal-dashboard/personal-dashboard-controller.ts @@ -104,11 +104,17 @@ export default class PersonalDashboardController extends Controller { ): Promise { const user = req.user; + const projectDetails = + await this.personalDashboardService.getPersonalProjectDetails( + req.params.projectId, + ); + this.openApiService.respondWithValidation( 200, res, personalDashboardProjectDetailsSchema.$id, { + ...projectDetails, owners: [{ ownerType: 'user', name: 'placeholder' }], roles: [{ name: 'placeholder', id: 0, type: 'project' }], }, diff --git a/src/lib/features/personal-dashboard/personal-dashboard-service.ts b/src/lib/features/personal-dashboard/personal-dashboard-service.ts index f62bc8b7c61d..993424a9adb5 100644 --- a/src/lib/features/personal-dashboard/personal-dashboard-service.ts +++ b/src/lib/features/personal-dashboard/personal-dashboard-service.ts @@ -5,6 +5,12 @@ import type { PersonalProject, } from './personal-dashboard-read-model-type'; import type { IProjectReadModel } from '../project/project-read-model-type'; +import type { IEventStore } from '../../types'; +import type { FeatureEventFormatter } from '../../addons/feature-event-formatter-md'; + +type PersonalProjectDetails = { + latestEvents: { summary: string; createdBy: string }[]; +}; export class PersonalDashboardService { private personalDashboardReadModel: IPersonalDashboardReadModel; @@ -13,14 +19,22 @@ export class PersonalDashboardService { private projectReadModel: IProjectReadModel; + private eventStore: IEventStore; + + private featureEventFormatter: FeatureEventFormatter; + constructor( personalDashboardReadModel: IPersonalDashboardReadModel, projectOwnersReadModel: IProjectOwnersReadModel, projectReadModel: IProjectReadModel, + eventStore: IEventStore, + featureEventFormatter: FeatureEventFormatter, ) { this.personalDashboardReadModel = personalDashboardReadModel; this.projectOwnersReadModel = projectOwnersReadModel; this.projectReadModel = projectReadModel; + this.eventStore = eventStore; + this.featureEventFormatter = featureEventFormatter; } getPersonalFeatures(userId: number): Promise { @@ -46,4 +60,20 @@ export class PersonalDashboardService { return normalizedProjects; } + + async getPersonalProjectDetails( + projectId: string, + ): Promise { + const recentEvents = await this.eventStore.searchEvents( + { project: projectId, limit: 4, offset: 0 }, + [], + ); + + const formattedEvents = recentEvents.map((event) => ({ + summary: this.featureEventFormatter.format(event).text, + createdBy: event.createdBy, + })); + + return { latestEvents: formattedEvents }; + } } diff --git a/src/lib/openapi/spec/personal-dashboard-project-details-schema.ts b/src/lib/openapi/spec/personal-dashboard-project-details-schema.ts index 390c5d11ae55..f8fc503dbc67 100644 --- a/src/lib/openapi/spec/personal-dashboard-project-details-schema.ts +++ b/src/lib/openapi/spec/personal-dashboard-project-details-schema.ts @@ -8,6 +8,29 @@ export const personalDashboardProjectDetailsSchema = { additionalProperties: false, required: ['owners', 'roles'], properties: { + latestEvents: { + type: 'array', + description: 'The latest events for the project.', + items: { + type: 'object', + description: 'An event summary', + additionalProperties: false, + required: ['summary', 'createdBy'], + properties: { + summary: { + type: 'string', + nullable: true, + description: + '**[Experimental]** A markdown-formatted summary of the event.', + }, + createdBy: { + type: 'string', + description: 'Which user created this event', + example: 'johndoe', + }, + }, + }, + }, owners: projectSchema.properties.owners, roles: { type: 'array', diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts index 7a76de191a62..d84327a0be11 100644 --- a/src/lib/services/index.ts +++ b/src/lib/services/index.ts @@ -408,7 +408,7 @@ export const createServices = ( const personalDashboardService = db ? createPersonalDashboardService(db, config) - : createFakePersonalDashboardService(); + : createFakePersonalDashboardService(config); return { accessService,