1
0
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:
Mateusz Kwasniewski 2023-11-03 13:15:12 +01:00 committed by GitHub
parent 9688955d4b
commit 43298e16e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 145 additions and 2 deletions

View File

@ -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));

View File

@ -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,
});
});

View File

@ -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' })

View File

@ -30,6 +30,8 @@ export interface IFeatureSearchParams {
status?: string[][];
limit: number;
cursor?: string;
sortBy: string;
sortOrder: 'asc' | 'desc';
}
export interface IFeatureStrategiesStore

View File

@ -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<