From 1acb4bbb36f2cc9e75fef4cfb9ff2a21749e4ff4 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Thu, 29 Feb 2024 11:30:56 +0100 Subject: [PATCH] feat: outdated sdk detection (#6381) --- .../metrics/instance/findOutdatedSdks.test.ts | 59 +++++++++++++++++++ .../metrics/instance/findOutdatedSdks.ts | 30 ++++++++++ .../metrics/instance/instance-service.ts | 14 ++++- .../application-overview-issues-schema.ts | 2 +- .../e2e/api/admin/applications.e2e.test.ts | 8 ++- 5 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 src/lib/features/metrics/instance/findOutdatedSdks.test.ts create mode 100644 src/lib/features/metrics/instance/findOutdatedSdks.ts diff --git a/src/lib/features/metrics/instance/findOutdatedSdks.test.ts b/src/lib/features/metrics/instance/findOutdatedSdks.test.ts new file mode 100644 index 0000000000..654335dd8c --- /dev/null +++ b/src/lib/features/metrics/instance/findOutdatedSdks.test.ts @@ -0,0 +1,59 @@ +import { findOutdatedSDKs } from './findOutdatedSdks'; + +describe('findOutdatedSDKs', () => { + it('should return an empty array when all SDKs are up to date', () => { + const sdkVersions = [ + 'unleash-client-node:6.0.0', + 'unleash-client-php:2.0.0', + ]; + const result = findOutdatedSDKs(sdkVersions); + expect(result).toEqual([]); + }); + + it('should return an array with outdated SDKs', () => { + const sdkVersions = [ + 'unleash-client-node:3.9.9', + 'unleash-client-php:0.9.9', + ]; + const result = findOutdatedSDKs(sdkVersions); + expect(result).toEqual([ + 'unleash-client-node:3.9.9', + 'unleash-client-php:0.9.9', + ]); + }); + + it('should ignore SDKs not in the config', () => { + const sdkVersions = ['unleash-client-pony:2.0.0']; + const result = findOutdatedSDKs(sdkVersions); + expect(result).toEqual([]); + }); + + it('should handle and remove duplicate SDK versions', () => { + const sdkVersions = [ + 'unleash-client-node:3.8.0', + 'unleash-client-node:3.8.0', + 'unleash-client-php:0.9.0', + 'unleash-client-php:0.9.0', + ]; + const result = findOutdatedSDKs(sdkVersions); + expect(result).toEqual([ + 'unleash-client-node:3.8.0', + 'unleash-client-php:0.9.0', + ]); + }); + + it('should correctly handle semver versions', () => { + const sdkVersions = [ + 'unleash-client-node:6.1.0', + 'unleash-client-php:1.20.3-beta.0', + ]; + const result = findOutdatedSDKs(sdkVersions); + expect(result).toEqual([]); + }); + + it('should ignore invalid SDK versions', () => { + const sdkVersions = ['unleash-client-node', '1.2.3']; + const result = findOutdatedSDKs(sdkVersions); + expect(result).toEqual([]); + }); +}); diff --git a/src/lib/features/metrics/instance/findOutdatedSdks.ts b/src/lib/features/metrics/instance/findOutdatedSdks.ts new file mode 100644 index 0000000000..ec75b9b235 --- /dev/null +++ b/src/lib/features/metrics/instance/findOutdatedSdks.ts @@ -0,0 +1,30 @@ +import semver from 'semver'; + +type SDKConfig = { + [key: string]: string; +}; + +const config: SDKConfig = { + 'unleash-client-node': '5.3.2', + 'unleash-client-java': '9.0.0', + 'unleash-client-go': '4.1.0', + 'unleash-client-python': '5.9.2', + 'unleash-client-ruby': '5.0.0', + 'unleash-client-dotnet': '4.1.3', + 'unleash-client-php': '1.13.1', +}; + +export function findOutdatedSDKs(sdkVersions: string[]): string[] { + const uniqueSdkVersions = Array.from(new Set(sdkVersions)); + const outdatedSDKs: string[] = []; + + return uniqueSdkVersions.filter((sdkVersion) => { + const result = sdkVersion.split(':'); + if (result.length !== 2) return false; + const [sdkName, version] = result; + const minVersion = config[sdkName]; + if (!minVersion) return false; + if (semver.lt(version, minVersion)) return true; + return false; + }); +} diff --git a/src/lib/features/metrics/instance/instance-service.ts b/src/lib/features/metrics/instance/instance-service.ts index 34d54c9c8e..4e8e91aa88 100644 --- a/src/lib/features/metrics/instance/instance-service.ts +++ b/src/lib/features/metrics/instance/instance-service.ts @@ -22,6 +22,7 @@ import { IPrivateProjectChecker } from '../../private-project/privateProjectChec import { IFlagResolver, SYSTEM_USER } from '../../../types'; import { ALL_PROJECTS, parseStrictSemVer } from '../../../util'; import { Logger } from '../../../logger'; +import { findOutdatedSDKs } from './findOutdatedSdks'; export default class ClientInstanceService { apps = {}; @@ -219,7 +220,18 @@ export default class ClientInstanceService { async getApplicationOverview( appName: string, ): Promise { - return this.clientApplicationsStore.getApplicationOverview(appName); + const result = + await this.clientApplicationsStore.getApplicationOverview(appName); + + const sdks = result.environments.flatMap( + (environment) => environment.sdks, + ); + const outdatedSdks = findOutdatedSDKs(sdks); + if (outdatedSdks.length > 0) { + result.issues.push({ type: 'outdatedSdks', items: outdatedSdks }); + } + + return result; } async getApplicationEnvironmentInstances( diff --git a/src/lib/openapi/spec/application-overview-issues-schema.ts b/src/lib/openapi/spec/application-overview-issues-schema.ts index 231915f767..0fd9a33372 100644 --- a/src/lib/openapi/spec/application-overview-issues-schema.ts +++ b/src/lib/openapi/spec/application-overview-issues-schema.ts @@ -9,7 +9,7 @@ export const applicationOverviewIssuesSchema = { properties: { type: { type: 'string', - enum: ['missingFeatures', 'missingStrategies'], + enum: ['missingFeatures', 'missingStrategies', 'outdatedSdks'], description: 'The name of this action.', }, items: { diff --git a/src/test/e2e/api/admin/applications.e2e.test.ts b/src/test/e2e/api/admin/applications.e2e.test.ts index c34a22db92..326817fa1c 100644 --- a/src/test/e2e/api/admin/applications.e2e.test.ts +++ b/src/test/e2e/api/admin/applications.e2e.test.ts @@ -159,7 +159,7 @@ test('should show missing features and strategies', async () => { appName: metrics.appName, instanceId: metrics.instanceId, strategies: ['my-special-strategy'], - sdkVersion: 'unleash-client-test', + sdkVersion: 'unleash-client-node:1.0.0', started: Date.now(), interval: 10, }), @@ -188,12 +188,16 @@ test('should show missing features and strategies', async () => { type: 'missingStrategies', items: ['my-special-strategy'], }, + { + type: 'outdatedSdks', + items: ['unleash-client-node:1.0.0'], + }, ], environments: [ { instanceCount: 1, name: 'default', - sdks: ['unleash-client-test'], + sdks: ['unleash-client-node:1.0.0'], }, ], featureCount: 3,