From de540e09f3cfb45cecd31d4055a06c643861c875 Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Wed, 25 Oct 2023 16:12:21 +0200 Subject: [PATCH] feat: feature search basic functionality (#5150) --- .../feature-search-controller.ts | 20 ++++----- .../feature-search/feature-search-service.ts | 7 +-- .../feature-search/feature.search.e2e.test.ts | 44 ++++++++++++++++--- .../spec/feature-search-query-parameters.ts | 28 ++++++++++++ src/lib/openapi/spec/index.ts | 1 + 5 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 src/lib/openapi/spec/feature-search-query-parameters.ts diff --git a/src/lib/features/feature-search/feature-search-controller.ts b/src/lib/features/feature-search/feature-search-controller.ts index d17ed36c8b..6a514ffd42 100644 --- a/src/lib/features/feature-search/feature-search-controller.ts +++ b/src/lib/features/feature-search/feature-search-controller.ts @@ -11,11 +11,10 @@ import { Logger } from '../../logger'; import { createResponseSchema, getStandardResponses } from '../../openapi'; import { IAuthRequest } from '../../routes/unleash-types'; import { InvalidOperationError } from '../../error'; - -interface ISearchQueryParams { - query: string; - tags: string[]; -} +import { + FeatureSearchQueryParameters, + featureSearchQueryParameters, +} from '../../openapi/spec/feature-search-query-parameters'; const PATH = '/features'; @@ -56,6 +55,8 @@ export default class FeatureSearchController extends Controller { summary: 'Search and filter features', description: 'Search and filter by selected fields.', operationId: 'searchFeatures', + // TODO: fix the type + parameters: featureSearchQueryParameters as any, responses: { 200: createResponseSchema('searchFeaturesSchema'), ...getStandardResponses(401, 403, 404), @@ -66,16 +67,11 @@ export default class FeatureSearchController extends Controller { } async searchFeatures( - req: IAuthRequest, + req: IAuthRequest, res: Response, ): Promise { - const { query, tags } = req.query; - if (this.config.flagResolver.isEnabled('featureSearchAPI')) { - const features = await this.featureSearchService.search( - query, - tags, - ); + const features = await this.featureSearchService.search(req.query); res.json({ features }); } else { throw new InvalidOperationError( diff --git a/src/lib/features/feature-search/feature-search-service.ts b/src/lib/features/feature-search/feature-search-service.ts index b37b4d4cf5..70aa393012 100644 --- a/src/lib/features/feature-search/feature-search-service.ts +++ b/src/lib/features/feature-search/feature-search-service.ts @@ -4,6 +4,7 @@ import { IUnleashConfig, IUnleashStores, } from '../../types'; +import { FeatureSearchQueryParameters } from '../../openapi/spec/feature-search-query-parameters'; export class FeatureSearchService { private featureStrategiesStore: IFeatureStrategiesStore; @@ -18,12 +19,12 @@ export class FeatureSearchService { this.logger = getLogger('services/feature-search-service.ts'); } - async search(query: string, tags: string[]) { + async search(params: FeatureSearchQueryParameters) { const features = await this.featureStrategiesStore.getFeatureOverview({ - projectId: 'default', + projectId: params.projectId, + namePrefix: params.query, }); return features; - // Search for features } } 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 a3cc0b6f81..271ee943ec 100644 --- a/src/lib/features/feature-search/feature.search.e2e.test.ts +++ b/src/lib/features/feature-search/feature.search.e2e.test.ts @@ -4,6 +4,7 @@ import { setupAppWithCustomConfig, } from '../../../test/e2e/helpers/test-helper'; import getLogger from '../../../test/fixtures/no-logger'; +import { FeatureSearchQueryParameters } from '../../openapi/spec/feature-search-query-parameters'; let app: IUnleashTest; let db: ITestDb; @@ -29,13 +30,44 @@ afterAll(async () => { await db.destroy(); }); -beforeEach(async () => {}); +beforeEach(async () => { + await db.stores.featureToggleStore.deleteAll(); +}); -const searchFeatures = async (expectedCode = 200) => { - return app.request.get(`/api/admin/search/features`).expect(expectedCode); +const searchFeatures = async ( + { query, projectId = 'default' }: Partial, + expectedCode = 200, +) => { + return app.request + .get(`/api/admin/search/features?query=${query}&projectId=${projectId}`) + .expect(expectedCode); }; -test('should return empty features', async () => { - const { body } = await searchFeatures(); - expect(body).toStrictEqual({ features: [] }); +test('should return matching features', async () => { + await app.createFeature('my_feature_a'); + await app.createFeature('my_feature_b'); + await app.createFeature('my_feat_c'); + + const { body } = await searchFeatures({ query: 'my_feature' }); + + expect(body).toMatchObject({ + features: [{ name: 'my_feature_a' }, { name: 'my_feature_b' }], + }); +}); + +test('should return empty features', async () => { + const { body } = await searchFeatures({ query: '' }); + expect(body).toMatchObject({ features: [] }); +}); + +test('should not return features from another project', async () => { + await app.createFeature('my_feature_a'); + await app.createFeature('my_feature_b'); + + const { body } = await searchFeatures({ + query: '', + projectId: 'another_project', + }); + + expect(body).toMatchObject({ features: [] }); }); diff --git a/src/lib/openapi/spec/feature-search-query-parameters.ts b/src/lib/openapi/spec/feature-search-query-parameters.ts new file mode 100644 index 0000000000..e97ba4db80 --- /dev/null +++ b/src/lib/openapi/spec/feature-search-query-parameters.ts @@ -0,0 +1,28 @@ +import { FromQueryParams } from '../util/from-query-params'; + +export const featureSearchQueryParameters = [ + { + name: 'query', + schema: { + default: '', + type: 'string' as const, + example: 'feature_a', + }, + description: 'The search query for the feature or tag', + in: 'query', + }, + { + name: 'projectId', + schema: { + default: '', + type: 'string' as const, + example: 'default', + }, + description: 'Id of the project where search is performed', + in: 'query', + }, +] as const; + +export type FeatureSearchQueryParameters = FromQueryParams< + typeof featureSearchQueryParameters +>; diff --git a/src/lib/openapi/spec/index.ts b/src/lib/openapi/spec/index.ts index eabbb0421d..56b47d1b9b 100644 --- a/src/lib/openapi/spec/index.ts +++ b/src/lib/openapi/spec/index.ts @@ -167,3 +167,4 @@ export * from './feature-dependencies-schema'; export * from './dependencies-exist-schema'; export * from './validate-archive-features-schema'; export * from './search-features-schema'; +export * from './feature-search-query-parameters';