mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: feature search basic functionality (#5150)
This commit is contained in:
		
							parent
							
								
									3ee250ee7d
								
							
						
					
					
						commit
						de540e09f3
					
				| @ -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<any, any, any, ISearchQueryParams>, | ||||
|         req: IAuthRequest<any, any, any, FeatureSearchQueryParameters>, | ||||
|         res: Response, | ||||
|     ): Promise<void> { | ||||
|         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( | ||||
|  | ||||
| @ -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
 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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<FeatureSearchQueryParameters>, | ||||
|     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: [] }); | ||||
| }); | ||||
|  | ||||
							
								
								
									
										28
									
								
								src/lib/openapi/spec/feature-search-query-parameters.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/lib/openapi/spec/feature-search-query-parameters.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| >; | ||||
| @ -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'; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user