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:
parent
827b8f274a
commit
e876e6438d
@ -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')
|
||||||
|
@ -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' }],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user