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:
parent
0f7360c1e8
commit
5d762dcb39
@ -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 });
|
||||
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -29,6 +29,7 @@ export interface IFeatureSearchParams {
|
||||
tag?: string[][];
|
||||
status?: string[][];
|
||||
offset: number;
|
||||
favoritesFirst?: boolean;
|
||||
limit: number;
|
||||
sortBy: string;
|
||||
sortOrder: 'asc' | 'desc';
|
||||
|
@ -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<
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user