1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

chore: add connected environments to project status payload (#8645)

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.
This commit is contained in:
Thomas Heartman 2024-11-05 11:12:08 +01:00 committed by GitHub
parent 6a8a75ce71
commit 1897f8a19d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 108 additions and 4 deletions

View File

@ -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 {

View File

@ -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),
};

View File

@ -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;
@ -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);
});

View File

@ -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(

View File

@ -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) => {

View File

@ -7,6 +7,7 @@ test('projectStatusSchema', () => {
{ date: '2022-12-14', count: 2 },
{ date: '2022-12-15', count: 5 },
],
resources: { connectedEnvironments: 2 },
};
expect(

View File

@ -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 projects activity level over time.',
properties: {
@ -14,6 +14,19 @@ export const projectStatusSchema = {
description:
'Array of activity records with date and count, representing the projects 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: {

View File

@ -214,4 +214,8 @@ export default class FakeProjectStore implements IProjectStore {
project.id === id ? { ...project, archivedAt: null } : project,
);
}
async getConnectedEnvironmentCountForProject(): Promise<number> {
return 0;
}
}