1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

feat: total count respect lifecycle filter (#9724)

This commit is contained in:
Mateusz Kwasniewski 2025-04-09 08:54:19 +02:00 committed by GitHub
parent 827b8f274a
commit e876e6438d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 56 additions and 50 deletions

View File

@ -79,24 +79,6 @@ class FeatureSearchStore implements IFeatureSearchStore {
}; };
} }
private getLatestLifecycleStageQuery() {
return this.db('feature_lifecycles')
.select(
'feature as stage_feature',
'stage as latest_stage',
'status as stage_status',
'created_at as entered_stage_at',
)
.distinctOn('stage_feature')
.orderBy([
'stage_feature',
{
column: 'entered_stage_at',
order: 'desc',
},
]);
}
async searchFeatures( async searchFeatures(
{ {
userId, userId,
@ -147,6 +129,9 @@ class FeatureSearchStore implements IFeatureSearchStore {
'users.username as user_username', 'users.username as user_username',
'users.email as user_email', 'users.email as user_email',
'users.image_url as user_image_url', 'users.image_url as user_image_url',
'lifecycle.latest_stage',
'lifecycle.stage_status',
'lifecycle.entered_stage_at',
] as (string | Raw<any> | Knex.QueryBuilder)[]; ] as (string | Raw<any> | Knex.QueryBuilder)[];
const lastSeenQuery = 'last_seen_at_metrics.last_seen_at'; const lastSeenQuery = 'last_seen_at_metrics.last_seen_at';
@ -245,19 +230,48 @@ class FeatureSearchStore implements IFeatureSearchStore {
'users', 'users',
'users.id', 'users.id',
'features.created_by_user_id', 'features.created_by_user_id',
)
.leftJoin('last_seen_at_metrics', function () {
this.on(
'last_seen_at_metrics.environment',
'=',
'environments.name',
).andOn(
'last_seen_at_metrics.feature_name',
'=',
'features.name',
);
})
.leftJoin(
this.db
.select(
'feature as stage_feature',
'stage as latest_stage',
'status as stage_status',
'created_at as entered_stage_at',
)
.from('feature_lifecycles')
.distinctOn('feature')
.orderBy([
'feature',
{ column: 'created_at', order: 'desc' },
])
.as('lifecycle'),
'features.name',
'lifecycle.stage_feature',
); );
query.leftJoin('last_seen_at_metrics', function () { if (this.flagResolver.isEnabled('flagsOverviewSearch')) {
this.on( const parsedLifecycle = lifecycle
'last_seen_at_metrics.environment', ? parseSearchOperatorValue(
'=', 'lifecycle.latest_stage',
'environments.name', lifecycle,
).andOn( )
'last_seen_at_metrics.feature_name', : null;
'=', if (parsedLifecycle) {
'features.name', applyGenericQueryParams(query, [parsedLifecycle]);
); }
}); }
const rankingSql = this.buildRankingSql( const rankingSql = this.buildRankingSql(
favoritesFirst, favoritesFirst,
@ -270,7 +284,6 @@ class FeatureSearchStore implements IFeatureSearchStore {
.select(selectColumns) .select(selectColumns)
.denseRank('rank', this.db.raw(rankingSql)); .denseRank('rank', this.db.raw(rankingSql));
}) })
.with('lifecycle', this.getLatestLifecycleStageQuery())
.with( .with(
'final_ranks', 'final_ranks',
this.db.raw( this.db.raw(
@ -321,26 +334,8 @@ class FeatureSearchStore implements IFeatureSearchStore {
.joinRaw('CROSS JOIN total_features') .joinRaw('CROSS JOIN total_features')
.whereBetween('final_rank', [offset + 1, offset + limit]) .whereBetween('final_rank', [offset + 1, offset + limit])
.orderBy('final_rank'); .orderBy('final_rank');
finalQuery
.select(
'lifecycle.latest_stage',
'lifecycle.stage_status',
'lifecycle.entered_stage_at',
)
.leftJoin(
'lifecycle',
'ranked_features.feature_name',
'lifecycle.stage_feature',
);
if (this.flagResolver.isEnabled('flagsOverviewSearch')) { if (this.flagResolver.isEnabled('flagsOverviewSearch')) {
const parsedLifecycle = lifecycle
? parseSearchOperatorValue('lifecycle.latest_stage', lifecycle)
: null;
if (parsedLifecycle) {
applyGenericQueryParams(finalQuery, [parsedLifecycle]);
}
finalQuery finalQuery
.leftJoin( .leftJoin(
this.db('change_request_events AS cre') this.db('change_request_events AS cre')

View File

@ -1111,6 +1111,10 @@ test('should return environment usage metrics and lifecycle', async () => {
name: 'my_feature_b', name: 'my_feature_b',
createdAt: '2023-01-29T15:21:39.975Z', createdAt: '2023-01-29T15:21:39.975Z',
}); });
await app.createFeature({
name: 'my_feature_c',
createdAt: '2023-01-29T15:21:39.975Z',
});
await stores.clientMetricsStoreV2.batchInsertMetrics([ await stores.clientMetricsStoreV2.batchInsertMetrics([
{ {
@ -1142,6 +1146,9 @@ test('should return environment usage metrics and lifecycle', async () => {
await stores.featureLifecycleStore.insert([ await stores.featureLifecycleStore.insert([
{ feature: 'my_feature_b', stage: 'initial' }, { feature: 'my_feature_b', stage: 'initial' },
]); ]);
await stores.featureLifecycleStore.insert([
{ feature: 'my_feature_c', stage: 'initial' },
]);
await stores.featureLifecycleStore.insert([ await stores.featureLifecycleStore.insert([
{ feature: 'my_feature_b', stage: 'completed', status: 'discarded' }, { feature: 'my_feature_b', stage: 'completed', status: 'discarded' },
]); ]);
@ -1150,6 +1157,7 @@ test('should return environment usage metrics and lifecycle', async () => {
query: 'my_feature_b', query: 'my_feature_b',
}); });
expect(noExplicitLifecycle).toMatchObject({ expect(noExplicitLifecycle).toMatchObject({
total: 1,
features: [ features: [
{ {
name: 'my_feature_b', name: 'my_feature_b',
@ -1180,14 +1188,17 @@ test('should return environment usage metrics and lifecycle', async () => {
query: 'my_feature_b', query: 'my_feature_b',
lifecycle: 'IS:initial', lifecycle: 'IS:initial',
}); });
expect(noFeaturesWithOtherLifecycle).toMatchObject({ features: [] }); expect(noFeaturesWithOtherLifecycle).toMatchObject({
total: 0,
features: [],
});
const { body: featureWithMatchingLifecycle } = const { body: featureWithMatchingLifecycle } =
await searchFeaturesWithLifecycle({ await searchFeaturesWithLifecycle({
query: 'my_feature_b',
lifecycle: 'IS:completed', lifecycle: 'IS:completed',
}); });
expect(featureWithMatchingLifecycle).toMatchObject({ expect(featureWithMatchingLifecycle).toMatchObject({
total: 1,
features: [{ name: 'my_feature_b' }], features: [{ name: 'my_feature_b' }],
}); });
}); });