mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-27 01:19:00 +02:00
feat: bulk apps should respect multi projects and multi envs (#9879)
This commit is contained in:
parent
b0223e38ef
commit
1b9c0e5000
@ -91,7 +91,7 @@ const reduceRows = (rows: any[]): IClientApplication[] => {
|
||||
return Object.values(appsObj);
|
||||
};
|
||||
|
||||
const remapRow = (input) => {
|
||||
const remapRow = (input: Partial<IClientApplication>) => {
|
||||
const temp = {
|
||||
app_name: input.appName,
|
||||
updated_at: input.updatedAt || new Date(),
|
||||
@ -152,10 +152,28 @@ export default class ClientApplicationsStore
|
||||
|
||||
async bulkUpsert(apps: Partial<IClientApplication>[]): Promise<void> {
|
||||
const rows = apps.map(remapRow);
|
||||
const uniqueRows = Object.values(
|
||||
rows.reduce((acc, row) => {
|
||||
if (row.app_name) {
|
||||
acc[row.app_name] = row;
|
||||
}
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
const usageRows = apps.flatMap(this.remapUsageRow);
|
||||
await this.db(TABLE).insert(rows).onConflict('app_name').merge();
|
||||
const uniqueUsageRows = Object.values(
|
||||
usageRows.reduce((acc, row) => {
|
||||
if (row.app_name) {
|
||||
acc[`${row.app_name} ${row.project} ${row.environment}`] =
|
||||
row;
|
||||
}
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
|
||||
await this.db(TABLE).insert(uniqueRows).onConflict('app_name').merge();
|
||||
await this.db(TABLE_USAGE)
|
||||
.insert(usageRows)
|
||||
.insert(uniqueUsageRows)
|
||||
.onConflict(['app_name', 'project', 'environment'])
|
||||
.merge();
|
||||
}
|
||||
@ -444,7 +462,7 @@ export default class ClientApplicationsStore
|
||||
};
|
||||
}
|
||||
|
||||
private remapUsageRow = (input) => {
|
||||
private remapUsageRow = (input: Partial<IClientApplication>) => {
|
||||
if (!input.projects || input.projects.length === 0) {
|
||||
return [
|
||||
{
|
||||
|
@ -173,8 +173,19 @@ export default class ClientInstanceService {
|
||||
const uniqueRegistrations = Object.values(this.seenClients);
|
||||
const uniqueApps: Partial<IClientApplication>[] = Object.values(
|
||||
uniqueRegistrations.reduce((soFar, reg) => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
soFar[reg.appName] = reg;
|
||||
let existingProjects = [];
|
||||
if (soFar[`${reg.appName} ${reg.environment}`]) {
|
||||
existingProjects =
|
||||
soFar[`${reg.appName} ${reg.environment}`]
|
||||
.projects || [];
|
||||
}
|
||||
soFar[`${reg.appName} ${reg.environment}`] = {
|
||||
...reg,
|
||||
projects: [
|
||||
...existingProjects,
|
||||
...(reg.projects || []),
|
||||
],
|
||||
};
|
||||
return soFar;
|
||||
}, {}),
|
||||
);
|
||||
|
@ -61,6 +61,7 @@ afterAll(async () => {
|
||||
|
||||
afterEach(async () => {
|
||||
await stores.featureToggleStore.deleteAll();
|
||||
await stores.clientApplicationsStore.deleteAll();
|
||||
});
|
||||
|
||||
test('should validate client metrics', () => {
|
||||
@ -125,7 +126,7 @@ test('should accept client metrics with yes/no with metricsV2', async () => {
|
||||
})
|
||||
.expect(202);
|
||||
|
||||
testRunner.destroy();
|
||||
await testRunner.destroy();
|
||||
});
|
||||
|
||||
test('should accept client metrics with variants', () => {
|
||||
@ -343,6 +344,99 @@ describe('bulk metrics', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should respect project from token', async () => {
|
||||
const frontendApp: BulkRegistrationSchema = {
|
||||
appName: 'application-name-token',
|
||||
instanceId: 'browser',
|
||||
environment: 'production',
|
||||
sdkVersion: 'unleash-client-js:1.0.0',
|
||||
sdkType: 'frontend',
|
||||
projects: ['project-a', 'project-b'],
|
||||
};
|
||||
const backendApp: BulkRegistrationSchema = {
|
||||
appName: 'application-name-token',
|
||||
instanceId: 'instance1234',
|
||||
environment: 'development',
|
||||
sdkVersion: 'unleash-client-node',
|
||||
sdkType: 'backend',
|
||||
started: '1952-03-11T12:00:00.000Z',
|
||||
interval: 15000,
|
||||
projects: ['project-b', 'project-c'],
|
||||
};
|
||||
const defaultApp: BulkRegistrationSchema = {
|
||||
appName: 'application-name-token',
|
||||
instanceId: 'instance5678',
|
||||
environment: 'development',
|
||||
sdkVersion: 'unleash-client-java',
|
||||
sdkType: null,
|
||||
started: '1952-03-11T12:00:00.000Z',
|
||||
interval: 15000,
|
||||
projects: ['project-c', 'project-d'],
|
||||
};
|
||||
await request
|
||||
.post('/api/client/metrics/bulk')
|
||||
.send({
|
||||
applications: [frontendApp, backendApp, defaultApp],
|
||||
metrics: [],
|
||||
})
|
||||
.expect(202);
|
||||
|
||||
await services.clientInstanceService.bulkAdd();
|
||||
const app = await services.clientInstanceService.getApplication(
|
||||
'application-name-token',
|
||||
);
|
||||
|
||||
expect(app).toMatchObject({
|
||||
appName: 'application-name-token',
|
||||
instances: [
|
||||
{
|
||||
instanceId: 'instance1234',
|
||||
sdkVersion: 'unleash-client-node',
|
||||
environment: 'development',
|
||||
},
|
||||
{
|
||||
instanceId: 'instance5678',
|
||||
sdkVersion: 'unleash-client-java',
|
||||
environment: 'development',
|
||||
},
|
||||
{
|
||||
instanceId: 'browser',
|
||||
sdkVersion: 'unleash-client-js:1.0.0',
|
||||
environment: 'production',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const applications =
|
||||
await stores.clientApplicationsStore.getApplications({
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
sortBy: 'name',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
expect(applications).toMatchObject({
|
||||
applications: [
|
||||
{
|
||||
usage: [
|
||||
{
|
||||
project: 'project-a',
|
||||
environments: ['production'],
|
||||
},
|
||||
{
|
||||
project: 'project-b',
|
||||
environments: ['production', 'development'],
|
||||
},
|
||||
{
|
||||
project: 'project-c',
|
||||
environments: ['development'],
|
||||
},
|
||||
{ project: 'project-d', environments: ['development'] },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('filters out metrics for environments we do not have access for. No auth setup so we can only access default env', async () => {
|
||||
const now = new Date();
|
||||
|
||||
|
@ -158,6 +158,7 @@ export default class ClientMetricsController extends Controller {
|
||||
environment: app.environment,
|
||||
sdkType: app.sdkType,
|
||||
sdkVersion: app.sdkVersion,
|
||||
projects: app.projects,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -64,6 +64,14 @@ export const bulkRegistrationSchema = {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
projects: {
|
||||
description: 'The list of projects used in the application',
|
||||
type: 'array',
|
||||
example: ['projectA', 'projectB'],
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
sdkVersion: {
|
||||
description:
|
||||
'The version the sdk is running. Typically <client>:<version>',
|
||||
|
@ -478,6 +478,7 @@ export interface IClientApp {
|
||||
seenToggles?: string[];
|
||||
metricsCount?: number;
|
||||
strategies?: string[] | Record<string, string>[];
|
||||
projects?: string[];
|
||||
count?: number;
|
||||
started?: string | number | Date;
|
||||
interval?: number;
|
||||
|
@ -19,6 +19,8 @@ export interface IClientApplication {
|
||||
icon: string;
|
||||
strategies: string[];
|
||||
usage?: IClientApplicationUsage[];
|
||||
projects?: string[];
|
||||
environment?: string;
|
||||
}
|
||||
|
||||
export interface IClientApplications {
|
||||
|
Loading…
Reference in New Issue
Block a user