Skip to content

Commit

Permalink
feat: feature search basic functionality (#5150)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Oct 25, 2023
1 parent 3ee250e commit de540e0
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 20 deletions.
20 changes: 8 additions & 12 deletions src/lib/features/feature-search/feature-search-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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),
Expand All @@ -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(
Expand Down
7 changes: 4 additions & 3 deletions src/lib/features/feature-search/feature-search-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
IUnleashConfig,
IUnleashStores,
} from '../../types';
import { FeatureSearchQueryParameters } from '../../openapi/spec/feature-search-query-parameters';

export class FeatureSearchService {
private featureStrategiesStore: IFeatureStrategiesStore;
Expand All @@ -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
}
}
42 changes: 37 additions & 5 deletions src/lib/features/feature-search/feature.search.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 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();
expect(body).toStrictEqual({ features: [] });
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 changes: 28 additions & 0 deletions src/lib/openapi/spec/feature-search-query-parameters.ts
Original file line number Diff line number Diff line change
@@ -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
>;
1 change: 1 addition & 0 deletions src/lib/openapi/spec/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

0 comments on commit de540e0

Please sign in to comment.