mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-17 01:17:29 +02:00
feat: now backend returns event counts for activity chart (#8638)
This commit is contained in:
parent
6f09bcdee1
commit
0ce49c789e
@ -17,7 +17,10 @@ import type { Db } from '../../db/db';
|
||||
import type { Knex } from 'knex';
|
||||
import type EventEmitter from 'events';
|
||||
import { ADMIN_TOKEN_USER, SYSTEM_USER, SYSTEM_USER_ID } from '../../types';
|
||||
import type { DeprecatedSearchEventsSchema } from '../../openapi';
|
||||
import type {
|
||||
DeprecatedSearchEventsSchema,
|
||||
ProjectActivitySchema,
|
||||
} from '../../openapi';
|
||||
import type { IQueryParam } from '../feature-toggle/types/feature-toggle-strategies-store-type';
|
||||
import { applyGenericQueryParams } from '../feature-search/search-utils';
|
||||
|
||||
@ -406,6 +409,24 @@ class EventStore implements IEventStore {
|
||||
}));
|
||||
}
|
||||
|
||||
async getProjectEventActivity(
|
||||
project: string,
|
||||
): Promise<ProjectActivitySchema> {
|
||||
const result = await this.db('events')
|
||||
.select(
|
||||
this.db.raw("TO_CHAR(created_at::date, 'YYYY-MM-DD') AS date"),
|
||||
)
|
||||
.count('* AS count')
|
||||
.where('project', project)
|
||||
.groupBy(this.db.raw("TO_CHAR(created_at::date, 'YYYY-MM-DD')"))
|
||||
.orderBy('date', 'asc');
|
||||
|
||||
return result.map((row) => ({
|
||||
date: row.date,
|
||||
count: Number(row.count),
|
||||
}));
|
||||
}
|
||||
|
||||
async deprecatedSearchEvents(
|
||||
search: DeprecatedSearchEventsSchema = {},
|
||||
): Promise<IEvent[]> {
|
||||
|
@ -1,15 +1,21 @@
|
||||
import type { Db, IUnleashConfig } from '../../server-impl';
|
||||
import { ProjectStatusService } from './project-status-service';
|
||||
import EventStore from '../events/event-store';
|
||||
import FakeEventStore from '../../../test/fixtures/fake-event-store';
|
||||
|
||||
export const createProjectStatusService = (
|
||||
db: Db,
|
||||
config: IUnleashConfig,
|
||||
): ProjectStatusService => {
|
||||
return new ProjectStatusService();
|
||||
const eventStore = new EventStore(db, config.getLogger);
|
||||
return new ProjectStatusService({ eventStore });
|
||||
};
|
||||
|
||||
export const createFakeProjectStatusService = () => {
|
||||
const projectStatusService = new ProjectStatusService();
|
||||
const eventStore = new FakeEventStore();
|
||||
const projectStatusService = new ProjectStatusService({
|
||||
eventStore,
|
||||
});
|
||||
|
||||
return {
|
||||
projectStatusService,
|
||||
|
@ -39,7 +39,7 @@ export default class ProjectStatusController extends Controller {
|
||||
permission: NONE,
|
||||
middleware: [
|
||||
this.openApiService.validPath({
|
||||
tags: ['Projects'],
|
||||
tags: ['Unstable'],
|
||||
operationId: 'getProjectStatus',
|
||||
summary: 'Get project status',
|
||||
description:
|
||||
|
@ -1,9 +1,16 @@
|
||||
import type { ProjectStatusSchema } from '../../openapi';
|
||||
import type { IEventStore, IUnleashStores } from '../../types';
|
||||
|
||||
export class ProjectStatusService {
|
||||
constructor() {}
|
||||
private eventStore: IEventStore;
|
||||
constructor({ eventStore }: Pick<IUnleashStores, 'eventStore'>) {
|
||||
this.eventStore = eventStore;
|
||||
}
|
||||
|
||||
async getProjectStatus(projectId: string): Promise<ProjectStatusSchema> {
|
||||
return { activityCountByDate: [{ date: '2024-09-11', count: 0 }] };
|
||||
return {
|
||||
activityCountByDate:
|
||||
await this.eventStore.getProjectEventActivity(projectId),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,26 @@ import {
|
||||
setupAppWithCustomConfig,
|
||||
} from '../../../test/e2e/helpers/test-helper';
|
||||
import getLogger from '../../../test/fixtures/no-logger';
|
||||
import { FEATURE_CREATED, type IUnleashConfig } from '../../types';
|
||||
import type { EventService } from '../../services';
|
||||
import { createEventsService } from '../events/createEventsService';
|
||||
import { createTestConfig } from '../../../test/config/test-config';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
let eventService: EventService;
|
||||
|
||||
const TEST_USER_ID = -9999;
|
||||
const config: IUnleashConfig = createTestConfig();
|
||||
|
||||
const getCurrentDateStrings = () => {
|
||||
const today = new Date();
|
||||
const todayString = today.toISOString().split('T')[0];
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(today.getDate() - 1);
|
||||
const yesterdayString = yesterday.toISOString().split('T')[0];
|
||||
return { todayString, yesterdayString };
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('projects_status', getLogger);
|
||||
@ -21,6 +38,7 @@ beforeAll(async () => {
|
||||
},
|
||||
db.rawDatabase,
|
||||
);
|
||||
eventService = createEventsService(db.rawDatabase, config);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -28,13 +46,55 @@ afterAll(async () => {
|
||||
await db.destroy();
|
||||
});
|
||||
|
||||
test('project insights happy path', async () => {
|
||||
test('project insights should return correct count for each day', async () => {
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
project: 'default',
|
||||
data: { featureName: 'today-event' },
|
||||
createdBy: 'test-user',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
ip: '127.0.0.1',
|
||||
});
|
||||
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
project: 'default',
|
||||
data: { featureName: 'today-event-two' },
|
||||
createdBy: 'test-user',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
ip: '127.0.0.1',
|
||||
});
|
||||
|
||||
await eventService.storeEvent({
|
||||
type: FEATURE_CREATED,
|
||||
project: 'default',
|
||||
data: { featureName: 'yesterday-event' },
|
||||
createdBy: 'test-user',
|
||||
createdByUserId: TEST_USER_ID,
|
||||
ip: '127.0.0.1',
|
||||
});
|
||||
|
||||
const { events } = await eventService.getEvents();
|
||||
|
||||
const yesterdayEvent = events.find(
|
||||
(e) => e.data.featureName === 'yesterday-event',
|
||||
);
|
||||
await db.rawDatabase.raw(
|
||||
`UPDATE events SET created_at = '2024-11-03' where id = ?`,
|
||||
[yesterdayEvent?.id],
|
||||
);
|
||||
|
||||
const { body } = await app.request
|
||||
.get('/api/admin/projects/default/status')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
|
||||
const { todayString, yesterdayString } = getCurrentDateStrings();
|
||||
|
||||
expect(body).toMatchObject({
|
||||
activityCountByDate: [{ date: '2024-09-11', count: 0 }],
|
||||
activityCountByDate: [
|
||||
{ date: yesterdayString, count: 1 },
|
||||
{ date: todayString, count: 2 },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,9 @@
|
||||
import type { IBaseEvent, IEvent } from '../events';
|
||||
import type { Store } from './store';
|
||||
import type { DeprecatedSearchEventsSchema } from '../../openapi';
|
||||
import type {
|
||||
DeprecatedSearchEventsSchema,
|
||||
ProjectActivitySchema,
|
||||
} from '../../openapi';
|
||||
import type EventEmitter from 'events';
|
||||
import type { IQueryOperations } from '../../features/events/event-store';
|
||||
import type { IQueryParam } from '../../features/feature-toggle/types/feature-toggle-strategies-store-type';
|
||||
@ -44,4 +47,5 @@ export interface IEventStore
|
||||
queryCount(operations: IQueryOperations[]): Promise<number>;
|
||||
setCreatedByUserId(batchSize: number): Promise<number | undefined>;
|
||||
getEventCreators(): Promise<Array<{ id: number; name: string }>>;
|
||||
getProjectEventActivity(project: string): Promise<ProjectActivitySchema>;
|
||||
}
|
||||
|
9
src/test/fixtures/fake-event-store.ts
vendored
9
src/test/fixtures/fake-event-store.ts
vendored
@ -2,7 +2,10 @@ import type { IEventStore } from '../../lib/types/stores/event-store';
|
||||
import type { IBaseEvent, IEvent } from '../../lib/types/events';
|
||||
import { sharedEventEmitter } from '../../lib/util/anyEventEmitter';
|
||||
import type { IQueryOperations } from '../../lib/features/events/event-store';
|
||||
import type { DeprecatedSearchEventsSchema } from '../../lib/openapi';
|
||||
import type {
|
||||
DeprecatedSearchEventsSchema,
|
||||
ProjectActivitySchema,
|
||||
} from '../../lib/openapi';
|
||||
import type EventEmitter from 'events';
|
||||
|
||||
class FakeEventStore implements IEventStore {
|
||||
@ -15,6 +18,10 @@ class FakeEventStore implements IEventStore {
|
||||
this.events = [];
|
||||
}
|
||||
|
||||
getProjectEventActivity(project: string): Promise<ProjectActivitySchema> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getEventCreators(): Promise<{ id: number; name: string }[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user