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:
parent
a5d304ca51
commit
3ee250ee7d
@ -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,
|
||||||
|
};
|
||||||
|
};
|
@ -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,
|
||||||
|
);
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import Controller from '../../routes/controller';
|
import Controller from '../../routes/controller';
|
||||||
import { OpenApiService } from '../../services';
|
import { FeatureSearchService, OpenApiService } from '../../services';
|
||||||
import {
|
import {
|
||||||
IFlagResolver,
|
IFlagResolver,
|
||||||
IUnleashConfig,
|
IUnleashConfig,
|
||||||
@ -19,22 +19,28 @@ interface ISearchQueryParams {
|
|||||||
|
|
||||||
const PATH = '/features';
|
const PATH = '/features';
|
||||||
|
|
||||||
type FeatureSearchServices = Pick<IUnleashServices, 'openApiService'>;
|
type FeatureSearchServices = Pick<
|
||||||
|
IUnleashServices,
|
||||||
|
'openApiService' | 'featureSearchService'
|
||||||
|
>;
|
||||||
|
|
||||||
export default class FeatureSearchController extends Controller {
|
export default class FeatureSearchController extends Controller {
|
||||||
private openApiService: OpenApiService;
|
private openApiService: OpenApiService;
|
||||||
|
|
||||||
private flagResolver: IFlagResolver;
|
private flagResolver: IFlagResolver;
|
||||||
|
|
||||||
|
private featureSearchService: FeatureSearchService;
|
||||||
|
|
||||||
private readonly logger: Logger;
|
private readonly logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
{ openApiService }: FeatureSearchServices,
|
{ openApiService, featureSearchService }: FeatureSearchServices,
|
||||||
) {
|
) {
|
||||||
super(config);
|
super(config);
|
||||||
this.openApiService = openApiService;
|
this.openApiService = openApiService;
|
||||||
this.flagResolver = config.flagResolver;
|
this.flagResolver = config.flagResolver;
|
||||||
|
this.featureSearchService = featureSearchService;
|
||||||
this.logger = config.getLogger(
|
this.logger = config.getLogger(
|
||||||
'/feature-search/feature-search-controller.ts',
|
'/feature-search/feature-search-controller.ts',
|
||||||
);
|
);
|
||||||
@ -66,7 +72,11 @@ export default class FeatureSearchController extends Controller {
|
|||||||
const { query, tags } = req.query;
|
const { query, tags } = req.query;
|
||||||
|
|
||||||
if (this.config.flagResolver.isEnabled('featureSearchAPI')) {
|
if (this.config.flagResolver.isEnabled('featureSearchAPI')) {
|
||||||
res.json({ features: [] });
|
const features = await this.featureSearchService.search(
|
||||||
|
query,
|
||||||
|
tags,
|
||||||
|
);
|
||||||
|
res.json({ features });
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidOperationError(
|
throw new InvalidOperationError(
|
||||||
'Feature Search API is not enabled',
|
'Feature Search API is not enabled',
|
||||||
|
29
src/lib/features/feature-search/feature-search-service.ts
Normal file
29
src/lib/features/feature-search/feature-search-service.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -99,6 +99,11 @@ import {
|
|||||||
createFakeClientFeatureToggleService,
|
createFakeClientFeatureToggleService,
|
||||||
} from '../features/client-feature-toggles/createClientFeatureToggleService';
|
} from '../features/client-feature-toggles/createClientFeatureToggleService';
|
||||||
import { ClientFeatureToggleService } from '../features/client-feature-toggles/client-feature-toggle-service';
|
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
|
// TODO: will be moved to scheduler feature directory
|
||||||
export const scheduleServices = async (
|
export const scheduleServices = async (
|
||||||
@ -336,6 +341,10 @@ export const createServices = (
|
|||||||
: withFakeTransactional(createFakeDependentFeaturesService(config));
|
: withFakeTransactional(createFakeDependentFeaturesService(config));
|
||||||
const dependentFeaturesService = transactionalDependentFeaturesService;
|
const dependentFeaturesService = transactionalDependentFeaturesService;
|
||||||
|
|
||||||
|
const featureSearchService = db
|
||||||
|
? createFeatureSearchService(config)(db)
|
||||||
|
: createFakeFeatureSearchService(config);
|
||||||
|
|
||||||
const featureToggleServiceV2 = new FeatureToggleService(
|
const featureToggleServiceV2 = new FeatureToggleService(
|
||||||
stores,
|
stores,
|
||||||
config,
|
config,
|
||||||
@ -483,6 +492,7 @@ export const createServices = (
|
|||||||
dependentFeaturesService,
|
dependentFeaturesService,
|
||||||
transactionalDependentFeaturesService,
|
transactionalDependentFeaturesService,
|
||||||
clientFeatureToggleService,
|
clientFeatureToggleService,
|
||||||
|
featureSearchService,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -528,4 +538,5 @@ export {
|
|||||||
SchedulerService,
|
SchedulerService,
|
||||||
DependentFeaturesService,
|
DependentFeaturesService,
|
||||||
ClientFeatureToggleService,
|
ClientFeatureToggleService,
|
||||||
|
FeatureSearchService,
|
||||||
};
|
};
|
||||||
|
@ -50,6 +50,7 @@ import { IPrivateProjectChecker } from '../features/private-project/privateProje
|
|||||||
import { DependentFeaturesService } from '../features/dependent-features/dependent-features-service';
|
import { DependentFeaturesService } from '../features/dependent-features/dependent-features-service';
|
||||||
import { WithTransactional } from 'lib/db/transaction';
|
import { WithTransactional } from 'lib/db/transaction';
|
||||||
import { ClientFeatureToggleService } from 'lib/features/client-feature-toggles/client-feature-toggle-service';
|
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 {
|
export interface IUnleashServices {
|
||||||
accessService: AccessService;
|
accessService: AccessService;
|
||||||
@ -107,4 +108,5 @@ export interface IUnleashServices {
|
|||||||
dependentFeaturesService: DependentFeaturesService;
|
dependentFeaturesService: DependentFeaturesService;
|
||||||
transactionalDependentFeaturesService: WithTransactional<DependentFeaturesService>;
|
transactionalDependentFeaturesService: WithTransactional<DependentFeaturesService>;
|
||||||
clientFeatureToggleService: ClientFeatureToggleService;
|
clientFeatureToggleService: ClientFeatureToggleService;
|
||||||
|
featureSearchService: FeatureSearchService;
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ process.nextTick(async () => {
|
|||||||
separateAdminClientApi: true,
|
separateAdminClientApi: true,
|
||||||
playgroundImprovements: true,
|
playgroundImprovements: true,
|
||||||
featureSwitchRefactor: true,
|
featureSwitchRefactor: true,
|
||||||
|
featureSearchAPI: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authentication: {
|
authentication: {
|
||||||
|
Loading…
Reference in New Issue
Block a user