1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: feature search basic functionality (#5150)

This commit is contained in:
Mateusz Kwasniewski 2023-10-25 16:12:21 +02:00 committed by GitHub
parent 3ee250ee7d
commit de540e09f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 79 additions and 21 deletions

View File

@ -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(

View File

@ -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
}
}

View File

@ -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: [] });
});

View 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
>;

View File

@ -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';