Skip to content

Commit

Permalink
initial draft
Browse files Browse the repository at this point in the history
  • Loading branch information
pgayvallet committed Dec 7, 2020
1 parent 446390d commit 7776444
Show file tree
Hide file tree
Showing 28 changed files with 304 additions and 41 deletions.
28 changes: 20 additions & 8 deletions src/plugins/saved_objects_tagging_oss/public/api.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
*/

import { ITagsClient } from '../common';
import { SavedObjectsTaggingApiUi, SavedObjectsTaggingApiUiComponent } from './api';
import { SavedObjectsTaggingApiUi, SavedObjectsTaggingApiUiComponent, ITagsCache } from './api';

const createClientMock = (): jest.Mocked<ITagsClient> => {
const mock = {
const createClientMock = () => {
const mock: jest.Mocked<ITagsClient> = {
create: jest.fn(),
get: jest.fn(),
getAll: jest.fn(),
Expand All @@ -32,14 +32,25 @@ const createClientMock = (): jest.Mocked<ITagsClient> => {
return mock;
};

const createCacheMock = () => {
const mock: jest.Mocked<ITagsCache> = {
getState: jest.fn(),
getState$: jest.fn(),
};

return mock;
};

interface SavedObjectsTaggingApiMock {
client: jest.Mocked<ITagsClient>;
cache: jest.Mocked<ITagsCache>;
ui: SavedObjectsTaggingApiUiMock;
}

const createApiMock = (): SavedObjectsTaggingApiMock => {
const mock = {
const mock: SavedObjectsTaggingApiMock = {
client: createClientMock(),
cache: createCacheMock(),
ui: createApiUiMock(),
};

Expand All @@ -50,8 +61,8 @@ type SavedObjectsTaggingApiUiMock = Omit<jest.Mocked<SavedObjectsTaggingApiUi>,
components: SavedObjectsTaggingApiUiComponentMock;
};

const createApiUiMock = (): SavedObjectsTaggingApiUiMock => {
const mock = {
const createApiUiMock = () => {
const mock: SavedObjectsTaggingApiUiMock = {
components: createApiUiComponentsMock(),
// TS is very picky with type guards
hasTagDecoration: jest.fn() as any,
Expand All @@ -69,8 +80,8 @@ const createApiUiMock = (): SavedObjectsTaggingApiUiMock => {

type SavedObjectsTaggingApiUiComponentMock = jest.Mocked<SavedObjectsTaggingApiUiComponent>;

const createApiUiComponentsMock = (): SavedObjectsTaggingApiUiComponentMock => {
const mock = {
const createApiUiComponentsMock = () => {
const mock: SavedObjectsTaggingApiUiComponentMock = {
TagList: jest.fn(),
TagSelector: jest.fn(),
SavedObjectSaveModalTagSelector: jest.fn(),
Expand All @@ -82,6 +93,7 @@ const createApiUiComponentsMock = (): SavedObjectsTaggingApiUiComponentMock => {
export const taggingApiMock = {
create: createApiMock,
createClient: createClientMock,
createCache: createCacheMock,
createUi: createApiUiMock,
createComponents: createApiUiComponentsMock,
};
22 changes: 21 additions & 1 deletion src/plugins/saved_objects_tagging_oss/public/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,42 @@
* under the License.
*/

import { Observable } from 'rxjs';
import { SearchFilterConfig, EuiTableFieldDataColumnType } from '@elastic/eui';
import type { FunctionComponent } from 'react';
import { SavedObject, SavedObjectReference } from '../../../core/types';
import { SavedObjectsFindOptionsReference } from '../../../core/public';
import { SavedObject as SavedObjectClass } from '../../saved_objects/public';
import { TagDecoratedSavedObject } from './decorator';
import { ITagsClient } from '../common';
import { ITagsClient, Tag } from '../common';

/**
* @public
*/
export interface SavedObjectsTaggingApi {
/**
* The client to perform tag-related operations on the server-side
*/
client: ITagsClient;
/**
* A client-side auto-refreshing cache of the existing tags. Can be used
* to synchronously access the list of tags.
*/
cache: ITagsCache;
/**
* UI API to use to add tagging capabilities to an application
*/
ui: SavedObjectsTaggingApiUi;
}

/**
* @public
*/
export interface ITagsCache {
getState(): Tag[];
getState$(): Observable<Tag[]>;
}

/**
* @public
*/
Expand Down
1 change: 1 addition & 0 deletions src/plugins/saved_objects_tagging_oss/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export {
SavedObjectsTaggingApi,
SavedObjectsTaggingApiUi,
SavedObjectsTaggingApiUiComponent,
ITagsCache,
TagListComponentProps,
TagSelectorComponentProps,
GetSearchBarFilterOptions,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/global_search/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const createStartMock = (): jest.Mocked<GlobalSearchPluginStart> => {

return {
find: searchMock.find,
getSearchableTypes: searchMock.getSearchableTypes,
};
};

Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/global_search/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,14 @@ export class GlobalSearchPlugin

start({ http }: CoreStart, { licensing }: GlobalSearchPluginStartDeps) {
this.licenseChecker = new LicenseChecker(licensing.license$);
const { find } = this.searchService.start({
const { find, getSearchableTypes } = this.searchService.start({
http,
licenseChecker: this.licenseChecker,
});

return {
find,
getSearchableTypes,
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { HttpStart } from 'src/core/public';

interface ServerSearchableTypesResponse {
types: string[];
}

export const fetchServerSearchableTypes = async (http: HttpStart) => {
const { types } = await http.get<ServerSearchableTypesResponse>(
'/internal/global_search/searchable_types'
);
return types;
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ const createSetupMock = (): jest.Mocked<SearchServiceSetup> => {
const createStartMock = (): jest.Mocked<SearchServiceStart> => {
const mock = {
find: jest.fn(),
getSearchableTypes: jest.fn(),
};
mock.find.mockReturnValue(of({ results: [] }));
mock.getSearchableTypes.mockReturnValue(of([]));

return mock;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ describe('SearchService', () => {
): jest.Mocked<GlobalSearchResultProvider> => ({
id,
find: jest.fn().mockImplementation((term, options, context) => source),
getSearchableTypes: jest.fn().mockReturnValue(['test-type']),
});

const expectedResult = (id: string) => expect.objectContaining({ id });
Expand Down
24 changes: 24 additions & 0 deletions x-pack/plugins/global_search/public/services/search_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { merge, Observable, timer, throwError } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { uniq } from 'lodash';
import { duration } from 'moment';
import { i18n } from '@kbn/i18n';
import { HttpStart } from 'src/core/public';
Expand All @@ -24,6 +25,7 @@ import { GlobalSearchClientConfigType } from '../config';
import { GlobalSearchFindOptions } from './types';
import { getDefaultPreference } from './utils';
import { fetchServerResults } from './fetch_server_results';
import { fetchServerSearchableTypes } from './fetch_server_searchable_types';

/** @public */
export interface SearchServiceSetup {
Expand Down Expand Up @@ -75,6 +77,11 @@ export interface SearchServiceStart {
params: GlobalSearchFindParams,
options: GlobalSearchFindOptions
): Observable<GlobalSearchBatchedResults>;

/**
* Returns all the searchable types registered by the underlying result providers.
*/
getSearchableTypes(): Promise<string[]>;
}

interface SetupDeps {
Expand All @@ -96,6 +103,7 @@ export class SearchService {
private http?: HttpStart;
private maxProviderResults = defaultMaxProviderResults;
private licenseChecker?: ILicenseChecker;
private serverTypes?: string[];

setup({ config, maxProviderResults = defaultMaxProviderResults }: SetupDeps): SearchServiceSetup {
this.config = config;
Expand All @@ -118,9 +126,25 @@ export class SearchService {

return {
find: (params, options) => this.performFind(params, options),
getSearchableTypes: () => this.getSearchableTypes(),
};
}

private async getSearchableTypes() {
const providerTypes = (
await Promise.all(
[...this.providers.values()].map((provider) => provider.getSearchableTypes())
)
).flat();

// only need to fetch from server once
if (!this.serverTypes) {
this.serverTypes = await fetchServerSearchableTypes(this.http!);
}

return uniq([...providerTypes, ...this.serverTypes]);
}

private performFind(params: GlobalSearchFindParams, options: GlobalSearchFindOptions) {
const licenseState = this.licenseChecker!.getState();
if (!licenseState.valid) {
Expand Down
8 changes: 7 additions & 1 deletion x-pack/plugins/global_search/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { SearchServiceSetup, SearchServiceStart } from './services';

export type GlobalSearchPluginSetup = Pick<SearchServiceSetup, 'registerResultProvider'>;
export type GlobalSearchPluginStart = Pick<SearchServiceStart, 'find'>;
export type GlobalSearchPluginStart = Pick<SearchServiceStart, 'find' | 'getSearchableTypes'>;

/**
* GlobalSearch result provider, to be registered using the {@link GlobalSearchPluginSetup | global search API}
Expand Down Expand Up @@ -44,4 +44,10 @@ export interface GlobalSearchResultProvider {
search: GlobalSearchProviderFindParams,
options: GlobalSearchProviderFindOptions
): Observable<GlobalSearchProviderResult[]>;

/**
* Method that should return all the possible {@link GlobalSearchProviderResult.type | type} of results that
* this provider can return.
*/
getSearchableTypes: () => string[] | Promise<string[]>;
}
2 changes: 2 additions & 0 deletions x-pack/plugins/global_search/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ const createStartMock = (): jest.Mocked<GlobalSearchPluginStart> => {

return {
find: searchMock.find,
getSearchableTypes: searchMock.getSearchableTypes,
};
};

const createRouteHandlerContextMock = (): jest.Mocked<RouteHandlerGlobalSearchContext> => {
const handlerContextMock = {
find: jest.fn(),
getSearchableTypes: jest.fn(),
};

handlerContextMock.find.mockReturnValue(of([]));
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/global_search/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export class GlobalSearchPlugin
core.http.registerRouteHandlerContext('globalSearch', (_, req) => {
return {
find: (term, options) => this.searchServiceStart!.find(term, options, req),
getSearchableTypes: () => this.searchServiceStart!.getSearchableTypes(req),
};
});

Expand All @@ -75,6 +76,7 @@ export class GlobalSearchPlugin
});
return {
find: this.searchServiceStart.find,
getSearchableTypes: this.searchServiceStart.getSearchableTypes,
};
}

Expand Down
24 changes: 24 additions & 0 deletions x-pack/plugins/global_search/server/routes/get_searchable_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { IRouter } from 'src/core/server';

export const registerInternalSearchableTypesRoute = (router: IRouter) => {
router.get(
{
path: '/internal/global_search/searchable_types',
validate: false,
},
async (ctx, req, res) => {
const types = await ctx.globalSearch!.getSearchableTypes();
return res.ok({
body: {
types,
},
});
}
);
};
10 changes: 8 additions & 2 deletions x-pack/plugins/global_search/server/routes/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,21 @@ describe('registerRoutes', () => {
registerRoutes(router);

expect(router.post).toHaveBeenCalledTimes(1);

expect(router.post).toHaveBeenCalledWith(
expect.objectContaining({
path: '/internal/global_search/find',
}),
expect.any(Function)
);

expect(router.get).toHaveBeenCalledTimes(0);
expect(router.get).toHaveBeenCalledTimes(1);
expect(router.get).toHaveBeenCalledWith(
expect.objectContaining({
path: '/internal/global_search/searchable_types',
}),
expect.any(Function)
);

expect(router.delete).toHaveBeenCalledTimes(0);
expect(router.put).toHaveBeenCalledTimes(0);
});
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/global_search/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

import { IRouter } from 'src/core/server';
import { registerInternalFindRoute } from './find';
import { registerInternalSearchableTypesRoute } from './get_searchable_types';

export const registerRoutes = (router: IRouter) => {
registerInternalFindRoute(router);
registerInternalSearchableTypesRoute(router);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ const createSetupMock = (): jest.Mocked<SearchServiceSetup> => {
const createStartMock = (): jest.Mocked<SearchServiceStart> => {
const mock = {
find: jest.fn(),
getSearchableTypes: jest.fn(),
};
mock.find.mockReturnValue(of({ results: [] }));
mock.getSearchableTypes.mockReturnValue(of([]));

return mock;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('SearchService', () => {
): jest.Mocked<GlobalSearchResultProvider> => ({
id,
find: jest.fn().mockImplementation((term, options, context) => source),
getSearchableTypes: jest.fn().mockReturnValue(['test-type']),
});

const expectedResult = (id: string) => expect.objectContaining({ id });
Expand Down
Loading

0 comments on commit 7776444

Please sign in to comment.