Skip to content

Commit 364595f

Browse files
lukasolsonLiza Kelasticmachine
committed
[Search] Add telemetry for data plugin search service (#70677)
* [search] Refactor the way search strategies are registered/retrieved on the server * Fix types and tests and update docs * Fix failing test * Fix build of example plugin * Fix functional test * Make server strategies sync * Move strategy name into options * docs * Remove FE strategies * TypeScript of hell delete search explorer * Fix search interceptor OSS tests * typos * test cleanup * Update search interceptor tests and abort utils * [Search] Add telemetry for data plugin search service * Add tracking of average query time * Add tests and rename to collectors * Fix TS * Fixed interceptor jest tests * Add to kibana json * docs * Properly use observables rather than only during setup * Update or create * Swallow version conflict errors Co-authored-by: Liza K <liza.katz@elastic.co> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 240b46c commit 364595f

31 files changed

Lines changed: 668 additions & 17 deletions

docs/development/plugins/data/public/kibana-plugin-plugins-data-public.plugin.setup.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
<b>Signature:</b>
88

99
```typescript
10-
setup(core: CoreSetup, { expressions, uiActions }: DataSetupDependencies): DataPublicPluginSetup;
10+
setup(core: CoreSetup, { expressions, uiActions, usageCollection }: DataSetupDependencies): DataPublicPluginSetup;
1111
```
1212

1313
## Parameters
1414

1515
| Parameter | Type | Description |
1616
| --- | --- | --- |
1717
| core | <code>CoreSetup</code> | |
18-
| { expressions, uiActions } | <code>DataSetupDependencies</code> | |
18+
| { expressions, uiActions, usageCollection } | <code>DataSetupDependencies</code> | |
1919

2020
<b>Returns:</b>
2121

docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptordeps.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ export interface SearchInterceptorDeps
1818
| [http](./kibana-plugin-plugins-data-public.searchinterceptordeps.http.md) | <code>CoreStart['http']</code> | |
1919
| [toasts](./kibana-plugin-plugins-data-public.searchinterceptordeps.toasts.md) | <code>ToastsStart</code> | |
2020
| [uiSettings](./kibana-plugin-plugins-data-public.searchinterceptordeps.uisettings.md) | <code>CoreStart['uiSettings']</code> | |
21+
| [usageCollector](./kibana-plugin-plugins-data-public.searchinterceptordeps.usagecollector.md) | <code>SearchUsageCollector</code> | |
2122

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
2+
3+
[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) &gt; [usageCollector](./kibana-plugin-plugins-data-public.searchinterceptordeps.usagecollector.md)
4+
5+
## SearchInterceptorDeps.usageCollector property
6+
7+
<b>Signature:</b>
8+
9+
```typescript
10+
usageCollector?: SearchUsageCollector;
11+
```

docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsetup.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ export interface ISearchSetup
1414

1515
| Property | Type | Description |
1616
| --- | --- | --- |
17-
| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | <code>(name: string, strategy: ISearchStrategy) =&gt; void</code> | Extension point exposed for other plugins to register their own search strategies. |
17+
| [registerSearchStrategy](./kibana-plugin-plugins-data-server.isearchsetup.registersearchstrategy.md) | <code>TRegisterSearchStrategy</code> | Extension point exposed for other plugins to register their own search strategies. |
18+
| [usage](./kibana-plugin-plugins-data-server.isearchsetup.usage.md) | <code>SearchUsage</code> | Used internally for telemetry |
1819

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
2+
3+
[Home](./index.md) &gt; [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) &gt; [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) &gt; [usage](./kibana-plugin-plugins-data-server.isearchsetup.usage.md)
4+
5+
## ISearchSetup.usage property
6+
7+
Used internally for telemetry
8+
9+
<b>Signature:</b>
10+
11+
```typescript
12+
usage: SearchUsage;
13+
```

src/plugins/data/kibana.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"optionalPlugins": ["usageCollection"],
1111
"extraPublicDirs": ["common", "common/utils/abort_utils"],
1212
"requiredBundles": [
13+
"usageCollection",
1314
"kibanaUtils",
1415
"kibanaReact",
1516
"kibanaLegacy",

src/plugins/data/public/plugin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export class DataPublicPlugin implements Plugin<DataPublicPluginSetup, DataPubli
112112

113113
public setup(
114114
core: CoreSetup,
115-
{ expressions, uiActions }: DataSetupDependencies
115+
{ expressions, uiActions, usageCollection }: DataSetupDependencies
116116
): DataPublicPluginSetup {
117117
const startServices = createStartServicesGetter(core.getStartServices);
118118

@@ -153,6 +153,7 @@ export class DataPublicPlugin implements Plugin<DataPublicPluginSetup, DataPubli
153153
autocomplete: this.autocomplete.setup(core),
154154
search: this.searchService.setup(core, {
155155
expressions,
156+
usageCollection,
156157
getInternalStartServices,
157158
packageInfo: this.packageInfo,
158159
}),

src/plugins/data/public/public.api.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ import { KibanaConfigType } from 'src/core/server/kibana_config';
117117
import { Location } from 'history';
118118
import { LocationDescriptorObject } from 'history';
119119
import { MaybePromise } from '@kbn/utility-types';
120+
import { METRIC_TYPE } from '@kbn/analytics';
120121
import { MGetParams } from 'elasticsearch';
121122
import { MGetResponse } from 'elasticsearch';
122123
import { Moment } from 'moment';
@@ -144,6 +145,7 @@ import { RecursiveReadonly } from '@kbn/utility-types';
144145
import { ReindexParams } from 'elasticsearch';
145146
import { ReindexRethrottleParams } from 'elasticsearch';
146147
import { RenderSearchTemplateParams } from 'elasticsearch';
148+
import { Reporter } from '@kbn/analytics';
147149
import { RequestAdapter } from 'src/plugins/inspector/common';
148150
import { RequestStatistics } from 'src/plugins/inspector/common';
149151
import { Required } from '@kbn/utility-types';
@@ -1449,7 +1451,7 @@ export class Plugin implements Plugin_2<DataPublicPluginSetup, DataPublicPluginS
14491451
// Warning: (ae-forgotten-export) The symbol "DataSetupDependencies" needs to be exported by the entry point index.d.ts
14501452
//
14511453
// (undocumented)
1452-
setup(core: CoreSetup, { expressions, uiActions }: DataSetupDependencies): DataPublicPluginSetup;
1454+
setup(core: CoreSetup, { expressions, uiActions, usageCollection }: DataSetupDependencies): DataPublicPluginSetup;
14531455
// Warning: (ae-forgotten-export) The symbol "DataStartDependencies" needs to be exported by the entry point index.d.ts
14541456
//
14551457
// (undocumented)
@@ -1777,6 +1779,10 @@ export interface SearchInterceptorDeps {
17771779
toasts: ToastsStart;
17781780
// (undocumented)
17791781
uiSettings: CoreStart['uiSettings'];
1782+
// Warning: (ae-forgotten-export) The symbol "SearchUsageCollector" needs to be exported by the entry point index.d.ts
1783+
//
1784+
// (undocumented)
1785+
usageCollector?: SearchUsageCollector;
17801786
}
17811787

17821788
// Warning: (ae-missing-release-tag) "SearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -2002,9 +2008,9 @@ export const UI_SETTINGS: {
20022008
// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
20032009
// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
20042010
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:41:60 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
2005-
// src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
2006-
// src/plugins/data/public/types.ts:53:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts
2007-
// src/plugins/data/public/types.ts:61:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts
2011+
// src/plugins/data/public/types.ts:54:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
2012+
// src/plugins/data/public/types.ts:55:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts
2013+
// src/plugins/data/public/types.ts:63:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts
20082014

20092015
// (No @packageDocumentation comment for this package)
20102016

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
import { CoreSetup, CoreStart } from '../../../../../core/public';
21+
import { coreMock } from '../../../../../core/public/mocks';
22+
import { usageCollectionPluginMock, Setup } from '../../../../usage_collection/public/mocks';
23+
import { createUsageCollector } from './create_usage_collector';
24+
import { SEARCH_EVENT_TYPE, SearchUsageCollector } from './types';
25+
import { METRIC_TYPE } from '@kbn/analytics';
26+
import { from } from 'rxjs';
27+
28+
describe('Search Usage Collector', () => {
29+
let mockCoreSetup: MockedKeys<CoreSetup>;
30+
let mockUsageCollectionSetup: Setup;
31+
let usageCollector: SearchUsageCollector;
32+
33+
beforeEach(() => {
34+
mockCoreSetup = coreMock.createSetup();
35+
(mockCoreSetup as any).getStartServices.mockResolvedValue([
36+
{
37+
application: {
38+
currentAppId$: from(['foo/bar']),
39+
},
40+
} as jest.Mocked<CoreStart>,
41+
{} as any,
42+
{} as any,
43+
]);
44+
mockUsageCollectionSetup = usageCollectionPluginMock.createSetupContract();
45+
usageCollector = createUsageCollector(mockCoreSetup, mockUsageCollectionSetup);
46+
});
47+
48+
test('tracks query timeouts', async () => {
49+
await usageCollector.trackQueryTimedOut();
50+
expect(mockUsageCollectionSetup.reportUiStats).toHaveBeenCalled();
51+
expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][0]).toBe('foo/bar');
52+
expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][1]).toBe(METRIC_TYPE.LOADED);
53+
expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][2]).toBe(
54+
SEARCH_EVENT_TYPE.QUERY_TIMED_OUT
55+
);
56+
});
57+
58+
test('tracks query cancellation', async () => {
59+
await usageCollector.trackQueriesCancelled();
60+
expect(mockUsageCollectionSetup.reportUiStats).toHaveBeenCalled();
61+
expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][1]).toBe(METRIC_TYPE.LOADED);
62+
expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][2]).toBe(
63+
SEARCH_EVENT_TYPE.QUERIES_CANCELLED
64+
);
65+
});
66+
67+
test('tracks long popups', async () => {
68+
await usageCollector.trackLongQueryPopupShown();
69+
expect(mockUsageCollectionSetup.reportUiStats).toHaveBeenCalled();
70+
expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][1]).toBe(METRIC_TYPE.LOADED);
71+
expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][2]).toBe(
72+
SEARCH_EVENT_TYPE.LONG_QUERY_POPUP_SHOWN
73+
);
74+
});
75+
76+
test('tracks long popups dismissed', async () => {
77+
await usageCollector.trackLongQueryDialogDismissed();
78+
expect(mockUsageCollectionSetup.reportUiStats).toHaveBeenCalled();
79+
expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][1]).toBe(METRIC_TYPE.CLICK);
80+
expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][2]).toBe(
81+
SEARCH_EVENT_TYPE.LONG_QUERY_DIALOG_DISMISSED
82+
);
83+
});
84+
85+
test('tracks run query beyond timeout', async () => {
86+
await usageCollector.trackLongQueryRunBeyondTimeout();
87+
expect(mockUsageCollectionSetup.reportUiStats).toHaveBeenCalled();
88+
expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][1]).toBe(METRIC_TYPE.CLICK);
89+
expect(mockUsageCollectionSetup.reportUiStats.mock.calls[0][2]).toBe(
90+
SEARCH_EVENT_TYPE.LONG_QUERY_RUN_BEYOND_TIMEOUT
91+
);
92+
});
93+
94+
test('tracks response errors', async () => {
95+
const duration = 10;
96+
await usageCollector.trackError(duration);
97+
expect(mockCoreSetup.http.post).toBeCalled();
98+
expect(mockCoreSetup.http.post.mock.calls[0][0]).toBe('/api/search/usage');
99+
});
100+
101+
test('tracks response duration', async () => {
102+
const duration = 5;
103+
await usageCollector.trackSuccess(duration);
104+
expect(mockCoreSetup.http.post).toBeCalled();
105+
expect(mockCoreSetup.http.post.mock.calls[0][0]).toBe('/api/search/usage');
106+
});
107+
});
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
import { first } from 'rxjs/operators';
21+
import { CoreSetup } from '../../../../../core/public';
22+
import { METRIC_TYPE, UsageCollectionSetup } from '../../../../usage_collection/public';
23+
import { SEARCH_EVENT_TYPE, SearchUsageCollector } from './types';
24+
25+
export const createUsageCollector = (
26+
core: CoreSetup,
27+
usageCollection?: UsageCollectionSetup
28+
): SearchUsageCollector => {
29+
const getCurrentApp = async () => {
30+
const [{ application }] = await core.getStartServices();
31+
return application.currentAppId$.pipe(first()).toPromise();
32+
};
33+
34+
return {
35+
trackQueryTimedOut: async () => {
36+
const currentApp = await getCurrentApp();
37+
return usageCollection?.reportUiStats(
38+
currentApp!,
39+
METRIC_TYPE.LOADED,
40+
SEARCH_EVENT_TYPE.QUERY_TIMED_OUT
41+
);
42+
},
43+
trackQueriesCancelled: async () => {
44+
const currentApp = await getCurrentApp();
45+
return usageCollection?.reportUiStats(
46+
currentApp!,
47+
METRIC_TYPE.LOADED,
48+
SEARCH_EVENT_TYPE.QUERIES_CANCELLED
49+
);
50+
},
51+
trackLongQueryPopupShown: async () => {
52+
const currentApp = await getCurrentApp();
53+
return usageCollection?.reportUiStats(
54+
currentApp!,
55+
METRIC_TYPE.LOADED,
56+
SEARCH_EVENT_TYPE.LONG_QUERY_POPUP_SHOWN
57+
);
58+
},
59+
trackLongQueryDialogDismissed: async () => {
60+
const currentApp = await getCurrentApp();
61+
return usageCollection?.reportUiStats(
62+
currentApp!,
63+
METRIC_TYPE.CLICK,
64+
SEARCH_EVENT_TYPE.LONG_QUERY_DIALOG_DISMISSED
65+
);
66+
},
67+
trackLongQueryRunBeyondTimeout: async () => {
68+
const currentApp = await getCurrentApp();
69+
return usageCollection?.reportUiStats(
70+
currentApp!,
71+
METRIC_TYPE.CLICK,
72+
SEARCH_EVENT_TYPE.LONG_QUERY_RUN_BEYOND_TIMEOUT
73+
);
74+
},
75+
trackError: async (duration: number) => {
76+
return core.http.post('/api/search/usage', {
77+
body: JSON.stringify({
78+
eventType: 'error',
79+
duration,
80+
}),
81+
});
82+
},
83+
trackSuccess: async (duration: number) => {
84+
return core.http.post('/api/search/usage', {
85+
body: JSON.stringify({
86+
eventType: 'success',
87+
duration,
88+
}),
89+
});
90+
},
91+
};
92+
};

0 commit comments

Comments
 (0)