Skip to content

Commit

Permalink
[Lens] Track actions in the UI by time
Browse files Browse the repository at this point in the history
  • Loading branch information
wylieconlon committed Oct 11, 2019
1 parent 0a85478 commit a832b1e
Show file tree
Hide file tree
Showing 16 changed files with 516 additions and 38 deletions.
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');
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);
}

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() {
if (this.timer) {
clearInterval(this.timer);
}
}

private async postToServer() {
try {
await this.http.post(`${this.basePath}${BASE_API_URL}/telemetry`, {
body: JSON.stringify({
clicks: this.clicks,
suggestionClicks: this.clicks,
}),
});
this.clicks = [];
this.suggestionClicks = [];
this.write();
} catch (e) {
// Maybe show an error
console.log(e);
}
}
}
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

0 comments on commit a832b1e

Please sign in to comment.