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