Skip to content

Commit c5546f4

Browse files
authored
Add globalSearch x-pack plugin (#66293)
* add skeleton for global_search plugin * base implementation of the server-side service * add utils tests * add server-side mocks * move take_in_array to common folder * implements base of client-side plugin * add tests for server-side service * fix server plugin tests * implement `navigateToUrl` core API * extract processResults for the client-side * fetch server results from the client side * factorize process_results * fix plugin start params * move things around * move all server types to single file * fix types imports * add basic FTR tests * add client-side service tests * add tests for addNavigate * add getDefaultPreference & tests * use optional for RequestHandlerContext * add registerRoutes test * add base test for context * resolve TODO * common nits/doc * common nits/doc on public * update CODEOWNERS * add import for declare statement * add license check on the server-side * add license check on the client-side * eslint * address some review comments * use properly typed errors for obs * add integration tests for the find endpoint * fix unit tests * use licensing start contract * translate the error message * fix eslint rule for test_utils * fix test_utils imports * remove NavigableGlobalSearchResult, use `application.navigateToUrl` instead. * use coreProvider plugin in FTR tests * nits * fix service start params * fix service start params, bis * I really need to fix this typecheck oom error * add README, update missing jsdoc * nits on doc
1 parent 3d2c3f1 commit c5546f4

File tree

81 files changed

+3294
-66
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+3294
-66
lines changed

.eslintrc.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ module.exports = {
225225
'!src/core/server/index.ts', // relative import
226226
'!src/core/server/mocks{,.ts}',
227227
'!src/core/server/types{,.ts}',
228-
'!src/core/server/test_utils',
228+
'!src/core/server/test_utils{,.ts}',
229229
// for absolute imports until fixed in
230230
// https://github.com/elastic/kibana/issues/36096
231231
'!src/core/server/*.test.mocks{,.ts}',

.github/CODEOWNERS

+1
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
/config/kibana.yml @elastic/kibana-platform
140140
/x-pack/plugins/features/ @elastic/kibana-platform
141141
/x-pack/plugins/licensing/ @elastic/kibana-platform
142+
/x-pack/plugins/global_search/ @elastic/kibana-platform
142143
/x-pack/plugins/cloud/ @elastic/kibana-platform
143144
/packages/kbn-config-schema/ @elastic/kibana-platform
144145
/src/legacy/server/config/ @elastic/kibana-platform

rfcs/text/0011_global_search.md

+19-42
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ Notes:
194194

195195
### Plugin API
196196

197-
#### server API
197+
#### Common types
198198

199199
```ts
200200
/**
@@ -208,6 +208,21 @@ type GlobalSearchResult = Omit<GlobalSearchProviderResult, 'url'> & {
208208
url: string;
209209
};
210210

211+
212+
/**
213+
* Response returned from the {@link GlobalSearchServiceStart | global search service}'s `find` API
214+
*/
215+
type GlobalSearchBatchedResults = {
216+
/**
217+
* Results for this batch
218+
*/
219+
results: GlobalSearchResult[];
220+
};
221+
```
222+
223+
#### server API
224+
225+
```ts
211226
/**
212227
* Options for the server-side {@link GlobalSearchServiceStart.find | find API}
213228
*/
@@ -226,16 +241,6 @@ interface GlobalSearchFindOptions {
226241
aborted$?: Observable<void>;
227242
}
228243

229-
/**
230-
* Response returned from the server-side {@link GlobalSearchServiceStart | global search service}'s `find` API
231-
*/
232-
type GlobalSearchBatchedResults = {
233-
/**
234-
* Results for this batch
235-
*/
236-
results: GlobalSearchResult[];
237-
};
238-
239244
/** @public */
240245
interface GlobalSearchPluginSetup {
241246
registerResultProvider(provider: GlobalSearchResultProvider);
@@ -265,28 +270,6 @@ interface GlobalSearchFindOptions {
265270
aborted$?: Observable<void>;
266271
}
267272

268-
/**
269-
* Enhanced {@link GlobalSearchResult | result type} for the client-side,
270-
* to allow navigating to a given result.
271-
*/
272-
interface NavigableGlobalSearchResult extends GlobalSearchResult {
273-
/**
274-
* Navigate to this result's associated url. If the result is on this kibana instance, user will be redirected to it
275-
* in a SPA friendly way using `application.navigateToApp`, else, a full page refresh will be performed.
276-
*/
277-
navigate: () => Promise<void>;
278-
}
279-
280-
/**
281-
* Response returned from the client-side {@link GlobalSearchServiceStart | global search service}'s `find` API
282-
*/
283-
type GlobalSearchBatchedResults = {
284-
/**
285-
* Results for this batch
286-
*/
287-
results: NavigableGlobalSearchResult[];
288-
};
289-
290273
/** @public */
291274
interface GlobalSearchPluginSetup {
292275
registerResultProvider(provider: GlobalSearchResultProvider);
@@ -304,9 +287,6 @@ Notes:
304287
- The `registerResultProvider` setup APIs share the same signature, however the input `GlobalSearchResultProvider`
305288
types are different on the client and server.
306289
- The `find` start API signature got a `KibanaRequest` for `server`, when this parameter is not present for `public`.
307-
- The `find` API returns a observable of `NavigableGlobalSearchResult` instead of plain `GlobalSearchResult`. This type
308-
is here to enhance results with a `navigate` method to let the `GlobalSearch` plugin handle the navigation logic, which is
309-
non-trivial. See the [Redirecting to a result](#redirecting-to-a-result) section for more info.
310290

311291
#### http API
312292

@@ -395,14 +375,11 @@ In current specification, the only conversion step is to transform the `result.u
395375

396376
#### redirecting to a result
397377

398-
Parsing a relative or absolute result url to perform SPA navigation can be non trivial, and should remains the responsibility
399-
of the GlobalSearch plugin API.
400-
401-
This is why `NavigableGlobalSearchResult.navigate` has been introduced on the client-side version of the `find` API
378+
Parsing a relative or absolute result url to perform SPA navigation can be non trivial. This is why `ApplicationService.navigateToUrl` has been introduced on the client-side core API
402379

403-
When using `navigate` from a result instance, the following logic will be executed:
380+
When using `navigateToUrl` with the url of a result instance, the following logic will be executed:
404381

405-
If all these criteria are true for `result.url`:
382+
If all these criteria are true for `url`:
406383

407384
- (only for absolute URLs) The origin of the URL matches the origin of the browser's current location
408385
- The pathname of the URL starts with the current basePath (eg. /mybasepath/s/my-space)

src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import supertest from 'supertest';
2121
import { UnwrapPromise } from '@kbn/utility-types';
2222
import { registerBulkCreateRoute } from '../bulk_create';
2323
import { savedObjectsClientMock } from '../../../../../core/server/mocks';
24-
import { setupServer } from './test_utils';
24+
import { setupServer } from '../test_utils';
2525

2626
type setupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
2727

src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import supertest from 'supertest';
2121
import { UnwrapPromise } from '@kbn/utility-types';
2222
import { registerBulkGetRoute } from '../bulk_get';
2323
import { savedObjectsClientMock } from '../../../../../core/server/mocks';
24-
import { setupServer } from './test_utils';
24+
import { setupServer } from '../test_utils';
2525

2626
type setupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
2727

src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import supertest from 'supertest';
2121
import { UnwrapPromise } from '@kbn/utility-types';
2222
import { registerBulkUpdateRoute } from '../bulk_update';
2323
import { savedObjectsClientMock } from '../../../../../core/server/mocks';
24-
import { setupServer } from './test_utils';
24+
import { setupServer } from '../test_utils';
2525

2626
type setupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
2727

src/core/server/saved_objects/routes/integration_tests/create.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import supertest from 'supertest';
2121
import { UnwrapPromise } from '@kbn/utility-types';
2222
import { registerCreateRoute } from '../create';
2323
import { savedObjectsClientMock } from '../../service/saved_objects_client.mock';
24-
import { setupServer } from './test_utils';
24+
import { setupServer } from '../test_utils';
2525

2626
type setupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
2727

src/core/server/saved_objects/routes/integration_tests/delete.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import supertest from 'supertest';
2121
import { UnwrapPromise } from '@kbn/utility-types';
2222
import { registerDeleteRoute } from '../delete';
2323
import { savedObjectsClientMock } from '../../../../../core/server/mocks';
24-
import { setupServer } from './test_utils';
24+
import { setupServer } from '../test_utils';
2525

2626
type setupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
2727

src/core/server/saved_objects/routes/integration_tests/export.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import supertest from 'supertest';
2727
import { UnwrapPromise } from '@kbn/utility-types';
2828
import { SavedObjectConfig } from '../../saved_objects_config';
2929
import { registerExportRoute } from '../export';
30-
import { setupServer, createExportableType } from './test_utils';
30+
import { setupServer, createExportableType } from '../test_utils';
3131

3232
type setupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
3333
const exportSavedObjectsToStream = exportMock.exportSavedObjectsToStream as jest.Mock;

src/core/server/saved_objects/routes/integration_tests/find.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import querystring from 'querystring';
2323
import { UnwrapPromise } from '@kbn/utility-types';
2424
import { registerFindRoute } from '../find';
2525
import { savedObjectsClientMock } from '../../../../../core/server/mocks';
26-
import { setupServer } from './test_utils';
26+
import { setupServer } from '../test_utils';
2727

2828
type setupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
2929

src/core/server/saved_objects/routes/integration_tests/import.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { UnwrapPromise } from '@kbn/utility-types';
2222
import { registerImportRoute } from '../import';
2323
import { savedObjectsClientMock } from '../../../../../core/server/mocks';
2424
import { SavedObjectConfig } from '../../saved_objects_config';
25-
import { setupServer, createExportableType } from './test_utils';
25+
import { setupServer, createExportableType } from '../test_utils';
2626

2727
type setupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
2828

src/core/server/saved_objects/routes/integration_tests/log_legacy_import.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import supertest from 'supertest';
2121
import { UnwrapPromise } from '@kbn/utility-types';
2222
import { registerLogLegacyImportRoute } from '../log_legacy_import';
2323
import { loggingServiceMock } from '../../../logging/logging_service.mock';
24-
import { setupServer } from './test_utils';
24+
import { setupServer } from '../test_utils';
2525

2626
type setupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
2727

src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import supertest from 'supertest';
2121
import { UnwrapPromise } from '@kbn/utility-types';
2222
import { registerResolveImportErrorsRoute } from '../resolve_import_errors';
2323
import { savedObjectsClientMock } from '../../../../../core/server/mocks';
24-
import { setupServer, createExportableType } from './test_utils';
24+
import { setupServer, createExportableType } from '../test_utils';
2525
import { SavedObjectConfig } from '../../saved_objects_config';
2626

2727
type setupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;

src/core/server/saved_objects/routes/integration_tests/update.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import supertest from 'supertest';
2121
import { UnwrapPromise } from '@kbn/utility-types';
2222
import { registerUpdateRoute } from '../update';
2323
import { savedObjectsClientMock } from '../../../../../core/server/mocks';
24-
import { setupServer } from './test_utils';
24+
import { setupServer } from '../test_utils';
2525

2626
type setupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;
2727

src/core/server/saved_objects/routes/integration_tests/test_utils.ts src/core/server/saved_objects/routes/test_utils.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
* under the License.
1818
*/
1919

20-
import { ContextService } from '../../../context';
21-
import { createHttpServer, createCoreContext } from '../../../http/test_utils';
22-
import { coreMock } from '../../../mocks';
23-
import { SavedObjectsType } from '../../types';
20+
import { ContextService } from '../../context';
21+
import { createHttpServer, createCoreContext } from '../../http/test_utils';
22+
import { coreMock } from '../../mocks';
23+
import { SavedObjectsType } from '../types';
2424

25-
const coreId = Symbol('core');
25+
const defaultCoreId = Symbol('core');
2626

27-
export const setupServer = async () => {
27+
export const setupServer = async (coreId: symbol = defaultCoreId) => {
2828
const coreContext = createCoreContext({ coreId });
2929
const contextService = new ContextService(coreContext);
3030

src/core/server/test_utils.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@
1919

2020
export { createHttpServer } from './http/test_utils';
2121
export { ServiceStatusLevelSnapshotSerializer } from './status/test_utils';
22+
export { setupServer } from './saved_objects/routes/test_utils';

src/core/types/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ export * from './capabilities';
2626
export * from './app_category';
2727
export * from './ui_settings';
2828
export * from './saved_objects';
29+
export * from './serializable';

src/core/types/serializable.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
export type Serializable =
21+
| string
22+
| number
23+
| boolean
24+
| null
25+
| SerializableArray
26+
| SerializableRecord;
27+
28+
// we need interfaces instead of types here to allow cyclic references
29+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
30+
export interface SerializableArray extends Array<Serializable> {}
31+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
32+
export interface SerializableRecord extends Record<string, Serializable> {}

test/plugin_functional/plugins/core_provider_plugin/kibana.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"id": "core_provider_plugin",
33
"version": "0.0.1",
44
"kibanaVersion": "kibana",
5-
"optionalPlugins": ["core_plugin_a", "core_plugin_b", "licensing"],
5+
"optionalPlugins": ["core_plugin_a", "core_plugin_b", "licensing", "globalSearchTest"],
66
"server": false,
77
"ui": true
88
}

x-pack/.i18nrc.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"xpack.endpoint": "plugins/endpoint",
1919
"xpack.features": "plugins/features",
2020
"xpack.fileUpload": "plugins/file_upload",
21+
"xpack.globalSearch": ["plugins/global_search"],
2122
"xpack.graph": ["plugins/graph"],
2223
"xpack.grokDebugger": "plugins/grokdebugger",
2324
"xpack.idxMgmt": "plugins/index_management",
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Kibana GlobalSearch plugin
2+
3+
The GlobalSearch plugin provides an easy way to search for various objects, such as applications
4+
or dashboards from the Kibana instance, from both server and client-side plugins
5+
6+
## Consuming the globalSearch API
7+
8+
```ts
9+
startDeps.globalSearch.find('some term').subscribe({
10+
next: ({ results }) => {
11+
addNewResultsToList(results);
12+
},
13+
error: () => {},
14+
complete: () => {
15+
showAsyncSearchIndicator(false);
16+
}
17+
});
18+
```
19+
20+
## Registering custom result providers
21+
22+
The GlobalSearch API allows to extend provided results by registering your own provider.
23+
24+
```ts
25+
setupDeps.globalSearch.registerResultProvider({
26+
id: 'my_provider',
27+
find: (term, options, context) => {
28+
const resultPromise = myService.search(term, context.core.savedObjects.client);
29+
return from(resultPromise).pipe(takeUntil(options.aborted$);
30+
},
31+
});
32+
```
33+
34+
## Known limitations
35+
36+
### Client-side registered providers
37+
38+
Results from providers registered from the client-side `registerResultProvider` API will
39+
not be available when performing a search from the server-side. For this reason, prefer
40+
registering providers using the server-side API when possible.
41+
42+
Refer to the [RFC](rfcs/text/0011_global_search.md#result_provider_registration) for more details
43+
44+
### Search completion cause
45+
46+
There is currently no way to identify `globalSearch.find` observable completion cause:
47+
searches completing because all providers returned all their results and searches
48+
completing because the consumer aborted the search using the `aborted$` option or because
49+
the internal timout period has been reaches will both complete the same way.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { GlobalSearchFindError } from './errors';
8+
9+
describe('GlobalSearchFindError', () => {
10+
describe('#invalidLicense', () => {
11+
it('create an error with the correct `type`', () => {
12+
const error = GlobalSearchFindError.invalidLicense('foobar');
13+
expect(error.message).toBe('foobar');
14+
expect(error.type).toBe('invalid-license');
15+
});
16+
17+
it('can be identified via instanceof', () => {
18+
const error = GlobalSearchFindError.invalidLicense('foo');
19+
expect(error instanceof GlobalSearchFindError).toBe(true);
20+
});
21+
});
22+
});

0 commit comments

Comments
 (0)