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);
|
return Object.values(appsObj);
|
||||||
};
|
};
|
||||||
|
|
||||||
const remapRow = (input) => {
|
const remapRow = (input: Partial<IClientApplication>) => {
|
||||||
const temp = {
|
const temp = {
|
||||||
app_name: input.appName,
|
app_name: input.appName,
|
||||||
updated_at: input.updatedAt || new Date(),
|
updated_at: input.updatedAt || new Date(),
|
||||||
@ -152,10 +152,28 @@ export default class ClientApplicationsStore
|
|||||||
|
|
||||||
async bulkUpsert(apps: Partial<IClientApplication>[]): Promise<void> {
|
async bulkUpsert(apps: Partial<IClientApplication>[]): Promise<void> {
|
||||||
const rows = apps.map(remapRow);
|
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);
|
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)
|
await this.db(TABLE_USAGE)
|
||||||
.insert(usageRows)
|
.insert(uniqueUsageRows)
|
||||||
.onConflict(['app_name', 'project', 'environment'])
|
.onConflict(['app_name', 'project', 'environment'])
|
||||||
.merge();
|
.merge();
|
||||||
}
|
}
|
||||||
@ -444,7 +462,7 @@ export default class ClientApplicationsStore
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private remapUsageRow = (input) => {
|
private remapUsageRow = (input: Partial<IClientApplication>) => {
|
||||||
if (!input.projects || input.projects.length === 0) {
|
if (!input.projects || input.projects.length === 0) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -173,8 +173,19 @@ export default class ClientInstanceService {
|
|||||||
const uniqueRegistrations = Object.values(this.seenClients);
|
const uniqueRegistrations = Object.values(this.seenClients);
|
||||||
const uniqueApps: Partial<IClientApplication>[] = Object.values(
|
const uniqueApps: Partial<IClientApplication>[] = Object.values(
|
||||||
uniqueRegistrations.reduce((soFar, reg) => {
|
uniqueRegistrations.reduce((soFar, reg) => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
let existingProjects = [];
|
||||||
soFar[reg.appName] = reg;
|
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;
|
return soFar;
|
||||||
}, {}),
|
}, {}),
|
||||||
);
|
);
|
||||||
|
@ -61,6 +61,7 @@ afterAll(async () => {
|
|||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await stores.featureToggleStore.deleteAll();
|
await stores.featureToggleStore.deleteAll();
|
||||||
|
await stores.clientApplicationsStore.deleteAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should validate client metrics', () => {
|
test('should validate client metrics', () => {
|
||||||
@ -125,7 +126,7 @@ test('should accept client metrics with yes/no with metricsV2', async () => {
|
|||||||
})
|
})
|
||||||
.expect(202);
|
.expect(202);
|
||||||
|
|
||||||
testRunner.destroy();
|
await testRunner.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should accept client metrics with variants', () => {
|
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 () => {
|
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();
|
const now = new Date();
|
||||||
|
|
||||||
|
@ -158,6 +158,7 @@ export default class ClientMetricsController extends Controller {
|
|||||||
environment: app.environment,
|
environment: app.environment,
|
||||||
sdkType: app.sdkType,
|
sdkType: app.sdkType,
|
||||||
sdkVersion: app.sdkVersion,
|
sdkVersion: app.sdkVersion,
|
||||||
|
projects: app.projects,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -64,6 +64,14 @@ export const bulkRegistrationSchema = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
projects: {
|
||||||
|
description: 'The list of projects used in the application',
|
||||||
|
type: 'array',
|
||||||
|
example: ['projectA', 'projectB'],
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
sdkVersion: {
|
sdkVersion: {
|
||||||
description:
|
description:
|
||||||
'The version the sdk is running. Typically <client>:<version>',
|
'The version the sdk is running. Typically <client>:<version>',
|
||||||
|
@ -478,6 +478,7 @@ export interface IClientApp {
|
|||||||
seenToggles?: string[];
|
seenToggles?: string[];
|
||||||
metricsCount?: number;
|
metricsCount?: number;
|
||||||
strategies?: string[] | Record<string, string>[];
|
strategies?: string[] | Record<string, string>[];
|
||||||
|
projects?: string[];
|
||||||
count?: number;
|
count?: number;
|
||||||
started?: string | number | Date;
|
started?: string | number | Date;
|
||||||
interval?: number;
|
interval?: number;
|
||||||
|
@ -19,6 +19,8 @@ export interface IClientApplication {
|
|||||||
icon: string;
|
icon: string;
|
||||||
strategies: string[];
|
strategies: string[];
|
||||||
usage?: IClientApplicationUsage[];
|
usage?: IClientApplicationUsage[];
|
||||||
|
projects?: string[];
|
||||||
|
environment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IClientApplications {
|
export interface IClientApplications {
|
||||||
|
Loading…
Reference in New Issue
Block a user