mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-04 00:18:40 +01:00
feat: outdated sdks api (#6539)
This commit is contained in:
parent
3c22a302c7
commit
9438400e77
@ -205,6 +205,20 @@ export default class ClientInstanceStore implements IClientInstanceStore {
|
||||
return rows.map(mapRow);
|
||||
}
|
||||
|
||||
async groupApplicationsBySdk(): Promise<
|
||||
{ sdkVersion: string; applications: string[] }[]
|
||||
> {
|
||||
const rows = await this.db
|
||||
.select([
|
||||
'sdk_version as sdkVersion',
|
||||
this.db.raw('ARRAY_AGG(DISTINCT app_name) as applications'),
|
||||
])
|
||||
.from(TABLE)
|
||||
.groupBy('sdk_version');
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async getDistinctApplications(): Promise<string[]> {
|
||||
const rows = await this.db
|
||||
.distinct('app_name')
|
||||
|
@ -14,16 +14,18 @@ const config: SDKConfig = {
|
||||
'unleash-client-php': '1.13.0',
|
||||
};
|
||||
|
||||
export const isOutdatedSdk = (sdkVersion: string) => {
|
||||
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;
|
||||
};
|
||||
|
||||
export function findOutdatedSDKs(sdkVersions: string[]): string[] {
|
||||
const uniqueSdkVersions = Array.from(new Set(sdkVersions));
|
||||
|
||||
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;
|
||||
});
|
||||
return uniqueSdkVersions.filter(isOutdatedSdk);
|
||||
}
|
||||
|
@ -22,7 +22,8 @@ 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';
|
||||
import { findOutdatedSDKs, isOutdatedSdk } from './findOutdatedSdks';
|
||||
import { OutdatedSdksSchema } from '../../../openapi/spec/outdated-sdks-schema';
|
||||
|
||||
export default class ClientInstanceService {
|
||||
apps = {};
|
||||
@ -261,6 +262,12 @@ export default class ClientInstanceService {
|
||||
return this.clientInstanceStore.removeInstancesOlderThanTwoDays();
|
||||
}
|
||||
|
||||
async getOutdatedSdks(): Promise<OutdatedSdksSchema['sdks']> {
|
||||
const sdkApps = await this.clientInstanceStore.groupApplicationsBySdk();
|
||||
|
||||
return sdkApps.filter((sdkApp) => isOutdatedSdk(sdkApp.sdkVersion));
|
||||
}
|
||||
|
||||
async usesSdkOlderThan(
|
||||
sdkName: string,
|
||||
sdkVersion: string,
|
||||
|
@ -112,6 +112,7 @@ export * from './login-schema';
|
||||
export * from './maintenance-schema';
|
||||
export * from './me-schema';
|
||||
export * from './name-schema';
|
||||
export * from './outdated-sdks-schema';
|
||||
export * from './override-schema';
|
||||
export * from './parameters-schema';
|
||||
export * from './parent-feature-options-schema';
|
||||
|
41
src/lib/openapi/spec/outdated-sdks-schema.ts
Normal file
41
src/lib/openapi/spec/outdated-sdks-schema.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
|
||||
export const outdatedSdksSchema = {
|
||||
$id: '#/components/schemas/outdatedSdksSchema',
|
||||
type: 'object',
|
||||
description: 'Data about outdated SDKs that should be upgraded.',
|
||||
additionalProperties: false,
|
||||
required: ['sdks'],
|
||||
properties: {
|
||||
sdks: {
|
||||
type: 'array',
|
||||
description: 'A list of SDKs',
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['sdkVersion', 'applications'],
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
sdkVersion: {
|
||||
type: 'string',
|
||||
description:
|
||||
'An outdated SDK version identifier. Usually formatted as "unleash-client-<language>:<version>"',
|
||||
example: 'unleash-client-java:7.0.0',
|
||||
},
|
||||
applications: {
|
||||
type: 'array',
|
||||
items: {
|
||||
description: 'Name of the application',
|
||||
type: 'string',
|
||||
example: 'accounting',
|
||||
},
|
||||
description:
|
||||
'A list of applications using the SDK version',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
} as const;
|
||||
|
||||
export type OutdatedSdksSchema = FromSchema<typeof outdatedSdksSchema>;
|
@ -29,6 +29,10 @@ import {
|
||||
applicationEnvironmentInstancesSchema,
|
||||
ApplicationEnvironmentInstancesSchema,
|
||||
} from '../../openapi/spec/application-environment-instances-schema';
|
||||
import {
|
||||
outdatedSdksSchema,
|
||||
OutdatedSdksSchema,
|
||||
} from '../../openapi/spec/outdated-sdks-schema';
|
||||
|
||||
class MetricsController extends Controller {
|
||||
private logger: Logger;
|
||||
@ -177,6 +181,25 @@ class MetricsController extends Controller {
|
||||
}),
|
||||
],
|
||||
});
|
||||
this.route({
|
||||
method: 'get',
|
||||
path: '/sdks/outdated',
|
||||
handler: this.getOutdatedSdks,
|
||||
permission: NONE,
|
||||
middleware: [
|
||||
openApiService.validPath({
|
||||
tags: ['Unstable'],
|
||||
operationId: 'getOutdatedSdks',
|
||||
summary: 'Get outdated SDKs',
|
||||
description:
|
||||
'Returns a list of the outdated SDKS with the applications using them.',
|
||||
responses: {
|
||||
200: createResponseSchema('outdatedSdksSchema'),
|
||||
...getStandardResponses(404),
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async deprecated(req: Request, res: Response): Promise<void> {
|
||||
@ -277,6 +300,20 @@ class MetricsController extends Controller {
|
||||
);
|
||||
}
|
||||
|
||||
async getOutdatedSdks(req: Request, res: Response<OutdatedSdksSchema>) {
|
||||
if (!this.flagResolver.isEnabled('sdkReporting')) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
const outdatedSdks = await this.clientInstanceService.getOutdatedSdks();
|
||||
|
||||
this.openApiService.respondWithValidation(
|
||||
200,
|
||||
res,
|
||||
outdatedSdksSchema.$id,
|
||||
{ sdks: outdatedSdks },
|
||||
);
|
||||
}
|
||||
|
||||
async getApplicationEnvironmentInstances(
|
||||
req: Request<{ appName: string; environment: string }>,
|
||||
res: Response<ApplicationEnvironmentInstancesSchema>,
|
||||
|
@ -26,6 +26,9 @@ export interface IClientInstanceStore
|
||||
environment: string,
|
||||
): Promise<IClientInstance[]>;
|
||||
getBySdkName(sdkName: string): Promise<IClientInstance[]>;
|
||||
groupApplicationsBySdk(): Promise<
|
||||
{ sdkVersion: string; applications: string[] }[]
|
||||
>;
|
||||
getDistinctApplications(): Promise<string[]>;
|
||||
getDistinctApplicationsCount(daysBefore?: number): Promise<number>;
|
||||
deleteForApplication(appName: string): Promise<void>;
|
||||
|
@ -85,7 +85,7 @@ afterAll(async () => {
|
||||
await db.destroy();
|
||||
});
|
||||
|
||||
test('should show correct number of total', async () => {
|
||||
test('should show correct application metrics', async () => {
|
||||
await Promise.all([
|
||||
app.createFeature('toggle-name-1'),
|
||||
app.createFeature('toggle-name-2'),
|
||||
@ -94,7 +94,7 @@ test('should show correct number of total', async () => {
|
||||
appName: metrics.appName,
|
||||
instanceId: metrics.instanceId,
|
||||
strategies: ['default'],
|
||||
sdkVersion: 'unleash-client-test',
|
||||
sdkVersion: 'unleash-client-node:3.2.1',
|
||||
started: Date.now(),
|
||||
interval: 10,
|
||||
}),
|
||||
@ -102,7 +102,7 @@ test('should show correct number of total', async () => {
|
||||
appName: metrics.appName,
|
||||
instanceId: 'another-instance',
|
||||
strategies: ['default'],
|
||||
sdkVersion: 'unleash-client-test2',
|
||||
sdkVersion: 'unleash-client-node:3.2.2',
|
||||
started: Date.now(),
|
||||
interval: 10,
|
||||
}),
|
||||
@ -129,7 +129,10 @@ test('should show correct number of total', async () => {
|
||||
{
|
||||
instanceCount: 2,
|
||||
name: 'default',
|
||||
sdks: ['unleash-client-test', 'unleash-client-test2'],
|
||||
sdks: [
|
||||
'unleash-client-node:3.2.1',
|
||||
'unleash-client-node:3.2.2',
|
||||
],
|
||||
},
|
||||
],
|
||||
featureCount: 3,
|
||||
@ -143,12 +146,31 @@ test('should show correct number of total', async () => {
|
||||
)
|
||||
.expect(200);
|
||||
|
||||
expect(instancesBody).toMatchObject({
|
||||
instances: [
|
||||
{ instanceId: 'instanceId', sdkVersion: 'unleash-client-test' },
|
||||
expect(
|
||||
instancesBody.instances.sort((a, b) =>
|
||||
a.instanceId.localeCompare(b.instanceId),
|
||||
),
|
||||
).toMatchObject([
|
||||
{
|
||||
instanceId: 'another-instance',
|
||||
sdkVersion: 'unleash-client-node:3.2.2',
|
||||
},
|
||||
{ instanceId: 'instanceId', sdkVersion: 'unleash-client-node:3.2.1' },
|
||||
]);
|
||||
|
||||
const { body: outdatedSdks } = await app.request
|
||||
.get(`/api/admin/metrics/sdks/outdated`)
|
||||
.expect(200);
|
||||
|
||||
expect(outdatedSdks).toMatchObject({
|
||||
sdks: [
|
||||
{
|
||||
instanceId: 'another-instance',
|
||||
sdkVersion: 'unleash-client-test2',
|
||||
sdkVersion: 'unleash-client-node:3.2.1',
|
||||
applications: ['appName'],
|
||||
},
|
||||
{
|
||||
sdkVersion: 'unleash-client-node:3.2.2',
|
||||
applications: ['appName'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
18
src/test/fixtures/fake-client-instance-store.ts
vendored
18
src/test/fixtures/fake-client-instance-store.ts
vendored
@ -4,6 +4,7 @@ import {
|
||||
INewClientInstance,
|
||||
} from '../../lib/types/stores/client-instance-store';
|
||||
import NotFoundError from '../../lib/error/notfound-error';
|
||||
import groupBy from 'lodash.groupby';
|
||||
|
||||
export default class FakeClientInstanceStore implements IClientInstanceStore {
|
||||
instances: IClientInstance[] = [];
|
||||
@ -32,10 +33,19 @@ export default class FakeClientInstanceStore implements IClientInstanceStore {
|
||||
}
|
||||
|
||||
async getBySdkName(sdkName: string): Promise<IClientInstance[]> {
|
||||
return Promise.resolve(
|
||||
this.instances.filter((instance) =>
|
||||
instance.sdkVersion?.startsWith(sdkName),
|
||||
),
|
||||
return this.instances.filter((instance) =>
|
||||
instance.sdkVersion?.startsWith(sdkName),
|
||||
);
|
||||
}
|
||||
|
||||
async groupApplicationsBySdk(): Promise<
|
||||
{ sdkVersion: string; applications: string[] }[]
|
||||
> {
|
||||
return Object.entries(groupBy(this.instances, 'sdkVersion')).map(
|
||||
([sdkVersion, apps]) => ({
|
||||
sdkVersion,
|
||||
applications: apps.map((item) => item.appName),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user