diff --git a/src/lib/features/metrics/unknown-flags/unknown-flags-store.ts b/src/lib/features/metrics/unknown-flags/unknown-flags-store.ts index f665f75a3d..9ac4d8098f 100644 --- a/src/lib/features/metrics/unknown-flags/unknown-flags-store.ts +++ b/src/lib/features/metrics/unknown-flags/unknown-flags-store.ts @@ -66,18 +66,24 @@ export class UnknownFlagsStore implements IUnknownFlagsStore { } async getAll({ limit, orderBy }: QueryParams = {}): Promise { - let query = this.db(`${TABLE} AS uf`).select( - 'uf.name', - 'uf.app_name', - 'uf.seen_at', - 'uf.environment', - this.db.raw( - `(SELECT MAX(e.created_at) + let query = this.db(`${TABLE} AS uf`) + .select( + 'uf.name', + 'uf.app_name', + 'uf.seen_at', + 'uf.environment', + this.db.raw( + `(SELECT MAX(e.created_at) FROM ${TABLE_EVENTS} AS e WHERE e.feature_name = uf.name) AS last_event_at`, - ), - ); + ), + ) + .whereNotExists( + this.db('features as f') + .select(this.db.raw('1')) + .whereRaw('f.name = uf.name'), + ); if (orderBy) { query = query.orderBy(orderBy); diff --git a/src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts b/src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts index 898356a3dd..be89d2811d 100644 --- a/src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts +++ b/src/lib/features/metrics/unknown-flags/unknown-flags.e2e.test.ts @@ -182,3 +182,140 @@ describe('should register unknown flags', () => { ); }); }); + +describe('should fetch unknown flags', () => { + test('returns empty list by default', async () => { + const res = await request + .get('/api/admin/metrics/unknown-flags') + .expect(200) + .expect('Content-Type', /json/); + + expect(res.body).toMatchObject({ + unknownFlags: [], + }); + }); + + test('returns list of unknown flags', async () => { + const unknownFlag: BulkRegistrationSchema = { + appName: 'demo', + instanceId: '1', + environment: 'development', + sdkVersion: 'unleash-client-js:1.0.0', + sdkType: 'frontend', + }; + + await request + .post('/api/client/metrics/bulk') + .send({ + applications: [unknownFlag], + metrics: [ + { + featureName: 'unknown_flag_1', + environment: 'development', + appName: 'demo', + timestamp: startOfHour(new Date()), + yes: 1337, + no: 0, + variants: {}, + }, + { + featureName: 'unknown_flag_2', + environment: 'development', + appName: 'demo', + timestamp: startOfHour(new Date()), + yes: 200, + no: 100, + variants: {}, + }, + ], + }) + .expect(202); + + await services.unknownFlagsService.flush(); + + const res = await request + .get('/api/admin/metrics/unknown-flags') + .expect(200) + .expect('Content-Type', /json/); + + expect(res.body.unknownFlags).toHaveLength(2); + expect(res.body.unknownFlags).toEqual([ + expect.objectContaining({ + name: 'unknown_flag_1', + environment: 'development', + appName: 'demo', + lastEventAt: null, + }), + expect.objectContaining({ + name: 'unknown_flag_2', + environment: 'development', + appName: 'demo', + lastEventAt: null, + }), + ]); + }); + + test('does not include flags that have since been created in Unleash', async () => { + const unknownFlag: BulkRegistrationSchema = { + appName: 'demo', + instanceId: '1', + environment: 'development', + sdkVersion: 'unleash-client-js:1.0.0', + sdkType: 'frontend', + }; + + await request + .post('/api/client/metrics/bulk') + .send({ + applications: [unknownFlag], + metrics: [ + { + featureName: 'flag_that_will_be_created', + environment: 'development', + appName: 'demo', + timestamp: startOfHour(new Date()), + yes: 1337, + no: 0, + variants: {}, + }, + { + featureName: 'unknown_flag_2', + environment: 'development', + appName: 'demo', + timestamp: startOfHour(new Date()), + yes: 200, + no: 100, + variants: {}, + }, + ], + }) + .expect(202); + + await services.unknownFlagsService.flush(); + + await request + .post('/api/admin/projects/default/features') + .send({ + name: 'flag_that_will_be_created', + description: '', + type: 'release', + impressionData: false, + }) + .expect(201); + + const res = await request + .get('/api/admin/metrics/unknown-flags') + .expect(200) + .expect('Content-Type', /json/); + + expect(res.body.unknownFlags).toHaveLength(1); + expect(res.body.unknownFlags).toEqual([ + expect.objectContaining({ + name: 'unknown_flag_2', + environment: 'development', + appName: 'demo', + lastEventAt: null, + }), + ]); + }); +});