From a204f2c61531e53c6c10fe60b086998518d4c8ce Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Mon, 20 May 2024 12:58:30 +0300 Subject: [PATCH] feat: outdated sdks project level (#7080) This adds project level endpoint to catch outdated SDKs only for that project. --- src/lib/db/client-instance-store.ts | 16 +++++++ .../metrics/instance/instance-service.ts | 11 +++++ .../features/project/project-controller.ts | 43 +++++++++++++++++++ src/lib/types/stores/client-instance-store.ts | 3 ++ .../e2e/api/admin/applications.e2e.test.ts | 2 +- .../fixtures/fake-client-instance-store.ts | 6 +++ 6 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/lib/db/client-instance-store.ts b/src/lib/db/client-instance-store.ts index 02d35f49d7..0b8533f395 100644 --- a/src/lib/db/client-instance-store.ts +++ b/src/lib/db/client-instance-store.ts @@ -218,6 +218,22 @@ export default class ClientInstanceStore implements IClientInstanceStore { return rows; } + async groupApplicationsBySdkAndProject( + projectId: string, + ): Promise<{ sdkVersion: string; applications: string[] }[]> { + const rows = await this.db + .select([ + 'ci.sdk_version as sdkVersion', + this.db.raw('ARRAY_AGG(DISTINCT cme.app_name) as applications'), + ]) + .from('client_metrics_env as cme') + .leftJoin('features as f', 'f.name', 'cme.feature_name') + .leftJoin('client_instances as ci', 'ci.app_name', 'cme.app_name') + .where('f.project', projectId) + .groupBy('ci.sdk_version'); + + return rows; + } async getDistinctApplications(): Promise { const rows = await this.db diff --git a/src/lib/features/metrics/instance/instance-service.ts b/src/lib/features/metrics/instance/instance-service.ts index 2855ac5d27..7154f3987c 100644 --- a/src/lib/features/metrics/instance/instance-service.ts +++ b/src/lib/features/metrics/instance/instance-service.ts @@ -275,6 +275,17 @@ export default class ClientInstanceService { return sdkApps.filter((sdkApp) => isOutdatedSdk(sdkApp.sdkVersion)); } + async getOutdatedSdksByProject( + projectId: string, + ): Promise { + const sdkApps = + await this.clientInstanceStore.groupApplicationsBySdkAndProject( + projectId, + ); + + return sdkApps.filter((sdkApp) => isOutdatedSdk(sdkApp.sdkVersion)); + } + async usesSdkOlderThan( sdkName: string, sdkVersion: string, diff --git a/src/lib/features/project/project-controller.ts b/src/lib/features/project/project-controller.ts index 0637bf28bd..0b7f12f4ba 100644 --- a/src/lib/features/project/project-controller.ts +++ b/src/lib/features/project/project-controller.ts @@ -18,6 +18,8 @@ import { createResponseSchema, type DeprecatedProjectOverviewSchema, deprecatedProjectOverviewSchema, + outdatedSdksSchema, + type OutdatedSdksSchema, type ProjectDoraMetricsSchema, projectDoraMetricsSchema, projectOverviewSchema, @@ -41,17 +43,21 @@ import { projectApplicationsQueryParameters } from '../../openapi/spec/project-a import { normalizeQueryParams } from '../feature-search/search-utils'; import ProjectInsightsController from '../project-insights/project-insights-controller'; import FeatureLifecycleController from '../feature-lifecycle/feature-lifecycle-controller'; +import type ClientInstanceService from '../metrics/instance/instance-service'; export default class ProjectController extends Controller { private projectService: ProjectService; private openApiService: OpenApiService; + private clientInstanceService: ClientInstanceService; + private flagResolver: IFlagResolver; constructor(config: IUnleashConfig, services: IUnleashServices, db: Db) { super(config); this.projectService = services.projectService; + this.clientInstanceService = services.clientInstanceService; this.openApiService = services.openApiService; this.flagResolver = config.flagResolver; @@ -160,6 +166,26 @@ export default class ProjectController extends Controller { ], }); + this.route({ + method: 'get', + path: '/:projectId/sdks/outdated', + handler: this.getOutdatedProjectSdks, + permission: NONE, + middleware: [ + this.openApiService.validPath({ + tags: ['Unstable'], + operationId: 'getOutdatedProjectSdks', + summary: 'Get outdated project SDKs', + description: + 'Returns a list of the outdated SDKS with the applications using them.', + responses: { + 200: createResponseSchema('outdatedSdksSchema'), + ...getStandardResponses(404), + }, + }), + ], + }); + this.use( '/', new ProjectFeaturesController( @@ -310,4 +336,21 @@ export default class ProjectController extends Controller { serializeDates(applications), ); } + async getOutdatedProjectSdks( + req: IAuthRequest, + res: Response, + ) { + const { projectId } = req.params; + const outdatedSdks = + await this.clientInstanceService.getOutdatedSdksByProject( + projectId, + ); + + this.openApiService.respondWithValidation( + 200, + res, + outdatedSdksSchema.$id, + { sdks: outdatedSdks }, + ); + } } diff --git a/src/lib/types/stores/client-instance-store.ts b/src/lib/types/stores/client-instance-store.ts index 6c5c916f28..1db8baeda2 100644 --- a/src/lib/types/stores/client-instance-store.ts +++ b/src/lib/types/stores/client-instance-store.ts @@ -29,6 +29,9 @@ export interface IClientInstanceStore groupApplicationsBySdk(): Promise< { sdkVersion: string; applications: string[] }[] >; + groupApplicationsBySdkAndProject( + projectId: string, + ): Promise<{ sdkVersion: string; applications: string[] }[]>; getDistinctApplications(): Promise; getDistinctApplicationsCount(daysBefore?: number): Promise; deleteForApplication(appName: string): Promise; diff --git a/src/test/e2e/api/admin/applications.e2e.test.ts b/src/test/e2e/api/admin/applications.e2e.test.ts index 41bd760193..fc5bca8b86 100644 --- a/src/test/e2e/api/admin/applications.e2e.test.ts +++ b/src/test/e2e/api/admin/applications.e2e.test.ts @@ -158,7 +158,7 @@ test('should show correct application metrics', async () => { ]); const { body: outdatedSdks } = await app.request - .get(`/api/admin/metrics/sdks/outdated`) + .get(`/api/admin/projects/default/sdks/outdated`) .expect(200); expect(outdatedSdks).toMatchObject({ diff --git a/src/test/fixtures/fake-client-instance-store.ts b/src/test/fixtures/fake-client-instance-store.ts index 4303ec5d59..5201389efd 100644 --- a/src/test/fixtures/fake-client-instance-store.ts +++ b/src/test/fixtures/fake-client-instance-store.ts @@ -49,6 +49,12 @@ export default class FakeClientInstanceStore implements IClientInstanceStore { ); } + async groupApplicationsBySdkAndProject( + projectId: string, + ): Promise<{ sdkVersion: string; applications: string[] }[]> { + throw new Error('Not implemented in mock'); + } + async deleteAll(): Promise { this.instances = []; }