Skip to content

Commit 7d5fb8e

Browse files
[GS] add search syntax support (#83422)
* add search syntax parsing logic * fix ts types * use type filter in providers * move search syntax logic to the searchbar * fix test plugin types * fix test plugin types again * use `onSearch` prop to disable internal component search * add tag filter support * add FTR tests * move away from CI group 7 * fix unit tests * add unit tests * remove the API test suite * Add icons to the SO results * add test for unknown type / tag * nits * ignore case for the `type` filter * Add syntax help text * remove unused import * hide icon for non-application results * add tsdoc on query utils * coerce known filter values to string Co-authored-by: Ryan Keairns <contactryank@gmail.com>
1 parent a0a6518 commit 7d5fb8e

File tree

46 files changed

+1703
-351
lines changed

Some content is hidden

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

46 files changed

+1703
-351
lines changed

src/plugins/saved_objects_tagging_oss/public/api.mock.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const createApiUiMock = (): SavedObjectsTaggingApiUiMock => {
6060
convertNameToReference: jest.fn(),
6161
parseSearchQuery: jest.fn(),
6262
getTagIdsFromReferences: jest.fn(),
63+
getTagIdFromName: jest.fn(),
6364
updateTagsReferences: jest.fn(),
6465
};
6566

src/plugins/saved_objects_tagging_oss/public/api.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export interface SavedObjectsTaggingApiUi {
8484
/**
8585
* Convert given tag name to a {@link SavedObjectsFindOptionsReference | reference }
8686
* to be used to search using the savedObjects `_find` API. Will return `undefined`
87-
* is the given name does not match any existing tag.
87+
* if the given name does not match any existing tag.
8888
*/
8989
convertNameToReference(tagName: string): SavedObjectsFindOptionsReference | undefined;
9090

@@ -124,6 +124,12 @@ export interface SavedObjectsTaggingApiUi {
124124
references: Array<SavedObjectReference | SavedObjectsFindOptionsReference>
125125
): string[];
126126

127+
/**
128+
* Returns the id for given tag name. Will return `undefined`
129+
* if the given name does not match any existing tag.
130+
*/
131+
getTagIdFromName(tagName: string): string | undefined;
132+
127133
/**
128134
* Returns a new references array that replace the old tag references with references to the
129135
* new given tag ids, while preserving all non-tag references.

x-pack/plugins/global_search/common/types.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,28 @@ export interface GlobalSearchBatchedResults {
8787
*/
8888
results: GlobalSearchResult[];
8989
}
90+
91+
/**
92+
* Search parameters for the {@link GlobalSearchPluginStart.find | `find` API}
93+
*
94+
* @public
95+
*/
96+
export interface GlobalSearchFindParams {
97+
/**
98+
* The term to search for. Can be undefined if searching by filters.
99+
*/
100+
term?: string;
101+
/**
102+
* The types of results to search for.
103+
*/
104+
types?: string[];
105+
/**
106+
* The tag ids to filter search by.
107+
*/
108+
tags?: string[];
109+
}
110+
111+
/**
112+
* @public
113+
*/
114+
export type GlobalSearchProviderFindParams = GlobalSearchFindParams;

x-pack/plugins/global_search/public/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export {
2525
GlobalSearchProviderResult,
2626
GlobalSearchProviderResultUrl,
2727
GlobalSearchResult,
28+
GlobalSearchFindParams,
29+
GlobalSearchProviderFindParams,
2830
} from '../common/types';
2931
export {
3032
GlobalSearchPluginSetup,

x-pack/plugins/global_search/public/services/fetch_server_results.test.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,18 @@ describe('fetchServerResults', () => {
3333
it('perform a POST request to the endpoint with valid options', () => {
3434
http.post.mockResolvedValue({ results: [] });
3535

36-
fetchServerResults(http, 'some term', { preference: 'pref' });
36+
fetchServerResults(
37+
http,
38+
{ term: 'some term', types: ['dashboard', 'map'] },
39+
{ preference: 'pref' }
40+
);
3741

3842
expect(http.post).toHaveBeenCalledTimes(1);
3943
expect(http.post).toHaveBeenCalledWith('/internal/global_search/find', {
40-
body: JSON.stringify({ term: 'some term', options: { preference: 'pref' } }),
44+
body: JSON.stringify({
45+
params: { term: 'some term', types: ['dashboard', 'map'] },
46+
options: { preference: 'pref' },
47+
}),
4148
});
4249
});
4350

@@ -47,7 +54,11 @@ describe('fetchServerResults', () => {
4754

4855
http.post.mockResolvedValue({ results: [resultA, resultB] });
4956

50-
const results = await fetchServerResults(http, 'some term', { preference: 'pref' }).toPromise();
57+
const results = await fetchServerResults(
58+
http,
59+
{ term: 'some term' },
60+
{ preference: 'pref' }
61+
).toPromise();
5162

5263
expect(http.post).toHaveBeenCalledTimes(1);
5364
expect(results).toHaveLength(2);
@@ -65,7 +76,7 @@ describe('fetchServerResults', () => {
6576
getTestScheduler().run(({ expectObservable, hot }) => {
6677
http.post.mockReturnValue(hot('---(a|)', { a: { results: [] } }) as any);
6778

68-
const results = fetchServerResults(http, 'term', {});
79+
const results = fetchServerResults(http, { term: 'term' }, {});
6980

7081
expectObservable(results).toBe('---(a|)', {
7182
a: [],
@@ -77,7 +88,7 @@ describe('fetchServerResults', () => {
7788
getTestScheduler().run(({ expectObservable, hot }) => {
7889
http.post.mockReturnValue(hot('---(a|)', { a: { results: [] } }) as any);
7990
const aborted$ = hot('-(a|)', { a: undefined });
80-
const results = fetchServerResults(http, 'term', { aborted$ });
91+
const results = fetchServerResults(http, { term: 'term' }, { aborted$ });
8192

8293
expectObservable(results).toBe('-|', {
8394
a: [],

x-pack/plugins/global_search/public/services/fetch_server_results.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { Observable, from, EMPTY } from 'rxjs';
88
import { map, takeUntil } from 'rxjs/operators';
99
import { HttpStart } from 'src/core/public';
10-
import { GlobalSearchResult } from '../../common/types';
10+
import { GlobalSearchResult, GlobalSearchProviderFindParams } from '../../common/types';
1111
import { GlobalSearchFindOptions } from './types';
1212

1313
interface ServerFetchResponse {
@@ -24,7 +24,7 @@ interface ServerFetchResponse {
2424
*/
2525
export const fetchServerResults = (
2626
http: HttpStart,
27-
term: string,
27+
params: GlobalSearchProviderFindParams,
2828
{ preference, aborted$ }: GlobalSearchFindOptions
2929
): Observable<GlobalSearchResult[]> => {
3030
let controller: AbortController | undefined;
@@ -36,7 +36,7 @@ export const fetchServerResults = (
3636
}
3737
return from(
3838
http.post<ServerFetchResponse>('/internal/global_search/find', {
39-
body: JSON.stringify({ term, options: { preference } }),
39+
body: JSON.stringify({ params, options: { preference } }),
4040
signal: controller?.signal,
4141
})
4242
).pipe(

x-pack/plugins/global_search/public/services/search_service.test.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,14 @@ describe('SearchService', () => {
116116
registerResultProvider(provider);
117117

118118
const { find } = service.start(startDeps());
119-
find('foobar', { preference: 'pref' });
119+
find(
120+
{ term: 'foobar', types: ['dashboard', 'map'], tags: ['tag-id'] },
121+
{ preference: 'pref' }
122+
);
120123

121124
expect(provider.find).toHaveBeenCalledTimes(1);
122125
expect(provider.find).toHaveBeenCalledWith(
123-
'foobar',
126+
{ term: 'foobar', types: ['dashboard', 'map'], tags: ['tag-id'] },
124127
expect.objectContaining({ preference: 'pref' })
125128
);
126129
});
@@ -129,12 +132,15 @@ describe('SearchService', () => {
129132
service.setup({ config: createConfig() });
130133

131134
const { find } = service.start(startDeps());
132-
find('foobar', { preference: 'pref' });
135+
find(
136+
{ term: 'foobar', types: ['dashboard', 'map'], tags: ['tag-id'] },
137+
{ preference: 'pref' }
138+
);
133139

134140
expect(fetchServerResultsMock).toHaveBeenCalledTimes(1);
135141
expect(fetchServerResultsMock).toHaveBeenCalledWith(
136142
httpStart,
137-
'foobar',
143+
{ term: 'foobar', types: ['dashboard', 'map'], tags: ['tag-id'] },
138144
expect.objectContaining({ preference: 'pref', aborted$: expect.any(Object) })
139145
);
140146
});
@@ -148,25 +154,25 @@ describe('SearchService', () => {
148154
registerResultProvider(provider);
149155

150156
const { find } = service.start(startDeps());
151-
find('foobar', { preference: 'pref' });
157+
find({ term: 'foobar' }, { preference: 'pref' });
152158

153159
expect(getDefaultPreferenceMock).not.toHaveBeenCalled();
154160

155161
expect(provider.find).toHaveBeenNthCalledWith(
156162
1,
157-
'foobar',
163+
{ term: 'foobar' },
158164
expect.objectContaining({
159165
preference: 'pref',
160166
})
161167
);
162168

163-
find('foobar', {});
169+
find({ term: 'foobar' }, {});
164170

165171
expect(getDefaultPreferenceMock).toHaveBeenCalledTimes(1);
166172

167173
expect(provider.find).toHaveBeenNthCalledWith(
168174
2,
169-
'foobar',
175+
{ term: 'foobar' },
170176
expect.objectContaining({
171177
preference: 'default_pref',
172178
})
@@ -186,7 +192,7 @@ describe('SearchService', () => {
186192
registerResultProvider(createProvider('A', providerResults));
187193

188194
const { find } = service.start(startDeps());
189-
const results = find('foo', {});
195+
const results = find({ term: 'foobar' }, {});
190196

191197
expectObservable(results).toBe('a-b-|', {
192198
a: expectedBatch('1'),
@@ -207,7 +213,7 @@ describe('SearchService', () => {
207213
fetchServerResultsMock.mockReturnValue(serverResults);
208214

209215
const { find } = service.start(startDeps());
210-
const results = find('foo', {});
216+
const results = find({ term: 'foobar' }, {});
211217

212218
expectObservable(results).toBe('a-b-|', {
213219
a: expectedBatch('1'),
@@ -242,7 +248,7 @@ describe('SearchService', () => {
242248
);
243249

244250
const { find } = service.start(startDeps());
245-
const results = find('foo', {});
251+
const results = find({ term: 'foobar' }, {});
246252

247253
expectObservable(results).toBe('ab-cd-|', {
248254
a: expectedBatch('A1', 'A2'),
@@ -276,7 +282,7 @@ describe('SearchService', () => {
276282
);
277283

278284
const { find } = service.start(startDeps());
279-
const results = find('foo', {});
285+
const results = find({ term: 'foobar' }, {});
280286

281287
expectObservable(results).toBe('a-b--(c|)', {
282288
a: expectedBatch('P1'),
@@ -301,7 +307,7 @@ describe('SearchService', () => {
301307
const aborted$ = hot('----a--|', { a: undefined });
302308

303309
const { find } = service.start(startDeps());
304-
const results = find('foo', { aborted$ });
310+
const results = find({ term: 'foobar' }, { aborted$ });
305311

306312
expectObservable(results).toBe('--a-|', {
307313
a: expectedBatch('1'),
@@ -323,7 +329,7 @@ describe('SearchService', () => {
323329
registerResultProvider(createProvider('A', providerResults));
324330

325331
const { find } = service.start(startDeps());
326-
const results = find('foo', {});
332+
const results = find({ term: 'foobar' }, {});
327333

328334
expectObservable(results).toBe('a 24ms b 74ms |', {
329335
a: expectedBatch('1'),
@@ -359,7 +365,7 @@ describe('SearchService', () => {
359365
);
360366

361367
const { find } = service.start(startDeps());
362-
const results = find('foo', {});
368+
const results = find({ term: 'foobar' }, {});
363369

364370
expectObservable(results).toBe('ab-(c|)', {
365371
a: expectedBatch('A1', 'A2'),
@@ -392,7 +398,7 @@ describe('SearchService', () => {
392398
registerResultProvider(provider);
393399

394400
const { find } = service.start(startDeps());
395-
const batch = await find('foo', {}).pipe(take(1)).toPromise();
401+
const batch = await find({ term: 'foobar' }, {}).pipe(take(1)).toPromise();
396402

397403
expect(batch.results).toHaveLength(2);
398404
expect(batch.results[0]).toEqual({
@@ -420,7 +426,7 @@ describe('SearchService', () => {
420426
registerResultProvider(createProvider('A', providerResults));
421427

422428
const { find } = service.start(startDeps());
423-
const results = find('foo', {});
429+
const results = find({ term: 'foobar' }, {});
424430

425431
expectObservable(results).toBe(
426432
'#',

x-pack/plugins/global_search/public/services/search_service.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import { map, takeUntil } from 'rxjs/operators';
99
import { duration } from 'moment';
1010
import { i18n } from '@kbn/i18n';
1111
import { HttpStart } from 'src/core/public';
12-
import { GlobalSearchProviderResult, GlobalSearchBatchedResults } from '../../common/types';
12+
import {
13+
GlobalSearchFindParams,
14+
GlobalSearchProviderResult,
15+
GlobalSearchBatchedResults,
16+
} from '../../common/types';
1317
import { GlobalSearchFindError } from '../../common/errors';
1418
import { takeInArray } from '../../common/operators';
1519
import { defaultMaxProviderResults } from '../../common/constants';
@@ -52,7 +56,7 @@ export interface SearchServiceStart {
5256
*
5357
* @example
5458
* ```ts
55-
* startDeps.globalSearch.find('some term').subscribe({
59+
* startDeps.globalSearch.find({term: 'some term'}).subscribe({
5660
* next: ({ results }) => {
5761
* addNewResultsToList(results);
5862
* },
@@ -67,7 +71,10 @@ export interface SearchServiceStart {
6771
* Emissions from the resulting observable will only contains **new** results. It is the consumer's
6872
* responsibility to aggregate the emission and sort the results if required.
6973
*/
70-
find(term: string, options: GlobalSearchFindOptions): Observable<GlobalSearchBatchedResults>;
74+
find(
75+
params: GlobalSearchFindParams,
76+
options: GlobalSearchFindOptions
77+
): Observable<GlobalSearchBatchedResults>;
7178
}
7279

7380
interface SetupDeps {
@@ -110,11 +117,11 @@ export class SearchService {
110117
this.licenseChecker = licenseChecker;
111118

112119
return {
113-
find: (term, options) => this.performFind(term, options),
120+
find: (params, options) => this.performFind(params, options),
114121
};
115122
}
116123

117-
private performFind(term: string, options: GlobalSearchFindOptions) {
124+
private performFind(params: GlobalSearchFindParams, options: GlobalSearchFindOptions) {
118125
const licenseState = this.licenseChecker!.getState();
119126
if (!licenseState.valid) {
120127
return throwError(
@@ -142,13 +149,13 @@ export class SearchService {
142149
const processResult = (result: GlobalSearchProviderResult) =>
143150
processProviderResult(result, this.http!.basePath);
144151

145-
const serverResults$ = fetchServerResults(this.http!, term, {
152+
const serverResults$ = fetchServerResults(this.http!, params, {
146153
preference,
147154
aborted$,
148155
});
149156

150157
const providersResults$ = [...this.providers.values()].map((provider) =>
151-
provider.find(term, providerOptions).pipe(
158+
provider.find(params, providerOptions).pipe(
152159
takeInArray(this.maxProviderResults),
153160
takeUntil(aborted$),
154161
map((results) => results.map((r) => processResult(r)))

x-pack/plugins/global_search/public/types.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
*/
66

77
import { Observable } from 'rxjs';
8-
import { GlobalSearchProviderFindOptions, GlobalSearchProviderResult } from '../common/types';
8+
import {
9+
GlobalSearchProviderFindOptions,
10+
GlobalSearchProviderResult,
11+
GlobalSearchProviderFindParams,
12+
} from '../common/types';
913
import { SearchServiceSetup, SearchServiceStart } from './services';
1014

1115
export type GlobalSearchPluginSetup = Pick<SearchServiceSetup, 'registerResultProvider'>;
@@ -29,15 +33,15 @@ export interface GlobalSearchResultProvider {
2933
* // returning all results in a single batch
3034
* setupDeps.globalSearch.registerResultProvider({
3135
* id: 'my_provider',
32-
* find: (term, { aborted$, preference, maxResults }, context) => {
36+
* find: ({ term, filters }, { aborted$, preference, maxResults }, context) => {
3337
* const resultPromise = myService.search(term, { preference, maxResults }, context.core.savedObjects.client);
3438
* return from(resultPromise).pipe(takeUntil(aborted$));
3539
* },
3640
* });
3741
* ```
3842
*/
3943
find(
40-
term: string,
44+
search: GlobalSearchProviderFindParams,
4145
options: GlobalSearchProviderFindOptions
4246
): Observable<GlobalSearchProviderResult[]>;
4347
}

0 commit comments

Comments
 (0)