From d19d97cf184ec87df29857c90b69524872be19c8 Mon Sep 17 00:00:00 2001 From: andreas-unleash Date: Mon, 21 Aug 2023 14:36:31 +0300 Subject: [PATCH] feat: persist client application usage (#4534) Closes # [1-1256](https://linear.app/unleash/issue/1-1256/backend-to-save-application-usage) Adds client application usage persisting on upsert and bulkUpsert functions --------- Signed-off-by: andreas-unleash --- src/lib/db/client-applications-store.ts | 20 ++++++++++++ src/lib/routes/client-api/register.ts | 11 +++++++ src/lib/services/client-metrics/schema.ts | 1 + ...-update-client-applications-usage-table.js | 32 +++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 src/migrations/20230818124614-update-client-applications-usage-table.js diff --git a/src/lib/db/client-applications-store.ts b/src/lib/db/client-applications-store.ts index f2a11f11ac..917d62b8f1 100644 --- a/src/lib/db/client-applications-store.ts +++ b/src/lib/db/client-applications-store.ts @@ -21,6 +21,8 @@ const COLUMNS = [ ]; const TABLE = 'client_applications'; +const TABLE_USAGE = 'client_applications_usage'; + const mapRow: (any) => IClientApplication = (row) => ({ appName: row.app_name, createdAt: row.created_at, @@ -57,6 +59,14 @@ 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 { @@ -72,11 +82,21 @@ export default class ClientApplicationsStore async upsert(details: Partial): Promise { const row = remapRow(details); await this.db(TABLE).insert(row).onConflict('app_name').merge(); + const usageRow = remapUsageRow(details); + await this.db(TABLE_USAGE) + .insert(usageRow) + .onConflict(['app_name', 'project', 'environment']) + .merge(); } async bulkUpsert(apps: Partial[]): Promise { const rows = apps.map(remapRow); + const usageRows = apps.map(remapUsageRow); await this.db(TABLE).insert(rows).onConflict('app_name').merge(); + await this.db(TABLE_USAGE) + .insert(usageRows) + .onConflict(['app_name', 'project', 'environment']) + .merge(); } async exists(appName: string): Promise { diff --git a/src/lib/routes/client-api/register.ts b/src/lib/routes/client-api/register.ts index 17201f3b5a..7cf1cda1a4 100644 --- a/src/lib/routes/client-api/register.ts +++ b/src/lib/routes/client-api/register.ts @@ -63,12 +63,23 @@ export default class RegisterController extends Controller { return 'default'; } + private static extractProjectFromRequest( + req: IAuthRequest, + ) { + const token = req.get('Authorisation'); + if (token) { + return token.split(':')[0]; + } + return 'default'; + } + async registerClientApplication( req: IAuthRequest, res: Response, ): Promise { const { body: data, ip: clientIp, user } = req; data.environment = RegisterController.resolveEnvironment(user, data); + data.project = RegisterController.extractProjectFromRequest(req); await this.clientInstanceService.registerClient(data, clientIp); res.status(202).end(); } diff --git a/src/lib/services/client-metrics/schema.ts b/src/lib/services/client-metrics/schema.ts index 04a68b57e2..fd83204f27 100644 --- a/src/lib/services/client-metrics/schema.ts +++ b/src/lib/services/client-metrics/schema.ts @@ -91,4 +91,5 @@ export const clientRegisterSchema = joi started: joi.date().required(), interval: joi.number().required(), environment: joi.string().optional(), + project: joi.string().optional(), }); diff --git a/src/migrations/20230818124614-update-client-applications-usage-table.js b/src/migrations/20230818124614-update-client-applications-usage-table.js new file mode 100644 index 0000000000..267435505f --- /dev/null +++ b/src/migrations/20230818124614-update-client-applications-usage-table.js @@ -0,0 +1,32 @@ +'use strict'; + +exports.up = function (db, callback) { + db.runSql( + ` + DROP TABLE IF EXISTS client_applications_usage; + + CREATE TABLE IF NOT EXISTS client_applications_usage ( + app_name VARCHAR(255) REFERENCES client_applications(app_name) ON DELETE CASCADE, + project VARCHAR(255) NOT NULL , + environment VARCHAR(100) NOT NULL , + PRIMARY KEY(app_name, project, environment) + ) ; + `, + callback, + ); +}; + +exports.down = function (db, callback) { + db.runSql( + ` + DROP TABLE IF EXISTS client_applications_usage; + CREATE TABLE IF NOT EXISTS client_applications_usage ( + app_name VARCHAR(255) REFERENCES client_applications(app_name) ON DELETE CASCADE, + project VARCHAR(255) REFERENCES projects(id) ON DELETE CASCADE, + environment VARCHAR(100) REFERENCES environments(name) ON DELETE CASCADE, + PRIMARY KEY(app_name, project, environment) + ) ; + `, + callback, + ); +};