From 2e75b09b790b70cdbe973e1043633b32c42ddbd8 Mon Sep 17 00:00:00 2001 From: sjaanus Date: Tue, 29 Oct 2024 10:20:40 +0200 Subject: [PATCH 1/6] feat: archived features can be searched now --- .../src/component/filter/Filters/Filters.tsx | 2 - .../ProjectFeatureToggles.tsx | 1 + .../ProjectOverviewFilters.tsx | 8 ++++ .../useProjectFeatureSearch.ts | 1 + .../feature-search-controller.ts | 3 ++ .../feature-search/feature-search-service.ts | 8 ---- .../feature-search/feature-search-store.ts | 4 +- .../feature-search/feature.search.e2e.test.ts | 45 ++++++++++++++++++- .../feature-toggle-strategies-store-type.ts | 1 + .../spec/feature-search-query-parameters.ts | 10 +++++ 10 files changed, 69 insertions(+), 14 deletions(-) diff --git a/frontend/src/component/filter/Filters/Filters.tsx b/frontend/src/component/filter/Filters/Filters.tsx index 472e22145f2d..85c2251b4246 100644 --- a/frontend/src/component/filter/Filters/Filters.tsx +++ b/frontend/src/component/filter/Filters/Filters.tsx @@ -126,7 +126,6 @@ export const Filters: FC = ({ if (!filter) { return null; } - const label = ( <> @@ -151,7 +150,6 @@ export const Filters: FC = ({ /> ); } - return ( = ({ singularOperators: ['IS', 'IS_NOT'], pluralOperators: ['IS_ANY_OF', 'IS_NONE_OF'], }, + { + label: 'Show only archived', + icon: 'inventory', + options: [{ label: 'True', value: 'true' }], + filterKey: 'archived', + singularOperators: ['IS'], + pluralOperators: ['IS_ANY_OF'], + }, ]; setAvailableFilters(availableFilters); diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/useProjectFeatureSearch.ts b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/useProjectFeatureSearch.ts index 500a02e23410..29976c741f53 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/useProjectFeatureSearch.ts +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/useProjectFeatureSearch.ts @@ -40,6 +40,7 @@ export const useProjectFeatureSearch = ( createdAt: FilterItemParam, type: FilterItemParam, createdBy: FilterItemParam, + archived: FilterItemParam, }; const [tableState, setTableState] = usePersistentTableState( `${storageKey}-${projectId}`, diff --git a/src/lib/features/feature-search/feature-search-controller.ts b/src/lib/features/feature-search/feature-search-controller.ts index a29889cfdffb..a82ca6bde275 100644 --- a/src/lib/features/feature-search/feature-search-controller.ts +++ b/src/lib/features/feature-search/feature-search-controller.ts @@ -102,6 +102,7 @@ export default class FeatureSearchController extends Controller { state, status, favoritesFirst, + archived, sortBy, } = req.query; const userId = req.user.id; @@ -131,6 +132,7 @@ export default class FeatureSearchController extends Controller { ['enabled', 'disabled'].includes(tag[1]), ); const normalizedFavoritesFirst = favoritesFirst === 'true'; + const normalizedArchived = archived === 'IS:true'; const { features, total } = await this.featureSearchService.search({ searchParams: normalizedQuery, project, @@ -147,6 +149,7 @@ export default class FeatureSearchController extends Controller { limit: normalizedLimit, sortOrder: normalizedSortOrder, favoritesFirst: normalizedFavoritesFirst, + archived: normalizedArchived, }); this.openApiService.respondWithValidation( diff --git a/src/lib/features/feature-search/feature-search-service.ts b/src/lib/features/feature-search/feature-search-service.ts index a07d860b0daf..e988dc4e7768 100644 --- a/src/lib/features/feature-search/feature-search-service.ts +++ b/src/lib/features/feature-search/feature-search-service.ts @@ -60,14 +60,6 @@ export class FeatureSearchService { if (parsed) queryParams.push(parsed); } - if (params.createdBy) { - const parsed = parseSearchOperatorValue( - 'users.id', - params.createdBy, - ); - if (parsed) queryParams.push(parsed); - } - if (params.type) { const parsed = parseSearchOperatorValue( 'features.type', diff --git a/src/lib/features/feature-search/feature-search-store.ts b/src/lib/features/feature-search/feature-search-store.ts index 76bfc84deb2b..bf3fd577c1a7 100644 --- a/src/lib/features/feature-search/feature-search-store.ts +++ b/src/lib/features/feature-search/feature-search-store.ts @@ -101,6 +101,7 @@ class FeatureSearchStore implements IFeatureSearchStore { limit, sortOrder, sortBy, + archived, favoritesFirst, }: IFeatureSearchParams, queryParams: IQueryParam[], @@ -188,9 +189,8 @@ class FeatureSearchStore implements IFeatureSearchStore { } }); } - query - .modify(FeatureToggleStore.filterByArchived, false) + .modify(FeatureToggleStore.filterByArchived, archived) .leftJoin( 'feature_environments', 'feature_environments.feature_name', diff --git a/src/lib/features/feature-search/feature.search.e2e.test.ts b/src/lib/features/feature-search/feature.search.e2e.test.ts index 34f116daef5c..73b98839ccd6 100644 --- a/src/lib/features/feature-search/feature.search.e2e.test.ts +++ b/src/lib/features/feature-search/feature.search.e2e.test.ts @@ -62,11 +62,17 @@ beforeEach(async () => { }); const searchFeatures = async ( - { query = '', project = 'IS:default' }: FeatureSearchQueryParameters, + { + query = '', + project = 'IS:default', + archived = 'IS:false', + }: FeatureSearchQueryParameters, expectedCode = 200, ) => { 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); }; @@ -1128,3 +1134,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', + }, + ], + }); +}); diff --git a/src/lib/features/feature-toggle/types/feature-toggle-strategies-store-type.ts b/src/lib/features/feature-toggle/types/feature-toggle-strategies-store-type.ts index 6e9cc96b295a..6998dc1d0215 100644 --- a/src/lib/features/feature-toggle/types/feature-toggle-strategies-store-type.ts +++ b/src/lib/features/feature-toggle/types/feature-toggle-strategies-store-type.ts @@ -33,6 +33,7 @@ export interface IFeatureSearchParams { status?: string[][]; offset: number; favoritesFirst?: boolean; + archived?: boolean; limit: number; sortBy?: string; sortOrder: 'asc' | 'desc'; diff --git a/src/lib/openapi/spec/feature-search-query-parameters.ts b/src/lib/openapi/spec/feature-search-query-parameters.ts index 0de66e97ec88..f2e618c199f7 100644 --- a/src/lib/openapi/spec/feature-search-query-parameters.ts +++ b/src/lib/openapi/spec/feature-search-query-parameters.ts @@ -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.', in: 'query', }, + { + name: 'archived', + schema: { + type: 'string', + example: 'true', + }, + description: + 'The flag to filter for archived features. Set to true to retrieve only archived features; by default, it is set to false.', + in: 'query', + }, { name: 'createdAt', schema: { From a9be28ec53fde3e5ce04a44010ce8faf6b01b4ce Mon Sep 17 00:00:00 2001 From: sjaanus Date: Tue, 29 Oct 2024 10:22:54 +0200 Subject: [PATCH 2/6] Fixes --- .../feature-search/feature-search-service.ts | 8 +++++++ .../feature-search/feature.search.e2e.test.ts | 21 ++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/lib/features/feature-search/feature-search-service.ts b/src/lib/features/feature-search/feature-search-service.ts index e988dc4e7768..a07d860b0daf 100644 --- a/src/lib/features/feature-search/feature-search-service.ts +++ b/src/lib/features/feature-search/feature-search-service.ts @@ -60,6 +60,14 @@ export class FeatureSearchService { if (parsed) queryParams.push(parsed); } + if (params.createdBy) { + const parsed = parseSearchOperatorValue( + 'users.id', + params.createdBy, + ); + if (parsed) queryParams.push(parsed); + } + if (params.type) { const parsed = parseSearchOperatorValue( 'features.type', diff --git a/src/lib/features/feature-search/feature.search.e2e.test.ts b/src/lib/features/feature-search/feature.search.e2e.test.ts index 73b98839ccd6..396010cc2278 100644 --- a/src/lib/features/feature-search/feature.search.e2e.test.ts +++ b/src/lib/features/feature-search/feature.search.e2e.test.ts @@ -57,6 +57,7 @@ afterAll(async () => { }); beforeEach(async () => { + await db.stores.dependentFeaturesStore.deleteAll(); await db.stores.featureToggleStore.deleteAll(); await db.stores.segmentStore.deleteAll(); }); @@ -1146,16 +1147,16 @@ test('should return archived when query param set', async () => { archived: true, }); - // const { body } = await searchFeatures({ - // query: 'my_feature', - // }); - // expect(body).toMatchObject({ - // features: [ - // { - // name: 'my_feature_a', - // } - // ], - // }); + const { body } = await searchFeatures({ + query: 'my_feature', + }); + expect(body).toMatchObject({ + features: [ + { + name: 'my_feature_a', + }, + ], + }); const { body: archivedFeatures } = await searchFeatures({ query: 'my_feature', From 361f0ca841f638521c558045bbc7c896af5c1007 Mon Sep 17 00:00:00 2001 From: sjaanus Date: Tue, 29 Oct 2024 10:23:14 +0200 Subject: [PATCH 3/6] Fixes --- frontend/src/component/filter/Filters/Filters.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/component/filter/Filters/Filters.tsx b/frontend/src/component/filter/Filters/Filters.tsx index 85c2251b4246..472e22145f2d 100644 --- a/frontend/src/component/filter/Filters/Filters.tsx +++ b/frontend/src/component/filter/Filters/Filters.tsx @@ -126,6 +126,7 @@ export const Filters: FC = ({ if (!filter) { return null; } + const label = ( <> @@ -150,6 +151,7 @@ export const Filters: FC = ({ /> ); } + return ( Date: Tue, 29 Oct 2024 10:42:33 +0200 Subject: [PATCH 4/6] Fix --- .../ProjectOverviewFilters.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOverviewFilters.tsx b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOverviewFilters.tsx index ffce3a6f89fd..0bbb2da2d3d6 100644 --- a/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOverviewFilters.tsx +++ b/frontend/src/component/project/Project/PaginatedProjectFeatureToggles/ProjectOverviewFilters.tsx @@ -95,14 +95,6 @@ export const ProjectOverviewFilters: VFC = ({ singularOperators: ['IS', 'IS_NOT'], pluralOperators: ['IS_ANY_OF', 'IS_NONE_OF'], }, - { - label: 'Show only archived', - icon: 'inventory', - options: [{ label: 'True', value: 'true' }], - filterKey: 'archived', - singularOperators: ['IS'], - pluralOperators: ['IS_ANY_OF'], - }, ]; setAvailableFilters(availableFilters); From 4196351391f69a903351ff2a6ce5d1889db35d86 Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Tue, 29 Oct 2024 10:55:35 +0200 Subject: [PATCH 5/6] Update src/lib/openapi/spec/feature-search-query-parameters.ts Co-authored-by: Thomas Heartman --- src/lib/openapi/spec/feature-search-query-parameters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/openapi/spec/feature-search-query-parameters.ts b/src/lib/openapi/spec/feature-search-query-parameters.ts index f2e618c199f7..6cbd350544c8 100644 --- a/src/lib/openapi/spec/feature-search-query-parameters.ts +++ b/src/lib/openapi/spec/feature-search-query-parameters.ts @@ -153,7 +153,7 @@ export const featureSearchQueryParameters = [ example: 'true', }, description: - 'The flag to filter for archived features. Set to true to retrieve only archived features; by default, it is set to false.', + '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', }, { From 3865ad16175daa49db6e234bb3fc39fe597beda2 Mon Sep 17 00:00:00 2001 From: sjaanus Date: Tue, 29 Oct 2024 11:15:45 +0200 Subject: [PATCH 6/6] Fix --- src/lib/openapi/spec/feature-search-query-parameters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/openapi/spec/feature-search-query-parameters.ts b/src/lib/openapi/spec/feature-search-query-parameters.ts index f2e618c199f7..0c84705b64c6 100644 --- a/src/lib/openapi/spec/feature-search-query-parameters.ts +++ b/src/lib/openapi/spec/feature-search-query-parameters.ts @@ -150,7 +150,7 @@ export const featureSearchQueryParameters = [ name: 'archived', schema: { type: 'string', - example: 'true', + example: 'IS:true', }, description: 'The flag to filter for archived features. Set to true to retrieve only archived features; by default, it is set to false.',