mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01: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