Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lens] Track actions in the UI by time #47919

Merged
merged 24 commits into from
Oct 16, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a832b1e
[Lens] Track actions in the UI by time
wylieconlon Oct 9, 2019
8c818e4
Switch collector to use task data
wylieconlon Oct 11, 2019
5281cbd
Report summarized version of task data when requested
wylieconlon Oct 11, 2019
bf26faa
Track a more complete set of metrics
wylieconlon Oct 12, 2019
5c7fdb7
Collect suggestion events separately
wylieconlon Oct 13, 2019
e8e2b70
Pre-aggregate by date in localStorage
wylieconlon Oct 14, 2019
1fcce38
Add integration tests
wylieconlon Oct 14, 2019
8a2dce4
Merge remote-tracking branch 'origin/master' into lens/click-telemetry
wylieconlon Oct 14, 2019
c4c5639
Fix test linter
wylieconlon Oct 14, 2019
e9590e8
Fix telemetry naming and run at midnight instead of every minute
wylieconlon Oct 14, 2019
bbe16fa
Improve cleanup at app level
wylieconlon Oct 14, 2019
b75ca97
Fix lint errors
wylieconlon Oct 14, 2019
863f329
Remove unused mock
wylieconlon Oct 14, 2019
7dc94b8
Fix tests
wylieconlon Oct 15, 2019
2a60204
Merge remote-tracking branch 'origin/master' into lens/click-telemetry
wylieconlon Oct 15, 2019
6c24d16
Fix types
wylieconlon Oct 15, 2019
f1a1399
Merge remote-tracking branch 'origin/master' into lens/click-telemetry
wylieconlon Oct 15, 2019
fd1f6d6
Update event names and fix local tracking
wylieconlon Oct 15, 2019
2a28500
Merge remote-tracking branch 'origin/master' into lens/click-telemetry
wylieconlon Oct 15, 2019
16ebf6c
Respond to review comments
wylieconlon Oct 15, 2019
4da50b3
Fix task time
wylieconlon Oct 15, 2019
b5d54a7
Merge remote-tracking branch 'origin/master' into lens/click-telemetry
wylieconlon Oct 15, 2019
5ef055f
Fix test
wylieconlon Oct 16, 2019
1836ed1
Merge remote-tracking branch 'origin/master' into lens/click-telemetry
wylieconlon Oct 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions x-pack/legacy/plugins/lens/common/clickdata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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.
*/

export interface LensClickEvent {
name: string;
date: string;
}
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/lens/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@

export * from './api';
export * from './constants';
export * from './clickdata';
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/lens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export const lens: LegacyPluginInitializer = kibana => {
savedObjects: server.savedObjects,
usage: server.usage,
config: server.config(),
server,
});

server.events.on('stop', () => {
Expand Down
13 changes: 13 additions & 0 deletions x-pack/legacy/plugins/lens/mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,18 @@
"type": "keyword"
}
}
},
"lens-ui-telemetry": {
"properties": {
"name": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"date": {
"type": "date"
}
}
}
}
7 changes: 7 additions & 0 deletions x-pack/legacy/plugins/lens/public/app_plugin/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_reac
import { Document, SavedObjectStore } from '../persistence';
import { EditorFrameInstance } from '../types';
import { NativeRenderer } from '../native_renderer';
import { useLensTelemetry } from '../lens_ui_telemetry';

