mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-19 17:52:45 +02:00
feat: app env instances api (#6339)
This commit is contained in:
parent
2c5d4ba0ce
commit
91c08593a6
@ -180,6 +180,21 @@ export default class ClientInstanceStore implements IClientInstanceStore {
|
|||||||
return rows.map(mapRow);
|
return rows.map(mapRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getByAppNameAndEnvironment(
|
||||||
|
appName: string,
|
||||||
|
environment: string,
|
||||||
|
): Promise<IClientInstance[]> {
|
||||||
|
const rows = await this.db
|
||||||
|
.select()
|
||||||
|
.from(TABLE)
|
||||||
|
.where('app_name', appName)
|
||||||
|
.where('environment', environment)
|
||||||
|
.orderBy('last_seen', 'desc')
|
||||||
|
.limit(1000);
|
||||||
|
|
||||||
|
return rows.map(mapRow);
|
||||||
|
}
|
||||||
|
|
||||||
async getBySdkName(sdkName: string): Promise<IClientInstance[]> {
|
async getBySdkName(sdkName: string): Promise<IClientInstance[]> {
|
||||||
const sdkPrefix = `${sdkName}%`;
|
const sdkPrefix = `${sdkName}%`;
|
||||||
const rows = await this.db
|
const rows = await this.db
|
||||||
|
@ -222,6 +222,24 @@ export default class ClientInstanceService {
|
|||||||
return this.clientApplicationsStore.getApplicationOverview(appName);
|
return this.clientApplicationsStore.getApplicationOverview(appName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getApplicationEnvironmentInstances(
|
||||||
|
appName: string,
|
||||||
|
environment: string,
|
||||||
|
) {
|
||||||
|
const instances =
|
||||||
|
await this.clientInstanceStore.getByAppNameAndEnvironment(
|
||||||
|
appName,
|
||||||
|
environment,
|
||||||
|
);
|
||||||
|
|
||||||
|
return instances.map((instance) => ({
|
||||||
|
instanceId: instance.instanceId,
|
||||||
|
clientIp: instance.clientIp,
|
||||||
|
sdkVersion: instance.sdkVersion,
|
||||||
|
lastSeen: instance.lastSeen,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
async deleteApplication(appName: string): Promise<void> {
|
async deleteApplication(appName: string): Promise<void> {
|
||||||
await this.clientInstanceStore.deleteForApplication(appName);
|
await this.clientInstanceStore.deleteForApplication(appName);
|
||||||
await this.clientApplicationsStore.delete(appName);
|
await this.clientApplicationsStore.delete(appName);
|
||||||
|
@ -201,6 +201,7 @@ import { rolesSchema } from './spec/roles-schema';
|
|||||||
import { applicationOverviewSchema } from './spec/application-overview-schema';
|
import { applicationOverviewSchema } from './spec/application-overview-schema';
|
||||||
import { applicationOverviewEnvironmentSchema } from './spec/application-overview-environment-schema';
|
import { applicationOverviewEnvironmentSchema } from './spec/application-overview-environment-schema';
|
||||||
import { applicationOverviewIssuesSchema } from './spec/application-overview-issues-schema';
|
import { applicationOverviewIssuesSchema } from './spec/application-overview-issues-schema';
|
||||||
|
import { applicationEnvironmentInstancesSchema } from './spec/application-environment-instances-schema';
|
||||||
|
|
||||||
// Schemas must have an $id property on the form "#/components/schemas/mySchema".
|
// Schemas must have an $id property on the form "#/components/schemas/mySchema".
|
||||||
export type SchemaId = (typeof schemas)[keyof typeof schemas]['$id'];
|
export type SchemaId = (typeof schemas)[keyof typeof schemas]['$id'];
|
||||||
@ -251,6 +252,7 @@ export const schemas: UnleashSchemas = {
|
|||||||
applicationOverviewSchema,
|
applicationOverviewSchema,
|
||||||
applicationOverviewIssuesSchema,
|
applicationOverviewIssuesSchema,
|
||||||
applicationOverviewEnvironmentSchema,
|
applicationOverviewEnvironmentSchema,
|
||||||
|
applicationEnvironmentInstancesSchema,
|
||||||
applicationUsageSchema,
|
applicationUsageSchema,
|
||||||
applicationsSchema,
|
applicationsSchema,
|
||||||
batchFeaturesSchema,
|
batchFeaturesSchema,
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const applicationEnvironmentInstancesSchema = {
|
||||||
|
$id: '#/components/schemas/applicationEnvironmentInstanceSchema',
|
||||||
|
type: 'object',
|
||||||
|
description:
|
||||||
|
'Data about an application environment instances that are connected to Unleash via an SDK.',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['instances'],
|
||||||
|
properties: {
|
||||||
|
instances: {
|
||||||
|
type: 'array',
|
||||||
|
description: 'A list of instances',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['instanceId'],
|
||||||
|
additionalProperties: false,
|
||||||
|
properties: {
|
||||||
|
instanceId: {
|
||||||
|
description:
|
||||||
|
'A unique identifier identifying the instance of the application running the SDK. Often changes based on execution environment. For instance: two pods in Kubernetes will have two different instanceIds',
|
||||||
|
type: 'string',
|
||||||
|
example: 'b77f3d13-5f48-4a7b-a3f4-a449b97ce43a',
|
||||||
|
},
|
||||||
|
sdkVersion: {
|
||||||
|
type: 'string',
|
||||||
|
description:
|
||||||
|
'An SDK version identifier. Usually formatted as "unleash-client-<language>:<version>"',
|
||||||
|
example: 'unleash-client-java:7.0.0',
|
||||||
|
},
|
||||||
|
clientIp: {
|
||||||
|
type: 'string',
|
||||||
|
description:
|
||||||
|
'An IP address identifying the instance of the application running the SDK',
|
||||||
|
example: '192.168.0.1',
|
||||||
|
},
|
||||||
|
lastSeen: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'date-time',
|
||||||
|
example: '2023-04-19T08:15:14.000Z',
|
||||||
|
description:
|
||||||
|
'The last time the application environment instance was seen',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type ApplicationEnvironmentInstancesSchema = FromSchema<
|
||||||
|
typeof applicationEnvironmentInstancesSchema
|
||||||
|
>;
|
@ -178,3 +178,4 @@ export * from './inactive-users-schema';
|
|||||||
export * from './record-ui-error-schema';
|
export * from './record-ui-error-schema';
|
||||||
export * from './project-application-schema';
|
export * from './project-application-schema';
|
||||||
export * from './project-applications-schema';
|
export * from './project-applications-schema';
|
||||||
|
export * from './application-environment-instances-schema';
|
||||||
|
@ -25,6 +25,10 @@ import {
|
|||||||
import { OpenApiService } from '../../services';
|
import { OpenApiService } from '../../services';
|
||||||
import { applicationsQueryParameters } from '../../openapi/spec/applications-query-parameters';
|
import { applicationsQueryParameters } from '../../openapi/spec/applications-query-parameters';
|
||||||
import { normalizeQueryParams } from '../../features/feature-search/search-utils';
|
import { normalizeQueryParams } from '../../features/feature-search/search-utils';
|
||||||
|
import {
|
||||||
|
applicationEnvironmentInstancesSchema,
|
||||||
|
ApplicationEnvironmentInstancesSchema,
|
||||||
|
} from '../../openapi/spec/application-environment-instances-schema';
|
||||||
|
|
||||||
class MetricsController extends Controller {
|
class MetricsController extends Controller {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
@ -152,6 +156,27 @@ class MetricsController extends Controller {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
this.route({
|
||||||
|
method: 'get',
|
||||||
|
path: '/instances/:appName/:environment',
|
||||||
|
handler: this.getApplicationEnvironmentInstances,
|
||||||
|
permission: NONE,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['Unstable'],
|
||||||
|
operationId: 'getApplicationEnvironmentInstances',
|
||||||
|
summary: 'Get application environment instances',
|
||||||
|
description:
|
||||||
|
'Returns an overview of the instances for the given `appName` and `environment` that receive traffic.',
|
||||||
|
responses: {
|
||||||
|
200: createResponseSchema(
|
||||||
|
'applicationEnvironmentInstancesSchema',
|
||||||
|
),
|
||||||
|
...getStandardResponses(404),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deprecated(req: Request, res: Response): Promise<void> {
|
async deprecated(req: Request, res: Response): Promise<void> {
|
||||||
@ -223,7 +248,7 @@ class MetricsController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getApplication(
|
async getApplication(
|
||||||
req: Request,
|
req: Request<{ appName: string }>,
|
||||||
res: Response<ApplicationSchema>,
|
res: Response<ApplicationSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { appName } = req.params;
|
const { appName } = req.params;
|
||||||
@ -234,7 +259,7 @@ class MetricsController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getApplicationOverview(
|
async getApplicationOverview(
|
||||||
req: Request,
|
req: Request<{ appName: string }>,
|
||||||
res: Response<ApplicationOverviewSchema>,
|
res: Response<ApplicationOverviewSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!this.flagResolver.isEnabled('sdkReporting')) {
|
if (!this.flagResolver.isEnabled('sdkReporting')) {
|
||||||
@ -251,6 +276,28 @@ class MetricsController extends Controller {
|
|||||||
serializeDates(overview),
|
serializeDates(overview),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getApplicationEnvironmentInstances(
|
||||||
|
req: Request<{ appName: string; environment: string }>,
|
||||||
|
res: Response<ApplicationEnvironmentInstancesSchema>,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.flagResolver.isEnabled('sdkReporting')) {
|
||||||
|
throw new NotFoundError();
|
||||||
|
}
|
||||||
|
const { appName, environment } = req.params;
|
||||||
|
const instances =
|
||||||
|
await this.clientInstanceService.getApplicationEnvironmentInstances(
|
||||||
|
appName,
|
||||||
|
environment,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.openApiService.respondWithValidation(
|
||||||
|
200,
|
||||||
|
res,
|
||||||
|
applicationEnvironmentInstancesSchema.$id,
|
||||||
|
serializeDates({ instances }),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MetricsController;
|
export default MetricsController;
|
||||||
|
@ -21,6 +21,10 @@ export interface IClientInstanceStore
|
|||||||
setLastSeen(INewClientInstance): Promise<void>;
|
setLastSeen(INewClientInstance): Promise<void>;
|
||||||
insert(details: INewClientInstance): Promise<void>;
|
insert(details: INewClientInstance): Promise<void>;
|
||||||
getByAppName(appName: string): Promise<IClientInstance[]>;
|
getByAppName(appName: string): Promise<IClientInstance[]>;
|
||||||
|
getByAppNameAndEnvironment(
|
||||||
|
appName: string,
|
||||||
|
environment: string,
|
||||||
|
): Promise<IClientInstance[]>;
|
||||||
getBySdkName(sdkName: string): Promise<IClientInstance[]>;
|
getBySdkName(sdkName: string): Promise<IClientInstance[]>;
|
||||||
getDistinctApplications(): Promise<string[]>;
|
getDistinctApplications(): Promise<string[]>;
|
||||||
getDistinctApplicationsCount(daysBefore?: number): Promise<number>;
|
getDistinctApplicationsCount(daysBefore?: number): Promise<number>;
|
||||||
|
@ -134,6 +134,20 @@ test('should show correct number of total', async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
expect(body).toMatchObject(expected);
|
expect(body).toMatchObject(expected);
|
||||||
|
|
||||||
|
const { body: instancesBody } = await app.request
|
||||||
|
.get(`/api/admin/metrics/instances/${metrics.appName}/default`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(instancesBody).toMatchObject({
|
||||||
|
instances: [
|
||||||
|
{ instanceId: 'instanceId', sdkVersion: 'unleash-client-test' },
|
||||||
|
{
|
||||||
|
instanceId: 'another-instance',
|
||||||
|
sdkVersion: 'unleash-client-test2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should show missing features and strategies', async () => {
|
test('should show missing features and strategies', async () => {
|
||||||
|
@ -77,6 +77,15 @@ export default class FakeClientInstanceStore implements IClientInstanceStore {
|
|||||||
return this.instances.filter((i) => i.appName === appName);
|
return this.instances.filter((i) => i.appName === appName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getByAppNameAndEnvironment(
|
||||||
|
appName: string,
|
||||||
|
environment: string,
|
||||||
|
): Promise<IClientInstance[]> {
|
||||||
|
return this.instances
|
||||||
|
.filter((i) => i.appName === appName)
|
||||||
|
.filter((i) => i.environment === environment);
|
||||||
|
}
|
||||||
|
|
||||||
async getDistinctApplications(): Promise<string[]> {
|
async getDistinctApplications(): Promise<string[]> {
|
||||||
const apps = new Set<string>();
|
const apps = new Set<string>();
|
||||||
this.instances.forEach((i) => apps.add(i.appName));
|
this.instances.forEach((i) => apps.add(i.appName));
|
||||||
|
Loading…
Reference in New Issue
Block a user