mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: start extracting project from session object (#6856)
Previously, we were extracting the project from the token, but now we will retrieve it from the session, which contains the full list of projects. This change also resolves an issue we encountered when the token was a multi-project token, formatted as []:dev:token. Previously, it was unable to display the exact list of projects. Now, it will show the exact project names.
This commit is contained in:
parent
8dbd680326
commit
f45593176c
@ -132,6 +132,7 @@ exports[`should create default config 1`] = `
|
||||
},
|
||||
"migrationLock": true,
|
||||
"outdatedSdksBanner": false,
|
||||
"parseProjectFromSession": false,
|
||||
"personalAccessTokensKillSwitch": false,
|
||||
"projectListFilterMyProjects": false,
|
||||
"projectOverviewRefactor": false,
|
||||
|
@ -10,6 +10,7 @@ import type { Logger, LogProvider } from '../logger';
|
||||
import type { Db } from './db';
|
||||
import type { IApplicationOverview } from '../features/metrics/instance/models';
|
||||
import { applySearchFilters } from '../features/feature-search/search-utils';
|
||||
import type { IFlagResolver } from '../types';
|
||||
|
||||
const COLUMNS = [
|
||||
'app_name',
|
||||
@ -110,14 +111,6 @@ const remapRow = (input) => {
|
||||
return temp;
|
||||
};
|
||||
|
||||
const remapUsageRow = (input) => {
|
||||
return {
|
||||
app_name: input.appName,
|
||||
project: input.project || '*',
|
||||
environment: input.environment || '*',
|
||||
};
|
||||
};
|
||||
|
||||
export default class ClientApplicationsStore
|
||||
implements IClientApplicationsStore
|
||||
{
|
||||
@ -125,24 +118,32 @@ export default class ClientApplicationsStore
|
||||
|
||||
private logger: Logger;
|
||||
|
||||
constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) {
|
||||
private flagResolver: IFlagResolver;
|
||||
|
||||
constructor(
|
||||
db: Db,
|
||||
eventBus: EventEmitter,
|
||||
getLogger: LogProvider,
|
||||
flagResolver: IFlagResolver,
|
||||
) {
|
||||
this.db = db;
|
||||
this.flagResolver = flagResolver;
|
||||
this.logger = getLogger('client-applications-store.ts');
|
||||
}
|
||||
|
||||
async upsert(details: Partial<IClientApplication>): Promise<void> {
|
||||
const row = remapRow(details);
|
||||
await this.db(TABLE).insert(row).onConflict('app_name').merge();
|
||||
const usageRow = remapUsageRow(details);
|
||||
const usageRows = this.remapUsageRow(details);
|
||||
await this.db(TABLE_USAGE)
|
||||
.insert(usageRow)
|
||||
.insert(usageRows)
|
||||
.onConflict(['app_name', 'project', 'environment'])
|
||||
.merge();
|
||||
}
|
||||
|
||||
async bulkUpsert(apps: Partial<IClientApplication>[]): Promise<void> {
|
||||
const rows = apps.map(remapRow);
|
||||
const usageRows = apps.map(remapUsageRow);
|
||||
const usageRows = apps.flatMap(this.remapUsageRow);
|
||||
await this.db(TABLE).insert(rows).onConflict('app_name').merge();
|
||||
await this.db(TABLE_USAGE)
|
||||
.insert(usageRows)
|
||||
@ -420,4 +421,30 @@ export default class ClientApplicationsStore
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private remapUsageRow = (input) => {
|
||||
if (this.flagResolver.isEnabled('parseProjectFromSession')) {
|
||||
if (!input.projects || input.projects.length === 0) {
|
||||
return [
|
||||
{
|
||||
app_name: input.appName,
|
||||
project: '*',
|
||||
environment: input.environment || '*',
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return input.projects.map((project) => ({
|
||||
app_name: input.appName,
|
||||
project: project,
|
||||
environment: input.environment || '*',
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
app_name: input.appName,
|
||||
project: input.project || '*',
|
||||
environment: input.environment || '*',
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ export const createStores = (
|
||||
db,
|
||||
eventBus,
|
||||
getLogger,
|
||||
config.flagResolver,
|
||||
),
|
||||
clientInstanceStore: new ClientInstanceStore(db, eventBus, getLogger),
|
||||
clientMetricsStoreV2: new ClientMetricsStoreV2(
|
||||
|
@ -28,6 +28,7 @@ export interface IApplication {
|
||||
instances?: IClientInstance[];
|
||||
seenToggles?: Record<string, any>;
|
||||
project?: string;
|
||||
projects?: string[];
|
||||
environment?: string;
|
||||
links?: Record<string, string>;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { Response } from 'express';
|
||||
import Controller from '../../../routes/controller';
|
||||
import type { IUnleashServices } from '../../../types';
|
||||
import type { IFlagResolver, IUnleashServices } from '../../../types';
|
||||
import type { IUnleashConfig } from '../../../types/option';
|
||||
import type { Logger } from '../../../logger';
|
||||
import type ClientInstanceService from './instance-service';
|
||||
@ -24,6 +24,8 @@ export default class RegisterController extends Controller {
|
||||
|
||||
openApiService: OpenApiService;
|
||||
|
||||
flagResolver: IFlagResolver;
|
||||
|
||||
constructor(
|
||||
{
|
||||
clientInstanceService,
|
||||
@ -35,6 +37,7 @@ export default class RegisterController extends Controller {
|
||||
this.logger = config.getLogger('/api/client/register');
|
||||
this.clientInstanceService = clientInstanceService;
|
||||
this.openApiService = openApiService;
|
||||
this.flagResolver = config.flagResolver;
|
||||
|
||||
this.route({
|
||||
method: 'post',
|
||||
@ -62,7 +65,7 @@ export default class RegisterController extends Controller {
|
||||
});
|
||||
}
|
||||
|
||||
private static resolveEnvironment(
|
||||
private resolveEnvironment(
|
||||
user: IUser | IApiUser,
|
||||
data: Partial<IClientApp>,
|
||||
) {
|
||||
@ -76,7 +79,14 @@ export default class RegisterController extends Controller {
|
||||
return 'default';
|
||||
}
|
||||
|
||||
private static extractProjectFromRequest(
|
||||
private resolveProject(user: IUser | IApiUser) {
|
||||
if (user instanceof ApiUser) {
|
||||
return user.projects;
|
||||
}
|
||||
return ['default'];
|
||||
}
|
||||
|
||||
private extractProjectFromRequest(
|
||||
req: IAuthRequest<unknown, void, ClientApplicationSchema>,
|
||||
) {
|
||||
const token = req.get('Authorisation') || req.headers.authorization;
|
||||
@ -91,8 +101,13 @@ export default class RegisterController extends Controller {
|
||||
res: Response<void>,
|
||||
): Promise<void> {
|
||||
const { body: data, ip: clientIp, user } = req;
|
||||
data.environment = RegisterController.resolveEnvironment(user, data);
|
||||
data.project = RegisterController.extractProjectFromRequest(req);
|
||||
data.environment = this.resolveEnvironment(user, data);
|
||||
if (this.flagResolver.isEnabled('parseProjectFromSession')) {
|
||||
data.projects = this.resolveProject(user);
|
||||
} else {
|
||||
data.project = this.extractProjectFromRequest(req);
|
||||
}
|
||||
|
||||
await this.clientInstanceService.registerClient(data, clientIp);
|
||||
res.header('X-Unleash-Version', version).status(202).end();
|
||||
}
|
||||
|
@ -92,4 +92,5 @@ export const clientRegisterSchema = joi
|
||||
interval: joi.number().required(),
|
||||
environment: joi.string().optional(),
|
||||
project: joi.string().optional(),
|
||||
projects: joi.array().optional().items(joi.string()),
|
||||
});
|
||||
|
@ -58,7 +58,8 @@ export type IFlagKey =
|
||||
| 'bearerTokenMiddleware'
|
||||
| 'projectOverviewRefactorFeedback'
|
||||
| 'featureLifecycle'
|
||||
| 'projectListFilterMyProjects';
|
||||
| 'projectListFilterMyProjects'
|
||||
| 'parseProjectFromSession';
|
||||
|
||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||
|
||||
@ -287,6 +288,10 @@ const flags: IFlags = {
|
||||
process.env.UNLEASH_EXPERIMENTAL_PROJECTS_LIST_MY_PROJECTS,
|
||||
false,
|
||||
),
|
||||
parseProjectFromSession: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_PARSE_PROJECT_FROM_SESSION,
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||
|
@ -55,6 +55,7 @@ process.nextTick(async () => {
|
||||
projectOverviewRefactorFeedback: true,
|
||||
featureLifecycle: true,
|
||||
projectListFilterMyProjects: true,
|
||||
parseProjectFromSession: true,
|
||||
},
|
||||
},
|
||||
authentication: {
|
||||
|
@ -4,13 +4,31 @@ import {
|
||||
setupAppWithCustomConfig,
|
||||
} from '../../helpers/test-helper';
|
||||
import getLogger from '../../../fixtures/no-logger';
|
||||
import { ApiTokenType } from '../../../../lib/types/models/api-token';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('metrics_serial', getLogger, {});
|
||||
app = await setupAppWithCustomConfig(db.stores, {}, db.rawDatabase);
|
||||
db = await dbInit('metrics_serial', getLogger, {
|
||||
experimental: {
|
||||
flags: {
|
||||
parseProjectFromSession: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
app = await setupAppWithCustomConfig(
|
||||
db.stores,
|
||||
{
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
parseProjectFromSession: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
db.rawDatabase,
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
@ -52,7 +70,7 @@ beforeEach(async () => {
|
||||
appName: 'usage-app',
|
||||
strategies: ['default'],
|
||||
description: 'Some desc',
|
||||
project: 'default',
|
||||
projects: ['default'],
|
||||
environment: 'dev',
|
||||
});
|
||||
});
|
||||
@ -123,6 +141,64 @@ test('should get list of application usage', async () => {
|
||||
);
|
||||
expect(application).toMatchObject({
|
||||
appName: 'usage-app',
|
||||
usage: [{ project: 'default', environments: ['dev'] }],
|
||||
usage: [
|
||||
{
|
||||
project: 'default',
|
||||
environments: ['dev'],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('should save multiple projects from token', async () => {
|
||||
await db.reset();
|
||||
await db.stores.projectStore.create({
|
||||
id: 'mainProject',
|
||||
name: 'mainProject',
|
||||
});
|
||||
|
||||
const multiProjectToken =
|
||||
await app.services.apiTokenService.createApiTokenWithProjects({
|
||||
type: ApiTokenType.CLIENT,
|
||||
projects: ['default', 'mainProject'],
|
||||
environment: 'default',
|
||||
tokenName: 'tester',
|
||||
});
|
||||
|
||||
await app.request
|
||||
.post('/api/client/register')
|
||||
.set('Authorization', multiProjectToken.secret)
|
||||
.send({
|
||||
appName: 'multi-project-app',
|
||||
instanceId: 'instance-1',
|
||||
strategies: ['default'],
|
||||
started: Date.now(),
|
||||
interval: 10,
|
||||
});
|
||||
|
||||
await app.services.clientInstanceService.bulkAdd();
|
||||
|
||||
const { body } = await app.request
|
||||
.get('/api/admin/metrics/applications')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
|
||||
expect(body).toMatchObject({
|
||||
applications: [
|
||||
{
|
||||
appName: 'multi-project-app',
|
||||
usage: [
|
||||
{
|
||||
environments: ['default'],
|
||||
project: 'default',
|
||||
},
|
||||
{
|
||||
environments: ['default'],
|
||||
project: 'mainProject',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user