Skip to content

Commit

Permalink
chore: add connected environments to project status payload (#8645)
Browse files Browse the repository at this point in the history
This PR adds connected environments to the project status payload.

It's done by:
- adding a new `getConnectedEnvironmentCountForProject` method to the
project store (I opted for this approach instead of creating a new view
model because it already has a `getEnvironmentsForProject` method)
- adding the project store to the project status service
- updating the schema

For the schema, I opted for adding a `resources` property, under which I
put `connectedEnvironments`. My thinking was that if we want to add the
rest of the project resources (that go in the resources widget), it'd
make sense to group those together inside an object. However, I'd also
be happy to place the property on the top level. If you have opinions
one way or the other, let me know.

As for the count, we're currently only counting environments that have
metrics and that are active for the current project.
  • Loading branch information
thomasheartman authored Nov 5, 2024
1 parent 6a8a75c commit 1897f8a
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 4 deletions.
12 changes: 11 additions & 1 deletion src/lib/features/project-status/createProjectStatusService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,29 @@ 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';
import ProjectStore from '../project/project-store';
import FakeProjectStore from '../../../test/fixtures/fake-project-store';

export const createProjectStatusService = (
db: Db,
config: IUnleashConfig,
): ProjectStatusService => {
const eventStore = new EventStore(db, config.getLogger);
return new ProjectStatusService({ eventStore });
const projectStore = new ProjectStore(
db,
config.eventBus,
config.getLogger,
config.flagResolver,
);
return new ProjectStatusService({ eventStore, projectStore });
};

export const createFakeProjectStatusService = () => {
const eventStore = new FakeEventStore();
const projectStore = new FakeProjectStore();
const projectStatusService = new ProjectStatusService({
eventStore,
projectStore,
});

return {
Expand Down
16 changes: 14 additions & 2 deletions src/lib/features/project-status/project-status-service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import type { ProjectStatusSchema } from '../../openapi';
import type { IEventStore, IUnleashStores } from '../../types';
import type { IEventStore, IProjectStore, IUnleashStores } from '../../types';

export class ProjectStatusService {
private eventStore: IEventStore;
constructor({ eventStore }: Pick<IUnleashStores, 'eventStore'>) {
private projectStore: IProjectStore;

constructor({
eventStore,
projectStore,
}: Pick<IUnleashStores, 'eventStore' | 'projectStore'>) {
this.eventStore = eventStore;
this.projectStore = projectStore;
}

async getProjectStatus(projectId: string): Promise<ProjectStatusSchema> {
return {
resources: {
connectedEnvironments:
await this.projectStore.getConnectedEnvironmentCountForProject(
projectId,
),
},
activityCountByDate:
await this.eventStore.getProjectEventActivity(projectId),
};
Expand Down
46 changes: 46 additions & 0 deletions src/lib/features/project-status/projects-status.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { FEATURE_CREATED, type IUnleashConfig } from '../../types';
import type { EventService } from '../../services';
import { createEventsService } from '../events/createEventsService';
import { createTestConfig } from '../../../test/config/test-config';
import { randomId } from '../../util';

let app: IUnleashTest;
let db: ITestDb;
Expand Down Expand Up @@ -99,3 +100,48 @@ test('project insights should return correct count for each day', async () => {
],
});
});

test('project status should return environments with connected SDKs', async () => {
const flagName = randomId();
await app.createFeature(flagName);

const envs =
await app.services.environmentService.getProjectEnvironments('default');
expect(envs.some((env) => env.name === 'default')).toBeTruthy();

const appName = 'blah';
const environment = 'default';
await db.stores.clientMetricsStoreV2.batchInsertMetrics([
{
featureName: `flag-doesnt-exist`,
appName,
environment,
timestamp: new Date(),
yes: 5,
no: 2,
},
{
featureName: flagName,
appName: `web2`,
environment,
timestamp: new Date(),
yes: 5,
no: 2,
},
{
featureName: flagName,
appName,
environment: 'not-a-real-env',
timestamp: new Date(),
yes: 2,
no: 2,
},
]);

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

expect(body.resources.connectedEnvironments).toBe(1);
});
2 changes: 2 additions & 0 deletions src/lib/features/project/project-store-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ export interface IProjectStore extends Store<IProject, string> {

getEnvironmentsForProject(id: string): Promise<ProjectEnvironment[]>;

getConnectedEnvironmentCountForProject(id: string): Promise<number>;

getMembersCountByProject(projectId: string): Promise<number>;

getMembersCountByProjectAfterDate(
Expand Down
16 changes: 16 additions & 0 deletions src/lib/features/project/project-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,22 @@ class ProjectStore implements IProjectStore {
return rows.map(this.mapProjectEnvironmentRow);
}

async getConnectedEnvironmentCountForProject(id: string): Promise<number> {
const [{ count }] = (await this.db
.countDistinct('cme.environment')
.from('client_metrics_env as cme')
.innerJoin('features', 'cme.feature_name', 'features.name')
.innerJoin('projects', 'features.project', 'projects.id')
.innerJoin(
'project_environments',
'cme.environment',
'project_environments.environment_name',
)
.where('features.project', id)) as { count: string }[];

return Number(count);
}

async getMembersCountByProject(projectId: string): Promise<number> {
const members = await this.db
.from((db) => {
Expand Down
1 change: 1 addition & 0 deletions src/lib/openapi/spec/project-status-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ test('projectStatusSchema', () => {
{ date: '2022-12-14', count: 2 },
{ date: '2022-12-15', count: 5 },
],
resources: { connectedEnvironments: 2 },
};

expect(
Expand Down
15 changes: 14 additions & 1 deletion src/lib/openapi/spec/project-status-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const projectStatusSchema = {
$id: '#/components/schemas/projectStatusSchema',
type: 'object',
additionalProperties: false,
required: ['activityCountByDate'],
required: ['activityCountByDate', 'resources'],
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: {
Expand All @@ -14,6 +14,19 @@ export const projectStatusSchema = {
description:
'Array of activity records with date and count, representing the project’s daily activity statistics.',
},
resources: {
type: 'object',
additionalProperties: false,
required: ['connectedEnvironments'],
description: 'Key resources within the project',
properties: {
connectedEnvironments: {
type: 'number',
description:
'The number of environments that have received SDK traffic in this project.',
},
},
},
},
components: {
schemas: {
Expand Down
4 changes: 4 additions & 0 deletions src/test/fixtures/fake-project-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,8 @@ export default class FakeProjectStore implements IProjectStore {
project.id === id ? { ...project, archivedAt: null } : project,
);
}

async getConnectedEnvironmentCountForProject(): Promise<number> {
return 0;
}
}

0 comments on commit 1897f8a

Please sign in to comment.