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

feat: add feature search service (#5149)

This commit is contained in:
Fredrik Strand Oseberg 2023-10-25 15:18:52 +02:00 committed by GitHub
parent a5d304ca51
commit 3ee250ee7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 150 additions and 4 deletions

View File

@ -0,0 +1,58 @@
import useSWR, { SWRConfiguration } from 'swr';
import { useCallback } from 'react';
import { IFeatureToggleListItem } from 'interfaces/featureToggle';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
type IFeatureSearchResponse = { features: IFeatureToggleListItem[] };
interface IUseFeatureSearchOutput {
features: IFeatureSearchResponse;
loading: boolean;
error: string;
refetch: () => void;
}
const fallbackFeatures: { features: IFeatureToggleListItem[] } = {
features: [],
};
export const useFeatureSearch = (
options: SWRConfiguration = {},
): IUseFeatureSearchOutput => {
const { KEY, fetcher } = getFeatureSearchFetcher();
const { data, error, mutate } = useSWR<IFeatureSearchResponse>(
KEY,
fetcher,
options,
);
const refetch = useCallback(() => {
mutate();
}, [mutate]);
return {
features: data || fallbackFeatures,
loading: !error && !data,
error,
refetch,
};
};
const getFeatureSearchFetcher = () => {
const fetcher = () => {
const path = formatApiPath(`api/admin/search/features`);
return fetch(path, {
method: 'GET',
})
.then(handleErrorResponses('Feature search'))
.then((res) => res.json());
};
const KEY = `api/admin/search/features`;
return {
fetcher,
KEY,
};
};

View File

@ -0,0 +1,35 @@
import { Db } from '../../db/db';
import { IUnleashConfig } from '../../types';
import FeatureStrategiesStore from '../feature-toggle/feature-toggle-strategies-store';
import { FeatureSearchService } from './feature-search-service';
import FakeFeatureStrategiesStore from '../feature-toggle/fakes/fake-feature-strategies-store';
export const createFeatureSearchService =
(config: IUnleashConfig) => (db: Db): FeatureSearchService => {
const { getLogger, eventBus, flagResolver } = config;
const featureStrategiesStore = new FeatureStrategiesStore(
db,
eventBus,
getLogger,
flagResolver,
);
return new FeatureSearchService(
{ featureStrategiesStore: featureStrategiesStore },
config,
);
};
export const createFakeFeatureSearchService = (
config: IUnleashConfig,
): FeatureSearchService => {
const fakeFeatureStrategiesStore = new FakeFeatureStrategiesStore();
return new FeatureSearchService(
{
featureStrategiesStore: fakeFeatureStrategiesStore,
},
config,
);
};

View File

@ -1,6 +1,6 @@
import { Response } from 'express';
import Controller from '../../routes/controller';
import { OpenApiService } from '../../services';
import { FeatureSearchService, OpenApiService } from '../../services';
import {
IFlagResolver,
IUnleashConfig,
@ -19,22 +19,28 @@ interface ISearchQueryParams {
const PATH = '/features';
type FeatureSearchServices = Pick<IUnleashServices, 'openApiService'>;
type FeatureSearchServices = Pick<
IUnleashServices,
'openApiService' | 'featureSearchService'
>;
export default class FeatureSearchController extends Controller {
private openApiService: OpenApiService;
private flagResolver: IFlagResolver;
private featureSearchService: FeatureSearchService;
private readonly logger: Logger;
constructor(
config: IUnleashConfig,
{ openApiService }: FeatureSearchServices,
{ openApiService, featureSearchService }: FeatureSearchServices,
) {
super(config);
this.openApiService = openApiService;
this.flagResolver = config.flagResolver;
this.featureSearchService = featureSearchService;
this.logger = config.getLogger(
'/feature-search/feature-search-controller.ts',
);
@ -66,7 +72,11 @@ export default class FeatureSearchController extends Controller {
const { query, tags } = req.query;
if (this.config.flagResolver.isEnabled('featureSearchAPI')) {
res.json({ features: [] });
const features = await this.featureSearchService.search(
query,
tags,
);
res.json({ features });
} else {
throw new InvalidOperationError(
'Feature Search API is not enabled',

View File

@ -0,0 +1,29 @@
import { Logger } from '../../logger';
import {
IFeatureStrategiesStore,
IUnleashConfig,
IUnleashStores,
} from '../../types';
export class FeatureSearchService {
private featureStrategiesStore: IFeatureStrategiesStore;
private logger: Logger;
constructor(
{
featureStrategiesStore,
}: Pick<IUnleashStores, 'featureStrategiesStore'>,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
) {
this.featureStrategiesStore = featureStrategiesStore;
this.logger = getLogger('services/feature-search-service.ts');
}
async search(query: string, tags: string[]) {
const features = await this.featureStrategiesStore.getFeatureOverview({
projectId: 'default',
});
return features;
// Search for features
}
}

View File

@ -99,6 +99,11 @@ import {
createFakeClientFeatureToggleService,
} from '../features/client-feature-toggles/createClientFeatureToggleService';
import { ClientFeatureToggleService } from '../features/client-feature-toggles/client-feature-toggle-service';
import {
createFeatureSearchService,
createFakeFeatureSearchService,
} from '../features/feature-search/createFeatureSearchService';
import { FeatureSearchService } from '../features/feature-search/feature-search-service';
// TODO: will be moved to scheduler feature directory
export const scheduleServices = async (
@ -336,6 +341,10 @@ export const createServices = (
: withFakeTransactional(createFakeDependentFeaturesService(config));
const dependentFeaturesService = transactionalDependentFeaturesService;
const featureSearchService = db
? createFeatureSearchService(config)(db)
: createFakeFeatureSearchService(config);
const featureToggleServiceV2 = new FeatureToggleService(
stores,
config,
@ -483,6 +492,7 @@ export const createServices = (
dependentFeaturesService,
transactionalDependentFeaturesService,
clientFeatureToggleService,
featureSearchService,
};
};
@ -528,4 +538,5 @@ export {
SchedulerService,
DependentFeaturesService,
ClientFeatureToggleService,
FeatureSearchService,
};

View File

@ -50,6 +50,7 @@ import { IPrivateProjectChecker } from '../features/private-project/privateProje
import { DependentFeaturesService } from '../features/dependent-features/dependent-features-service';
import { WithTransactional } from 'lib/db/transaction';
import { ClientFeatureToggleService } from 'lib/features/client-feature-toggles/client-feature-toggle-service';
import { FeatureSearchService } from 'lib/features/feature-search/feature-search-service';
export interface IUnleashServices {
accessService: AccessService;
@ -107,4 +108,5 @@ export interface IUnleashServices {
dependentFeaturesService: DependentFeaturesService;
transactionalDependentFeaturesService: WithTransactional<DependentFeaturesService>;
clientFeatureToggleService: ClientFeatureToggleService;
featureSearchService: FeatureSearchService;
}

View File

@ -50,6 +50,7 @@ process.nextTick(async () => {
separateAdminClientApi: true,
playgroundImprovements: true,
featureSwitchRefactor: true,
featureSearchAPI: true,
},
},
authentication: {