mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-15 01:16:22 +02:00
feat: Server side sort by (#5250)
This commit is contained in:
parent
9688955d4b
commit
43298e16e2
@ -81,6 +81,8 @@ export default class FeatureSearchController extends Controller {
|
||||
status,
|
||||
cursor,
|
||||
limit = '50',
|
||||
sortOrder,
|
||||
sortBy,
|
||||
} = req.query;
|
||||
const userId = req.user.id;
|
||||
const normalizedTag = tag
|
||||
@ -95,6 +97,9 @@ export default class FeatureSearchController extends Controller {
|
||||
);
|
||||
const normalizedLimit =
|
||||
Number(limit) > 0 && Number(limit) <= 50 ? Number(limit) : 50;
|
||||
const normalizedSortBy: string = sortBy ? sortBy : 'createdAt';
|
||||
const normalizedSortOrder =
|
||||
sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc';
|
||||
const { features, nextCursor, total } =
|
||||
await this.featureSearchService.search({
|
||||
query,
|
||||
@ -105,6 +110,8 @@ export default class FeatureSearchController extends Controller {
|
||||
status: normalizedStatus,
|
||||
cursor,
|
||||
limit: normalizedLimit,
|
||||
sortBy: normalizedSortBy,
|
||||
sortOrder: normalizedSortOrder,
|
||||
});
|
||||
|
||||
res.header('Link', nextLink(req, nextCursor));
|
||||
|
@ -43,6 +43,21 @@ const searchFeatures = async (
|
||||
.expect(expectedCode);
|
||||
};
|
||||
|
||||
const sortFeatures = async (
|
||||
{
|
||||
sortBy = '',
|
||||
sortOrder = '',
|
||||
projectId = 'default',
|
||||
}: FeatureSearchQueryParameters,
|
||||
expectedCode = 200,
|
||||
) => {
|
||||
return app.request
|
||||
.get(
|
||||
`/api/admin/search/features?sortBy=${sortBy}&sortOrder=${sortOrder}&projectId=${projectId}`,
|
||||
)
|
||||
.expect(expectedCode);
|
||||
};
|
||||
|
||||
const searchFeaturesWithCursor = async (
|
||||
{
|
||||
query = '',
|
||||
@ -243,3 +258,80 @@ test('should return features without query', async () => {
|
||||
features: [{ name: 'my_feature_a' }, { name: 'my_feature_b' }],
|
||||
});
|
||||
});
|
||||
|
||||
test('should sort features', async () => {
|
||||
await app.createFeature('my_feature_a');
|
||||
await app.createFeature('my_feature_c');
|
||||
await app.createFeature('my_feature_b');
|
||||
await app.enableFeature('my_feature_c', 'default');
|
||||
|
||||
const { body: ascName } = await sortFeatures({
|
||||
sortBy: 'name',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
|
||||
expect(ascName).toMatchObject({
|
||||
features: [
|
||||
{ name: 'my_feature_a' },
|
||||
{ name: 'my_feature_b' },
|
||||
{ name: 'my_feature_c' },
|
||||
],
|
||||
total: 3,
|
||||
});
|
||||
|
||||
const { body: descName } = await sortFeatures({
|
||||
sortBy: 'name',
|
||||
sortOrder: 'desc',
|
||||
});
|
||||
|
||||
expect(descName).toMatchObject({
|
||||
features: [
|
||||
{ name: 'my_feature_c' },
|
||||
{ name: 'my_feature_b' },
|
||||
{ name: 'my_feature_a' },
|
||||
],
|
||||
total: 3,
|
||||
});
|
||||
|
||||
const { body: defaultCreatedAt } = await sortFeatures({
|
||||
sortBy: '',
|
||||
sortOrder: '',
|
||||
});
|
||||
|
||||
expect(defaultCreatedAt).toMatchObject({
|
||||
features: [
|
||||
{ name: 'my_feature_a' },
|
||||
{ name: 'my_feature_c' },
|
||||
{ name: 'my_feature_b' },
|
||||
],
|
||||
total: 3,
|
||||
});
|
||||
|
||||
const { body: environmentAscSort } = await sortFeatures({
|
||||
sortBy: 'environment:default',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
|
||||
expect(environmentAscSort).toMatchObject({
|
||||
features: [
|
||||
{ name: 'my_feature_a' },
|
||||
{ name: 'my_feature_b' },
|
||||
{ name: 'my_feature_c' },
|
||||
],
|
||||
total: 3,
|
||||
});
|
||||
|
||||
const { body: environmentDescSort } = await sortFeatures({
|
||||
sortBy: 'environment:default',
|
||||
sortOrder: 'desc',
|
||||
});
|
||||
|
||||
expect(environmentDescSort).toMatchObject({
|
||||
features: [
|
||||
{ name: 'my_feature_c' },
|
||||
{ name: 'my_feature_a' },
|
||||
{ name: 'my_feature_b' },
|
||||
],
|
||||
total: 3,
|
||||
});
|
||||
});
|
||||
|
@ -532,6 +532,8 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
status,
|
||||
cursor,
|
||||
limit,
|
||||
sortOrder,
|
||||
sortBy,
|
||||
}: IFeatureSearchParams): Promise<{
|
||||
features: IFeatureOverview[];
|
||||
total: number;
|
||||
@ -681,7 +683,27 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
if (cursor) {
|
||||
query = query.where('features.created_at', '>=', cursor);
|
||||
}
|
||||
query = query.orderBy('features.created_at', 'asc');
|
||||
|
||||
const sortByMapping = {
|
||||
name: 'feature_name',
|
||||
type: 'type',
|
||||
lastSeenAt: 'env_last_seen_at',
|
||||
};
|
||||
if (sortBy.startsWith('environment:')) {
|
||||
const [, envName] = sortBy.split(':');
|
||||
query = query
|
||||
.orderByRaw(
|
||||
`CASE WHEN feature_environments.environment = ? THEN feature_environments.enabled ELSE NULL END ${sortOrder}`,
|
||||
[envName],
|
||||
)
|
||||
.orderBy('created_at', 'asc');
|
||||
} else if (sortByMapping[sortBy]) {
|
||||
query = query
|
||||
.orderBy(sortByMapping[sortBy], sortOrder)
|
||||
.orderBy('created_at', 'asc');
|
||||
} else {
|
||||
query = query.orderBy('created_at', sortOrder);
|
||||
}
|
||||
|
||||
const total = await countQuery
|
||||
.countDistinct({ total: 'features.name' })
|
||||
|
@ -30,6 +30,8 @@ export interface IFeatureSearchParams {
|
||||
status?: string[][];
|
||||
limit: number;
|
||||
cursor?: string;
|
||||
sortBy: string;
|
||||
sortOrder: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export interface IFeatureStrategiesStore
|
||||
|
@ -7,7 +7,7 @@ export const featureSearchQueryParameters = [
|
||||
type: 'string',
|
||||
example: 'feature_a',
|
||||
},
|
||||
description: 'The search query for the feature or tag',
|
||||
description: 'The search query for the feature name or tag',
|
||||
in: 'query',
|
||||
},
|
||||
{
|
||||
@ -77,6 +77,26 @@ export const featureSearchQueryParameters = [
|
||||
'The number of feature environments to return in a page. By default it is set to 50.',
|
||||
in: 'query',
|
||||
},
|
||||
{
|
||||
name: 'sortBy',
|
||||
schema: {
|
||||
type: 'string',
|
||||
example: 'type',
|
||||
},
|
||||
description:
|
||||
'The field to sort the results by. By default it is set to "createdAt".',
|
||||
in: 'query',
|
||||
},
|
||||
{
|
||||
name: 'sortOrder',
|
||||
schema: {
|
||||
type: 'string',
|
||||
example: 'desc',
|
||||
},
|
||||
description:
|
||||
'The sort order for the sortBy. By default it is det to "asc".',
|
||||
in: 'query',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type FeatureSearchQueryParameters = Partial<
|
||||
|
Loading…
Reference in New Issue
Block a user