From b919c445b4e5826b46e88591f13aea1ab2507a66 Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Tue, 26 Sep 2023 09:37:42 +0200 Subject: [PATCH] feat: add kill switch for client metrics (#4829) This PR adds a killswitch for client metrics that we can use to disable metrics in our cloud offering. --- .../__snapshots__/create-config.test.ts.snap | 2 + src/lib/routes/client-api/metrics.test.ts | 29 ++++++++++++++ src/lib/routes/client-api/metrics.ts | 30 ++++++++------ src/lib/routes/proxy-api/index.ts | 7 ++++ src/lib/types/experimental.ts | 7 +++- src/test/e2e/api/proxy/proxy.e2e.test.ts | 39 +++++++++++++++++++ 6 files changed, 102 insertions(+), 12 deletions(-) diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 771f220e82..334a2e21ea 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -79,6 +79,7 @@ exports[`should create default config 1`] = ` "demo": false, "dependentFeatures": false, "disableBulkToggle": false, + "disableMetrics": false, "disableNotifications": false, "doraMetrics": false, "embedProxy": true, @@ -117,6 +118,7 @@ exports[`should create default config 1`] = ` "demo": false, "dependentFeatures": false, "disableBulkToggle": false, + "disableMetrics": false, "disableNotifications": false, "doraMetrics": false, "embedProxy": true, diff --git a/src/lib/routes/client-api/metrics.test.ts b/src/lib/routes/client-api/metrics.test.ts index 9093fb2015..355039ab4c 100644 --- a/src/lib/routes/client-api/metrics.test.ts +++ b/src/lib/routes/client-api/metrics.test.ts @@ -254,3 +254,32 @@ test('should return a 200 if required fields are there', async () => { }) .expect(202); }); + +test('should return 204 if metrics are disabled by feature flag', async () => { + const { request: localRequest } = await getSetup({ + experimental: { + flags: { + disableMetrics: true, + }, + }, + }); + + await localRequest + .post('/api/client/metrics') + .send({ + appName: 'demo', + someParam: 'some-value', + somOtherParam: 'some--other-value', + bucket: { + start: Date.now(), + stop: Date.now(), + toggles: { + toggleLastSeen: { + yes: 200, + no: 0, + }, + }, + }, + }) + .expect(204); +}); diff --git a/src/lib/routes/client-api/metrics.ts b/src/lib/routes/client-api/metrics.ts index 253d7f2408..a99d4efdea 100644 --- a/src/lib/routes/client-api/metrics.ts +++ b/src/lib/routes/client-api/metrics.ts @@ -58,6 +58,7 @@ export default class ClientMetricsController extends Controller { responses: { ...getStandardResponses(400), 202: emptyResponse, + 204: emptyResponse, }, }), ], @@ -65,18 +66,25 @@ export default class ClientMetricsController extends Controller { } async registerMetrics(req: IAuthRequest, res: Response): Promise { - try { - const { body: data, ip: clientIp, user } = req; - data.environment = this.metricsV2.resolveMetricsEnvironment( - user, - data, - ); - await this.clientInstanceService.registerInstance(data, clientIp); + if (this.config.flagResolver.isEnabled('disableMetrics')) { + res.status(204).end(); + } else { + try { + const { body: data, ip: clientIp, user } = req; + data.environment = this.metricsV2.resolveMetricsEnvironment( + user, + data, + ); + await this.clientInstanceService.registerInstance( + data, + clientIp, + ); - await this.metricsV2.registerClientMetrics(data, clientIp); - res.status(202).end(); - } catch (e) { - res.status(400).end(); + await this.metricsV2.registerClientMetrics(data, clientIp); + res.status(202).end(); + } catch (e) { + res.status(400).end(); + } } } } diff --git a/src/lib/routes/proxy-api/index.ts b/src/lib/routes/proxy-api/index.ts index 2a9f9886b7..30672b6973 100644 --- a/src/lib/routes/proxy-api/index.ts +++ b/src/lib/routes/proxy-api/index.ts @@ -108,6 +108,7 @@ export default class ProxyController extends Controller { requestBody: createRequestSchema('clientMetricsSchema'), responses: { 200: emptyResponse, + 204: emptyResponse, ...getStandardResponses(400, 401, 404), }, }), @@ -189,6 +190,12 @@ export default class ProxyController extends Controller { if (!this.config.flagResolver.isEnabled('embedProxy')) { throw new NotFoundError(); } + + if (this.config.flagResolver.isEnabled('disableMetrics')) { + res.sendStatus(204); + return; + } + await this.services.proxyService.registerProxyMetrics( req.user, req.body, diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index 907989dd49..c01111566c 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -30,7 +30,8 @@ export type IFlagKey = | 'accessOverview' | 'privateProjects' | 'dependentFeatures' - | 'datadogJsonTemplate'; + | 'datadogJsonTemplate' + | 'disableMetrics'; export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; @@ -142,6 +143,10 @@ const flags: IFlags = { process.env.UNLEASH_EXPERIMENTAL_DATADOG_JSON_TEMPLATE, false, ), + disableMetrics: parseEnvVarBoolean( + process.env.UNLEASH_EXPERIMENTAL_DISABLE_METRICS, + false, + ), }; export const defaultExperimentalOptions: IExperimentalOptions = { diff --git a/src/test/e2e/api/proxy/proxy.e2e.test.ts b/src/test/e2e/api/proxy/proxy.e2e.test.ts index d164e0224b..f18db74384 100644 --- a/src/test/e2e/api/proxy/proxy.e2e.test.ts +++ b/src/test/e2e/api/proxy/proxy.e2e.test.ts @@ -1193,3 +1193,42 @@ test('should NOT evaluate disabled strategies when returning toggles', async () }); }); }); + +test('should return 204 if metrics are disabled', async () => { + const localApp = await setupAppWithAuth(db.stores, { + frontendApiOrigins: ['https://example.com'], + experimental: { + flags: { + disableMetrics: true, + }, + }, + }); + + const frontendToken = + await localApp.services.apiTokenService.createApiTokenWithProjects({ + type: ApiTokenType.FRONTEND, + projects: ['*'], + environment: 'default', + tokenName: `disabledMetric-token-${randomId()}`, + }); + + const appName = randomId(); + const instanceId = randomId(); + const featureName = 'metricsDisabled'; + + const now = new Date(); + + await localApp.request + .post('/api/frontend/client/metrics') + .set('Authorization', frontendToken.secret) + .send({ + appName, + instanceId, + bucket: { + start: now, + stop: now, + toggles: { [featureName]: { yes: 2, no: 20 } }, + }, + }) + .expect(204); +});