interface State {
isLoading: boolean;
Expand Down Expand Up @@ -64,6 +65,7 @@ export function App({
const timeDefaults = core.uiSettings.get('timepicker:timeDefaults');
const language =
store.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage');
const { trackClick } = useLensTelemetry();

const [state, setState] = useState<State>({
isLoading: !!docId,
Expand All @@ -84,6 +86,8 @@ export function App({
const subscription = dataShim.filter.filterManager.getUpdates$().subscribe({
next: () => {
setState(s => ({ ...s, filters: dataShim.filter.filterManager.getFilters() }));

trackClick('app_filters_updated');
},
});
return () => {
Expand Down Expand Up @@ -198,6 +202,7 @@ export function App({
}
})
.catch(() => {
trackClick('save_failed');
wylieconlon marked this conversation as resolved.
Show resolved Hide resolved
core.notifications.toasts.addDanger(
i18n.translate('xpack.lens.editorFrame.docSavingError', {
defaultMessage: 'Error saving document',
Expand All @@ -222,6 +227,8 @@ export function App({
},
query: query || s.query,
}));

trackClick('date_or_query_change');
}}
appName={'lens'}
indexPatterns={state.indexPatternsForTopNav}
Expand Down
52 changes: 37 additions & 15 deletions x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '../datatable_visualization_plugin';
import { App } from './app';
import { EditorFrameInstance } from '../types';
import { LensReportManager, LensTelemetryContext } from '../lens_ui_telemetry';

export interface LensPluginStartDependencies {
data: DataPublicPluginStart;
Expand All @@ -33,6 +34,7 @@ export interface LensPluginStartDependencies {
export class AppPlugin {
private instance: EditorFrameInstance | null = null;
private store: SavedObjectIndexStore | null = null;
private reporter: LensReportManager | null = null;

constructor() {}

Expand Down Expand Up @@ -63,24 +65,40 @@ export class AppPlugin {

this.instance = editorFrameStartInterface.createInstance({});

this.reporter = new LensReportManager({
storage: new Storage(localStorage),
basePath: core.http.basePath.get(),
http: core.http,
});

const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => {
if (this.reporter) {
this.reporter.trackClick('loaded');
}
return (
<App
core={core}
data={data}
dataShim={dataShim}
editorFrame={this.instance!}
store={new Storage(localStorage)}
docId={routeProps.match.params.id}
docStorage={store}
redirectTo={id => {
if (!id) {
routeProps.history.push('/');
} else {
routeProps.history.push(`/edit/${id}`);
}
<LensTelemetryContext.Provider
value={{
trackClick: name => this.reporter && this.reporter.trackClick(name),
trackSuggestionClick: name => this.reporter && this.reporter.trackSuggestionClick(name),
}}
/>
>
<App
core={core}
data={data}
dataShim={dataShim}
editorFrame={this.instance!}
store={new Storage(localStorage)}
docId={routeProps.match.params.id}
docStorage={store}
redirectTo={id => {
if (!id) {
routeProps.history.push('/');
} else {
routeProps.history.push(`/edit/${id}`);
}
}}
/>
</LensTelemetryContext.Provider>
);
};

Expand All @@ -106,6 +124,10 @@ export class AppPlugin {
this.instance.unmount();
}

if (this.reporter) {
this.reporter.stop();
}

// TODO this will be handled by the plugin platform itself
indexPatternDatasourceStop();
xyVisualizationStop();
Expand Down
41 changes: 23 additions & 18 deletions x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { EditorFrame } from './editor_frame';
import { mergeTables } from './merge_tables';
import { EmbeddableFactory } from './embeddable/embeddable_factory';
import { getActiveDatasourceIdFromDoc } from './editor_frame/state_management';
import { LensTelemetryContext, useLensTelemetry } from '../lens_ui_telemetry';

export interface EditorFrameSetupPlugins {
data: typeof dataSetup;
Expand Down Expand Up @@ -83,24 +84,28 @@ export class EditorFramePlugin {

render(
<I18nProvider>
<EditorFrame
data-test-subj="lnsEditorFrame"
onError={onError}
datasourceMap={this.datasources}
visualizationMap={this.visualizations}
initialDatasourceId={getActiveDatasourceIdFromDoc(doc) || firstDatasourceId || null}
initialVisualizationId={
(doc && doc.visualizationType) || firstVisualizationId || null
}
core={core}
ExpressionRenderer={plugins.expressions.ExpressionRenderer}
doc={doc}
dateRange={dateRange}
query={query}
filters={filters}
savedQuery={savedQuery}
onChange={onChange}
/>
<LensTelemetryContext>
<EditorFrame
data-test-subj="lnsEditorFrame"
onError={onError}
datasourceMap={this.datasources}
visualizationMap={this.visualizations}
initialDatasourceId={
getActiveDatasourceIdFromDoc(doc) || firstDatasourceId || null
}
initialVisualizationId={
(doc && doc.visualizationType) || firstVisualizationId || null
}
core={core}
ExpressionRenderer={plugins.expressions.ExpressionRenderer}
doc={doc}
dateRange={dateRange}
query={query}
filters={filters}
savedQuery={savedQuery}
onChange={onChange}
/>
</LensTelemetryContext>
</I18nProvider>,
domElement
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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 { createContext, useContext } from 'react';

export const LensTelemetryContext = createContext<{
trackClick: (name: string) => void;
trackSuggestionClick: (name: string, suggestionData: unknown) => void;
}>({
trackClick: jest.fn(),
trackSuggestionClick: jest.fn(),
});

export const useLensTelemetry = () => useContext(LensTelemetryContext);
89 changes: 89 additions & 0 deletions x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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 { HttpServiceBase } from 'src/core/public';

import { Storage } from 'src/legacy/core_plugins/data/public/types';
import { LensClickEvent, BASE_API_URL } from '../../common';

const STORAGE_KEY = 'lens-ui-telemetry';

export class LensReportManager {
private clicks: LensClickEvent[];
private suggestionClicks: LensClickEvent[];

private storage: Storage;
private http: HttpServiceBase;
private basePath: string;
private timer: ReturnType<typeof setInterval>;

constructor({
storage,
http,
basePath,
}: {
storage: Storage;
http: HttpServiceBase;
basePath: string;
}) {
this.storage = storage;
this.http = http;
this.basePath = basePath;

const unsent = this.storage.get(STORAGE_KEY);
this.clicks = unsent && unsent.clicks ? unsent.clicks : [];
this.suggestionClicks = unsent && unsent.suggestionClicks ? unsent.suggestionClicks : [];

this.timer = setInterval(() => {
if (this.clicks.length || this.suggestionClicks.length) {
this.postToServer();
}
}, 10000);
wylieconlon marked this conversation as resolved.
Show resolved Hide resolved
}

public trackClick(name: string) {
this.clicks.push({
name,
date: new Date().toISOString(),
});
this.write();
}

public trackSuggestionClick(name: string) {
this.suggestionClicks.push({
name,
date: new Date().toISOString(),
});
this.write();
}

private write() {
this.storage.set(STORAGE_KEY, { clicks: this.clicks, suggestionClicks: this.suggestionClicks });
}

public stop() {
wylieconlon marked this conversation as resolved.
Show resolved Hide resolved
if (this.timer) {
clearInterval(this.timer);
}
}

private async postToServer() {
try {
await this.http.post(`${this.basePath}${BASE_API_URL}/telemetry`, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should track this.request or something and then just exit here, if the previous request is still running for some reason. 10 seconds should be enough, but...

body: JSON.stringify({
clicks: this.clicks,
suggestionClicks: this.clicks,
}),
});
this.clicks = [];
this.suggestionClicks = [];
this.write();
} catch (e) {
// Maybe show an error
wylieconlon marked this conversation as resolved.
Show resolved Hide resolved
console.log(e);
wylieconlon marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
8 changes: 8 additions & 0 deletions x-pack/legacy/plugins/lens/public/lens_ui_telemetry/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export * from './factory';
export * from './provider';
17 changes: 17 additions & 0 deletions x-pack/legacy/plugins/lens/public/lens_ui_telemetry/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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 { createContext, useContext } from 'react';

export const LensTelemetryContext = createContext<{
trackClick: (name: string) => void;
trackSuggestionClick: (name: string, suggestionData: unknown) => void;
}>({
trackClick: () => {},
trackSuggestionClick: () => {},
});

export const useLensTelemetry = () => useContext(LensTelemetryContext);
8 changes: 5 additions & 3 deletions x-pack/legacy/plugins/lens/server/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { KibanaConfig } from 'src/legacy/server/kbn_server';
import { Server, KibanaConfig } from 'src/legacy/server/kbn_server';
import { Plugin, CoreSetup, SavedObjectsLegacyService } from 'src/core/server';
import { setupRoutes } from './routes';
import { registerLensUsageCollector } from './usage';
import { registerLensUsageCollector, initializeLensTelemetry } from './usage';

export class LensServer implements Plugin<{}, {}, {}, {}> {
constructor() {}
Expand All @@ -23,10 +23,12 @@ export class LensServer implements Plugin<{}, {}, {}, {}> {
};
};
config: KibanaConfig;
server: Server;
}
) {
setupRoutes(core);
setupRoutes(core, plugins);
registerLensUsageCollector(core, plugins);
initializeLensTelemetry(core, plugins);

return {};
}
Expand Down
Loading