mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: total count in search results (#5235)
This commit is contained in:
parent
cb2ffdd796
commit
74bbc7799e
@ -95,7 +95,7 @@ export default class FeatureSearchController extends Controller {
|
||||
);
|
||||
const normalizedLimit =
|
||||
Number(limit) > 0 && Number(limit) <= 50 ? Number(limit) : 50;
|
||||
const { features, nextCursor } =
|
||||
const { features, nextCursor, total } =
|
||||
await this.featureSearchService.search({
|
||||
query,
|
||||
projectId,
|
||||
@ -108,7 +108,7 @@ export default class FeatureSearchController extends Controller {
|
||||
});
|
||||
|
||||
res.header('Link', nextLink(req, nextCursor));
|
||||
res.json({ features });
|
||||
res.json({ features, total });
|
||||
} else {
|
||||
throw new InvalidOperationError(
|
||||
'Feature Search API is not enabled',
|
||||
|
@ -22,10 +22,11 @@ export class FeatureSearchService {
|
||||
|
||||
async search(params: IFeatureSearchParams) {
|
||||
// fetch one more item than needed to get a cursor of the next item
|
||||
const features = await this.featureStrategiesStore.searchFeatures({
|
||||
...params,
|
||||
limit: params.limit + 1,
|
||||
});
|
||||
const { features, total } =
|
||||
await this.featureStrategiesStore.searchFeatures({
|
||||
...params,
|
||||
limit: params.limit + 1,
|
||||
});
|
||||
|
||||
const nextCursor =
|
||||
features.length > params.limit
|
||||
@ -39,6 +40,7 @@ export class FeatureSearchService {
|
||||
? features.slice(0, -1)
|
||||
: features,
|
||||
nextCursor,
|
||||
total,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,7 @@ test('should search matching features by name', async () => {
|
||||
|
||||
expect(body).toMatchObject({
|
||||
features: [{ name: 'my_feature_a' }, { name: 'my_feature_b' }],
|
||||
total: 2,
|
||||
});
|
||||
});
|
||||
|
||||
@ -120,6 +121,7 @@ test('should paginate with cursor', async () => {
|
||||
|
||||
expect(firstPage).toMatchObject({
|
||||
features: [{ name: 'my_feature_a' }, { name: 'my_feature_b' }],
|
||||
total: 4,
|
||||
});
|
||||
|
||||
const { body: secondPage, headers: secondHeaders } = await getPage(
|
||||
@ -128,6 +130,7 @@ test('should paginate with cursor', async () => {
|
||||
|
||||
expect(secondPage).toMatchObject({
|
||||
features: [{ name: 'my_feature_c' }, { name: 'my_feature_d' }],
|
||||
total: 4,
|
||||
});
|
||||
|
||||
expect(secondHeaders.link).toBe('');
|
||||
|
@ -325,8 +325,10 @@ export default class FakeFeatureStrategiesStore
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
searchFeatures(params: IFeatureSearchParams): Promise<IFeatureOverview[]> {
|
||||
return Promise.resolve([]);
|
||||
searchFeatures(
|
||||
params: IFeatureSearchParams,
|
||||
): Promise<{ features: IFeatureOverview[]; total: number }> {
|
||||
return Promise.resolve({ features: [], total: 0 });
|
||||
}
|
||||
|
||||
getAllByFeatures(
|
||||
|
@ -532,14 +532,17 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
status,
|
||||
cursor,
|
||||
limit,
|
||||
}: IFeatureSearchParams): Promise<IFeatureOverview[]> {
|
||||
let query = this.db('features').limit(limit);
|
||||
}: IFeatureSearchParams): Promise<{
|
||||
features: IFeatureOverview[];
|
||||
total: number;
|
||||
}> {
|
||||
let query = this.db('features');
|
||||
if (projectId) {
|
||||
query = query.where({ project: projectId });
|
||||
}
|
||||
|
||||
if (queryString?.trim()) {
|
||||
// todo: we can run cheaper query when no colon is detected
|
||||
// todo: we can run a cheaper query when no colon is detected
|
||||
const tagQuery = this.db
|
||||
.from('feature_tag')
|
||||
.select('feature_name')
|
||||
@ -582,11 +585,6 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
});
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
query = query.where('features.created_at', '>=', cursor);
|
||||
}
|
||||
query = query.orderBy('features.created_at', 'asc');
|
||||
|
||||
query = query
|
||||
.modify(FeatureToggleStore.filterByArchived, false)
|
||||
.leftJoin(
|
||||
@ -601,6 +599,8 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
)
|
||||
.leftJoin('feature_tag as ft', 'ft.feature_name', 'features.name');
|
||||
|
||||
const countQuery = query.clone();
|
||||
|
||||
if (this.flagResolver.isEnabled('useLastSeenRefactor')) {
|
||||
query.leftJoin('last_seen_at_metrics', function () {
|
||||
this.on(
|
||||
@ -670,14 +670,24 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
];
|
||||
}
|
||||
|
||||
query = query.select(selectColumns);
|
||||
if (cursor) {
|
||||
query = query.where('features.created_at', '>=', cursor);
|
||||
}
|
||||
query = query.orderBy('features.created_at', 'asc');
|
||||
|
||||
const total = await countQuery
|
||||
.countDistinct({ total: 'features.name' })
|
||||
.first();
|
||||
|
||||
query = query.select(selectColumns).limit(limit);
|
||||
const rows = await query;
|
||||
|
||||
if (rows.length > 0) {
|
||||
const overview = this.getFeatureOverviewData(getUniqueRows(rows));
|
||||
return sortEnvironments(overview);
|
||||
const features = sortEnvironments(overview);
|
||||
return { features, total: Number(total?.total) || 0 };
|
||||
}
|
||||
return [];
|
||||
return { features: [], total: 0 };
|
||||
}
|
||||
|
||||
async getFeatureOverview({
|
||||
|
@ -59,7 +59,9 @@ export interface IFeatureStrategiesStore
|
||||
getFeatureOverview(
|
||||
params: IFeatureProjectUserParams,
|
||||
): Promise<IFeatureOverview[]>;
|
||||
searchFeatures(params: IFeatureSearchParams): Promise<IFeatureOverview[]>;
|
||||
searchFeatures(
|
||||
params: IFeatureSearchParams,
|
||||
): Promise<{ features: IFeatureOverview[]; total: number }>;
|
||||
getStrategyById(id: string): Promise<IFeatureStrategy>;
|
||||
updateStrategy(
|
||||
id: string,
|
||||
|
@ -71,10 +71,10 @@ export const featureSearchQueryParameters = [
|
||||
name: 'limit',
|
||||
schema: {
|
||||
type: 'string',
|
||||
example: '10',
|
||||
example: '50',
|
||||
},
|
||||
description:
|
||||
'The number of results to return in a page. By default it is set to 50',
|
||||
'The number of feature environments to return in a page. By default it is set to 50.',
|
||||
in: 'query',
|
||||
},
|
||||
] as const;
|
||||
|
@ -24,6 +24,12 @@ export const searchFeaturesSchema = {
|
||||
description:
|
||||
'The full list of features in this project (excluding archived features)',
|
||||
},
|
||||
total: {
|
||||
type: 'number',
|
||||
description:
|
||||
'Total count of the features matching search and filter criteria',
|
||||
example: 10,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
|
Loading…
Reference in New Issue
Block a user