1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-10 01:16:39 +02:00

feat: sort favorites on the backend (#5326)

Now favorites will be always on first page, if pinned.
This commit is contained in:
Jaanus Sellin 2023-11-14 09:22:35 +02:00 committed by GitHub
parent 0f7360c1e8
commit 5d762dcb39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 101 additions and 11 deletions

View File

@ -81,6 +81,7 @@ export default class FeatureSearchController extends Controller {
limit = '50',
sortOrder,
sortBy,
favoritesFirst,
} = req.query;
const userId = req.user.id;
const normalizedTag = tag?.map((tag) => tag.split(':'));
@ -97,6 +98,7 @@ export default class FeatureSearchController extends Controller {
const normalizedSortBy: string = sortBy ? sortBy : 'createdAt';
const normalizedSortOrder =
sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc';
const normalizedFavoritesFirst = favoritesFirst === 'true';
const { features, total } = await this.featureSearchService.search({
query,
projectId,
@ -108,6 +110,7 @@ export default class FeatureSearchController extends Controller {
limit: normalizedLimit,
sortBy: normalizedSortBy,
sortOrder: normalizedSortOrder,
favoritesFirst: normalizedFavoritesFirst,
});
res.json({ features, total });

View File

@ -1,6 +1,7 @@
import dbInit, { ITestDb } from '../../../test/e2e/helpers/database-init';
import {
IUnleashTest,
setupAppWithAuth,
setupAppWithCustomConfig,
} from '../../../test/e2e/helpers/test-helper';
import getLogger from '../../../test/fixtures/no-logger';
@ -11,7 +12,7 @@ let db: ITestDb;
beforeAll(async () => {
db = await dbInit('feature_search', getLogger);
app = await setupAppWithCustomConfig(
app = await setupAppWithAuth(
db.stores,
{
experimental: {
@ -23,6 +24,13 @@ beforeAll(async () => {
},
db.rawDatabase,
);
await app.request
.post(`/auth/demo/login`)
.send({
email: 'user@getunleash.io',
})
.expect(200);
});
afterAll(async () => {
@ -48,12 +56,13 @@ const sortFeatures = async (
sortBy = '',
sortOrder = '',
projectId = 'default',
favoritesFirst = 'false',
}: FeatureSearchQueryParameters,
expectedCode = 200,
) => {
return app.request
.get(
`/api/admin/search/features?sortBy=${sortBy}&sortOrder=${sortOrder}&projectId=${projectId}`,
`/api/admin/search/features?sortBy=${sortBy}&sortOrder=${sortOrder}&projectId=${projectId}&favoritesFirst=${favoritesFirst}`,
)
.expect(expectedCode);
};
@ -149,8 +158,14 @@ test('should paginate with offset', async () => {
});
test('should filter features by type', async () => {
await app.createFeature({ name: 'my_feature_a', type: 'release' });
await app.createFeature({ name: 'my_feature_b', type: 'experimental' });
await app.createFeature({
name: 'my_feature_a',
type: 'release',
});
await app.createFeature({
name: 'my_feature_b',
type: 'experimental',
});
const { body } = await filterFeaturesByType([
'experimental',
@ -165,7 +180,10 @@ test('should filter features by type', async () => {
test('should filter features by tag', async () => {
await app.createFeature('my_feature_a');
await app.createFeature('my_feature_b');
await app.addTag('my_feature_a', { type: 'simple', value: 'my_tag' });
await app.addTag('my_feature_a', {
type: 'simple',
value: 'my_tag',
});
const { body } = await filterFeaturesByTag(['simple:my_tag']);
@ -193,7 +211,10 @@ test('should filter features by environment status', async () => {
test('should filter by partial tag', async () => {
await app.createFeature('my_feature_a');
await app.createFeature('my_feature_b');
await app.addTag('my_feature_a', { type: 'simple', value: 'my_tag' });
await app.addTag('my_feature_a', {
type: 'simple',
value: 'my_tag',
});
const { body } = await filterFeaturesByTag(['simple']);
@ -205,7 +226,10 @@ test('should filter by partial tag', async () => {
test('should search matching features by tag', async () => {
await app.createFeature('my_feature_a');
await app.createFeature('my_feature_b');
await app.addTag('my_feature_a', { type: 'simple', value: 'my_tag' });
await app.addTag('my_feature_a', {
type: 'simple',
value: 'my_tag',
});
const { body: fullMatch } = await searchFeatures({
query: 'simple:my_tag',
@ -230,8 +254,14 @@ test('should search matching features by tag', async () => {
test('should return all feature tags', async () => {
await app.createFeature('my_feature_a');
await app.addTag('my_feature_a', { type: 'simple', value: 'my_tag' });
await app.addTag('my_feature_a', { type: 'simple', value: 'second_tag' });
await app.addTag('my_feature_a', {
type: 'simple',
value: 'my_tag',
});
await app.addTag('my_feature_a', {
type: 'simple',
value: 'second_tag',
});
const { body } = await searchFeatures({});
@ -240,8 +270,14 @@ test('should return all feature tags', async () => {
{
name: 'my_feature_a',
tags: [
{ type: 'simple', value: 'my_tag' },
{ type: 'simple', value: 'second_tag' },
{
type: 'simple',
value: 'my_tag',
},
{
type: 'simple',
value: 'second_tag',
},
],
},
],
@ -281,6 +317,7 @@ test('should sort features', async () => {
await app.createFeature('my_feature_c');
await app.createFeature('my_feature_b');
await app.enableFeature('my_feature_c', 'default');
await app.favoriteFeature('my_feature_b');
const { body: ascName } = await sortFeatures({
sortBy: 'name',
@ -351,4 +388,19 @@ test('should sort features', async () => {
],
total: 3,
});
const { body: favoriteEnvironmentDescSort } = await sortFeatures({
sortBy: 'environment:default',
sortOrder: 'desc',
favoritesFirst: 'true',
});
expect(favoriteEnvironmentDescSort).toMatchObject({
features: [
{ name: 'my_feature_b' },
{ name: 'my_feature_c' },
{ name: 'my_feature_a' },
],
total: 3,
});
});

View File

@ -530,6 +530,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
limit,
sortOrder,
sortBy,
favoritesFirst,
}: IFeatureSearchParams): Promise<{
features: IFeatureOverview[];
total: number;
@ -706,6 +707,10 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
lastSeenAt: 'env_last_seen_at',
};
if (favoritesFirst) {
query = query.orderBy('favorite', 'desc');
}
if (sortBy.startsWith('environment:')) {
const [, envName] = sortBy.split(':');
query = query

View File

@ -29,6 +29,7 @@ export interface IFeatureSearchParams {
tag?: string[][];
status?: string[][];
offset: number;
favoritesFirst?: boolean;
limit: number;
sortBy: string;
sortOrder: 'asc' | 'desc';

View File

@ -97,6 +97,16 @@ export const featureSearchQueryParameters = [
'The sort order for the sortBy. By default it is det to "asc".',
in: 'query',
},
{
name: 'favoritesFirst',
schema: {
type: 'string',
example: 'true',
},
description:
'The flag to indicate if the favorite features should be returned first. By default it is set to false.',
in: 'query',
},
] as const;
export type FeatureSearchQueryParameters = Partial<

View File

@ -54,6 +54,12 @@ export interface IUnleashHttpAPI {
expectedResponseCode?: number,
): supertest.Test;
favoriteFeature(
feature: string,
project?: string,
expectedResponseCode?: number,
): supertest.Test;
getFeatures(name?: string, expectedResponseCode?: number): supertest.Test;
getProjectFeatures(
@ -239,6 +245,19 @@ function httpApis(
)
.expect(expectedResponseCode);
},
favoriteFeature(
feature: string,
project = 'default',
expectedResponseCode = 200,
): supertest.Test {
return request
.post(
`/api/admin/projects/${project}/features/${feature}/favorites`,
)
.set('Content-Type', 'application/json')
.expect(expectedResponseCode);
},
};
}