From 1429b5495778f03ed8dde91e541d076dd944dba1 Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Tue, 21 Nov 2023 15:19:06 +0200 Subject: [PATCH] feat: sorting by last seen, environments now working properly (#5376) Now calculates final ranks also, if there are some ranks missing from duplicates. --- .../feature-search/feature.search.e2e.test.ts | 83 ++++++++++++++++--- .../feature-toggle-strategies-store.ts | 15 +++- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/lib/features/feature-search/feature.search.e2e.test.ts b/src/lib/features/feature-search/feature.search.e2e.test.ts index 9227f0f9e5..c39deaabb6 100644 --- a/src/lib/features/feature-search/feature.search.e2e.test.ts +++ b/src/lib/features/feature-search/feature.search.e2e.test.ts @@ -2,13 +2,14 @@ import dbInit, { ITestDb } from '../../../test/e2e/helpers/database-init'; import { IUnleashTest, setupAppWithAuth, - setupAppWithCustomConfig, } from '../../../test/e2e/helpers/test-helper'; import getLogger from '../../../test/fixtures/no-logger'; import { FeatureSearchQueryParameters } from '../../openapi/spec/feature-search-query-parameters'; +import { IUnleashStores } from '../../types'; let app: IUnleashTest; let db: ITestDb; +let stores: IUnleashStores; beforeAll(async () => { db = await dbInit('feature_search', getLogger); @@ -24,6 +25,7 @@ beforeAll(async () => { }, db.rawDatabase, ); + stores = db.stores; await app.request .post(`/auth/demo/login`) @@ -410,21 +412,78 @@ test('should paginate correctly when using tags', async () => { await app.createFeature('my_feature_c'); await app.createFeature('my_feature_d'); - await app.addTag('my_feature_b', { type: 'simple', value: 'first_tag' }); - await app.addTag('my_feature_b', { type: 'simple', value: 'second_tag' }); - await app.addTag('my_feature_a', { type: 'simple', value: 'second_tag' }); - await app.addTag('my_feature_c', { type: 'simple', value: 'second_tag' }); - await app.addTag('my_feature_c', { type: 'simple', value: 'first_tag' }); + await app.addTag('my_feature_b', { + type: 'simple', + value: 'first_tag', + }); + await app.addTag('my_feature_b', { + type: 'simple', + value: 'second_tag', + }); + await app.addTag('my_feature_a', { + type: 'simple', + value: 'second_tag', + }); + await app.addTag('my_feature_c', { + type: 'simple', + value: 'second_tag', + }); + await app.addTag('my_feature_c', { + type: 'simple', + value: 'first_tag', + }); - const { body: secondPage, headers: secondHeaders } = - await searchFeaturesWithOffset({ - query: 'feature', - offset: '2', - limit: '2', - }); + const { body: secondPage } = await searchFeaturesWithOffset({ + query: 'feature', + offset: '2', + limit: '2', + }); expect(secondPage).toMatchObject({ features: [{ name: 'my_feature_c' }, { name: 'my_feature_d' }], total: 4, }); }); + +test('should not return duplicate entries when sorting by last seen', async () => { + await app.createFeature('my_feature_a'); + await app.createFeature('my_feature_b'); + await app.createFeature('my_feature_c'); + + await stores.environmentStore.create({ + name: 'production', + type: 'production', + }); + + await app.linkProjectToEnvironment('default', 'production'); + await app.enableFeature('my_feature_a', 'production'); + await app.enableFeature('my_feature_b', 'production'); + + const { body } = await sortFeatures({ + sortBy: 'environment:production', + sortOrder: 'desc', + }); + + expect(body).toMatchObject({ + features: [ + { name: 'my_feature_a' }, + { name: 'my_feature_b' }, + { name: 'my_feature_c' }, + ], + total: 3, + }); + + const { body: ascendingBody } = await sortFeatures({ + sortBy: 'environment:production', + sortOrder: 'asc', + }); + + expect(ascendingBody).toMatchObject({ + features: [ + { name: 'my_feature_c' }, + { name: 'my_feature_a' }, + { name: 'my_feature_b' }, + ], + total: 3, + }); +}); 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 34fe4aac50..08737ba26c 100644 --- a/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts +++ b/src/lib/features/feature-toggle/feature-toggle-strategies-store.ts @@ -733,14 +733,25 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore { .select(selectColumns) .denseRank('rank', this.db.raw(rankingSql)); }) + .with( + 'final_ranks', + this.db.raw( + 'select feature_name, row_number() over (order by min(rank)) as final_rank from ranked_features group by feature_name', + ), + ) .with( 'total_features', - this.db.raw('select max(rank) as total from ranked_features'), + this.db.raw('select count(*) as total from final_ranks'), ) .select('*') .from('ranked_features') + .innerJoin( + 'final_ranks', + 'ranked_features.feature_name', + 'final_ranks.feature_name', + ) .joinRaw('CROSS JOIN total_features') - .whereBetween('rank', [offset + 1, offset + limit]); + .whereBetween('final_rank', [offset + 1, offset + limit]); const rows = await finalQuery;