1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00

feat: archived features can be searched now (#8568)

Archived features can be searched now.
This is the backend and small parts of frontend preparing to add
filters, buttons etc in next PR.

---------

Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
Jaanus Sellin 2024-10-29 13:19:13 +02:00 committed by GitHub
parent 30c14ff995
commit 28e062b5cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 62 additions and 4 deletions

View File

@ -92,6 +92,7 @@ export const ProjectFeatureToggles = ({
type: tableState.type, type: tableState.type,
state: tableState.state, state: tableState.state,
createdBy: tableState.createdBy, createdBy: tableState.createdBy,
archived: tableState.archived,
}; };
const { favorite, unfavorite } = useFavoriteFeaturesApi(); const { favorite, unfavorite } = useFavoriteFeaturesApi();

View File

@ -40,6 +40,7 @@ export const useProjectFeatureSearch = (
createdAt: FilterItemParam, createdAt: FilterItemParam,
type: FilterItemParam, type: FilterItemParam,
createdBy: FilterItemParam, createdBy: FilterItemParam,
archived: FilterItemParam,
}; };
const [tableState, setTableState] = usePersistentTableState( const [tableState, setTableState] = usePersistentTableState(
`${storageKey}-${projectId}`, `${storageKey}-${projectId}`,

View File

@ -102,6 +102,7 @@ export default class FeatureSearchController extends Controller {
state, state,
status, status,
favoritesFirst, favoritesFirst,
archived,
sortBy, sortBy,
} = req.query; } = req.query;
const userId = req.user.id; const userId = req.user.id;
@ -131,6 +132,7 @@ export default class FeatureSearchController extends Controller {
['enabled', 'disabled'].includes(tag[1]), ['enabled', 'disabled'].includes(tag[1]),
); );
const normalizedFavoritesFirst = favoritesFirst === 'true'; const normalizedFavoritesFirst = favoritesFirst === 'true';
const normalizedArchived = archived === 'IS:true';
const { features, total } = await this.featureSearchService.search({ const { features, total } = await this.featureSearchService.search({
searchParams: normalizedQuery, searchParams: normalizedQuery,
project, project,
@ -147,6 +149,7 @@ export default class FeatureSearchController extends Controller {
limit: normalizedLimit, limit: normalizedLimit,
sortOrder: normalizedSortOrder, sortOrder: normalizedSortOrder,
favoritesFirst: normalizedFavoritesFirst, favoritesFirst: normalizedFavoritesFirst,
archived: normalizedArchived,
}); });
this.openApiService.respondWithValidation( this.openApiService.respondWithValidation(

View File

@ -101,6 +101,7 @@ class FeatureSearchStore implements IFeatureSearchStore {
limit, limit,
sortOrder, sortOrder,
sortBy, sortBy,
archived,
favoritesFirst, favoritesFirst,
}: IFeatureSearchParams, }: IFeatureSearchParams,
queryParams: IQueryParam[], queryParams: IQueryParam[],
@ -188,9 +189,8 @@ class FeatureSearchStore implements IFeatureSearchStore {
} }
}); });
} }
query query
.modify(FeatureToggleStore.filterByArchived, false) .modify(FeatureToggleStore.filterByArchived, archived)
.leftJoin( .leftJoin(
'feature_environments', 'feature_environments',
'feature_environments.feature_name', 'feature_environments.feature_name',

View File

@ -57,16 +57,23 @@ afterAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await db.stores.dependentFeaturesStore.deleteAll();
await db.stores.featureToggleStore.deleteAll(); await db.stores.featureToggleStore.deleteAll();
await db.stores.segmentStore.deleteAll(); await db.stores.segmentStore.deleteAll();
}); });
const searchFeatures = async ( const searchFeatures = async (
{ query = '', project = 'IS:default' }: FeatureSearchQueryParameters, {
query = '',
project = 'IS:default',
archived = 'IS:false',
}: FeatureSearchQueryParameters,
expectedCode = 200, expectedCode = 200,
) => { ) => {
return app.request return app.request
.get(`/api/admin/search/features?query=${query}&project=${project}`) .get(
`/api/admin/search/features?query=${query}&project=${project}&archived=${archived}`,
)
.expect(expectedCode); .expect(expectedCode);
}; };
@ -1128,3 +1135,38 @@ test('should return dependencyType', async () => {
], ],
}); });
}); });
test('should return archived when query param set', async () => {
await app.createFeature({
name: 'my_feature_a',
createdAt: '2023-01-29T15:21:39.975Z',
});
await app.createFeature({
name: 'my_feature_b',
createdAt: '2023-01-29T15:21:39.975Z',
archived: true,
});
const { body } = await searchFeatures({
query: 'my_feature',
});
expect(body).toMatchObject({
features: [
{
name: 'my_feature_a',
},
],
});
const { body: archivedFeatures } = await searchFeatures({
query: 'my_feature',
archived: 'IS:true',
});
expect(archivedFeatures).toMatchObject({
features: [
{
name: 'my_feature_b',
},
],
});
});

View File

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

View File

@ -146,6 +146,16 @@ export const featureSearchQueryParameters = [
'The flag to indicate if the favorite features should be returned first. By default it is set to false.', 'The flag to indicate if the favorite features should be returned first. By default it is set to false.',
in: 'query', in: 'query',
}, },
{
name: 'archived',
schema: {
type: 'string',
example: 'IS:true',
},
description:
'Whether to get results for archived feature flags or active feature flags. If `true`, Unleash will return only archived flags. If `false`, it will return only active flags.',
in: 'query',
},
{ {
name: 'createdAt', name: 'createdAt',
schema: { schema: {