Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: now backend returns event counts for activity chart #8638

Merged
merged 5 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion src/lib/features/events/event-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
ProjectStatusSchema,
} from '../../openapi';
import type { IQueryParam } from '../feature-toggle/types/feature-toggle-strategies-store-type';
import { applyGenericQueryParams } from '../feature-search/search-utils';

Expand Down Expand Up @@ -406,6 +409,24 @@ class EventStore implements IEventStore {
}));
}

async getEventCounts(project: string): Promise<ProjectStatusSchema> {
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', 'default')
.groupBy(this.db.raw("TO_CHAR(created_at::date, 'YYYY-MM-DD')"))
.orderBy('date', 'asc');

return {
activityCountByDate: result.map((row) => ({
date: row.date,
count: Number(row.count),
})),
};
}

async deprecatedSearchEvents(
search: DeprecatedSearchEventsSchema = {},
): Promise<IEvent[]> {
Expand Down
10 changes: 8 additions & 2 deletions src/lib/features/project-status/createProjectStatusService.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 6 additions & 2 deletions src/lib/features/project-status/project-status-service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
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 this.eventStore.getEventCounts(projectId);
}
}
59 changes: 57 additions & 2 deletions src/lib/features/project-status/projects-status.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ 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();

beforeAll(async () => {
db = await dbInit('projects_status', getLogger);
Expand All @@ -21,20 +29,67 @@ beforeAll(async () => {
},
db.rawDatabase,
);
eventService = createEventsService(db.rawDatabase, config);
});

afterAll(async () => {
await app.destroy();
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 lateEvent = events.find(
sjaanus marked this conversation as resolved.
Show resolved Hide resolved
(e) => e.data.featureName === 'yesterday-event',
);
await db.rawDatabase.raw(
`UPDATE events SET created_at = created_at - interval '1 day' where id = ?`,
[lateEvent?.id],
);

const { body } = await app.request
.get('/api/admin/projects/default/status')
.expect('Content-Type', /json/)
.expect(200);

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];
sjaanus marked this conversation as resolved.
Show resolved Hide resolved

expect(body).toMatchObject({
activityCountByDate: [{ date: '2024-09-11', count: 0 }],
activityCountByDate: [
{ date: yesterdayString, count: 1 },
{ date: todayString, count: 2 },
],
});
});
6 changes: 5 additions & 1 deletion src/lib/types/stores/event-store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { IBaseEvent, IEvent } from '../events';
import type { Store } from './store';
import type { DeprecatedSearchEventsSchema } from '../../openapi';
import type {
DeprecatedSearchEventsSchema,
ProjectStatusSchema,
} 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';
Expand Down Expand Up @@ -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 }>>;
getEventCounts(project: string): Promise<ProjectStatusSchema>;
sjaanus marked this conversation as resolved.
Show resolved Hide resolved
}
9 changes: 8 additions & 1 deletion src/test/fixtures/fake-event-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
ProjectStatusSchema,
} from '../../lib/openapi';
import type EventEmitter from 'events';

class FakeEventStore implements IEventStore {
Expand All @@ -15,6 +18,10 @@ class FakeEventStore implements IEventStore {
this.events = [];
}

getEventCounts(project: string): Promise<ProjectStatusSchema> {
throw new Error('Method not implemented.');
}

getEventCreators(): Promise<{ id: number; name: string }[]> {
throw new Error('Method not implemented.');
}
Expand Down
Loading