diff --git a/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts b/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts index 5cb03de1ec18..a5164fc5d10d 100644 --- a/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts +++ b/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts @@ -386,6 +386,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { acc.description = r.description; acc.project = r.project; acc.stale = r.stale; + acc.lastSeenAt = r.last_seen_at; acc.createdAt = r.created_at; acc.type = r.type; @@ -638,6 +639,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { 'features.type as type', 'features.created_at as created_at', 'features.stale as stale', + 'features.last_seen_at as last_seen_at', 'features.impression_data as impression_data', 'feature_environments.enabled as enabled', 'feature_environments.environment as environment', @@ -806,6 +808,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { 'features.type as type', 'features.created_at as created_at', 'features.stale as stale', + 'features.last_seen_at as last_seen_at', 'features.impression_data as impression_data', 'feature_environments.enabled as enabled', 'feature_environments.environment as environment', @@ -888,6 +891,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { createdAt: row.created_at, stale: row.stale, impressionData: row.impression_data, + lastSeenAt: row.last_seen_at, environments: [FeatureStrategiesStore.getEnvironment(row)], }; @@ -898,7 +902,8 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { const featureRow = acc[row.feature_name]; if ( featureRow.lastSeenAt === undefined || - new Date(row.env_last_seen_at) > new Date(featureRow.lastSeenAt) + new Date(row.env_last_seen_at) > + new Date(featureRow.last_seen_at) ) { featureRow.lastSeenAt = row.env_last_seen_at; } diff --git a/src/lib/services/client-metrics/last-seen/last-seen-store.ts b/src/lib/services/client-metrics/last-seen/last-seen-store.ts index cc1deb69303b..9dc91d89178e 100644 --- a/src/lib/services/client-metrics/last-seen/last-seen-store.ts +++ b/src/lib/services/client-metrics/last-seen/last-seen-store.ts @@ -74,6 +74,10 @@ export default class LastSeenStore implements ILastSeenStore { async cleanLastSeen() { await this.db(TABLE) .whereNotIn('feature_name', this.db.select('name').from('features')) + .or.whereNotIn( + 'environment', + this.db.select('name').from('environments'), + ) .del(); } } diff --git a/src/lib/services/client-metrics/last-seen/tests/last-seen-service.e2e.test.ts b/src/lib/services/client-metrics/last-seen/tests/last-seen-service.e2e.test.ts index 6594a86fabb8..73d793e797f6 100644 --- a/src/lib/services/client-metrics/last-seen/tests/last-seen-service.e2e.test.ts +++ b/src/lib/services/client-metrics/last-seen/tests/last-seen-service.e2e.test.ts @@ -24,6 +24,10 @@ beforeAll(async () => { ); }); +beforeEach(async () => { + await db.rawDatabase.raw('DELETE FROM last_seen_at_metrics;'); +}); + afterAll(async () => { await app.destroy(); await db.destroy(); @@ -79,3 +83,51 @@ test('should clean unknown feature toggle names from last seen store', async () expect(notInDirty.length).toBe(4); }); + +test('should clean unknown feature toggle environments from last seen store', async () => { + const { lastSeenService, featureToggleService } = app.services; + + const clean = [ + { name: 'clean5', environment: 'default' }, + { name: 'clean6', environment: 'default' }, + { name: 'clean7', environment: 'nonexisting' }, + { name: 'clean8', environment: 'nonexisting' }, + ]; + + await Promise.all( + clean.map((feature) => + featureToggleService.createFeatureToggle( + 'default', + { name: feature.name }, + 'user', + ), + ), + ); + + const inserts = [...clean].map((feature) => { + return { + featureName: feature.name, + environment: feature.environment, + yes: 1, + no: 0, + appName: 'test', + timestamp: new Date(), + }; + }); + + lastSeenService.updateLastSeen(inserts); + await lastSeenService.store(); + + // We have no method to get these from the last seen service or any other service or store + let stored = await db.rawDatabase.raw( + 'SELECT * FROM last_seen_at_metrics;', + ); + + expect(stored.rows.length).toBe(4); + + await lastSeenService.cleanLastSeen(); + + stored = await db.rawDatabase.raw('SELECT * FROM last_seen_at_metrics;'); + + expect(stored.rows.length).toBe(2); +}); diff --git a/src/migrations/20231123100052-drop-last-seen-foreign-key.js b/src/migrations/20231123100052-drop-last-seen-foreign-key.js new file mode 100644 index 000000000000..eaa0557d192b --- /dev/null +++ b/src/migrations/20231123100052-drop-last-seen-foreign-key.js @@ -0,0 +1,17 @@ +exports.up = function (db, cb) { + db.runSql( + ` + ALTER TABLE last_seen_at_metrics DROP CONSTRAINT last_seen_at_metrics_environment_fkey; + `, + cb + ); +}; + +exports.down = function (db, cb) { + db.runSql( + ` + ALTER TABLE last_seen_at_metrics ADD CONSTRAINT last_seen_at_metrics_environment_fkey FOREIGN KEY (environment) REFERENCES environments(name); + `, + cb + ); +}; \ No newline at end of file