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 16 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
21 changes: 16 additions & 5 deletions x-pack/legacy/plugins/apm/typings/elasticsearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,22 @@ declare module 'elasticsearch' {

// eslint-disable-next-line @typescript-eslint/prefer-interface
type FiltersAggregation<SubAggregationMap> = {
buckets: Array<
{
doc_count: number;
} & SubAggregation<SubAggregationMap>
>;
// The filters aggregation can have named filters or anonymous filters,
// which changes the structure of the return
// https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-filters-aggregation.html
buckets: SubAggregationMap extends {
filters: { filters: Record<string, unknown> };
}
? {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm unfamiliar with this TypeScript signature. TIL

[key in keyof SubAggregationMap['filters']['filters']]: {
doc_count: number;
} & SubAggregation<SubAggregationMap>;
}
: Array<
{
doc_count: number;
} & SubAggregation<SubAggregationMap>
>;
};

type SamplerAggregation<SubAggregationMap> = SubAggregation<
Expand Down
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
16 changes: 16 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,21 @@
"type": "keyword"
}
}
},
"lens-ui-telemetry": {
"properties": {
"name": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"date": {
"type": "date"
},
"count": {
"type": "integer"
}
}
}
}
14 changes: 14 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 { trackUiEvent } from '../lens_ui_telemetry';

interface State {
isLoading: boolean;
Expand Down Expand Up @@ -84,6 +85,8 @@ export function App({
const subscription = dataShim.filter.filterManager.getUpdates$().subscribe({
next: () => {
setState(s => ({ ...s, filters: dataShim.filter.filterManager.getFilters() }));

trackUiEvent('app_filters_updated');
},
});
return () => {
Expand Down Expand Up @@ -198,6 +201,7 @@ export function App({
}
})
.catch(() => {
trackUiEvent('save_failed');
core.notifications.toasts.addDanger(
i18n.translate('xpack.lens.editorFrame.docSavingError', {
defaultMessage: 'Error saving document',
Expand All @@ -214,6 +218,16 @@ export function App({
screenTitle={'lens'}
onQuerySubmit={payload => {
const { dateRange, query } = payload;

if (
dateRange.from !== state.dateRange.fromDate ||
dateRange.to !== state.dateRange.toDate
) {
trackUiEvent('app_date_change');
} else {
trackUiEvent('app_query_change');
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this actually interesting? I'm not sure it's worth collecting, as I expect it to be just super noisy.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I could combine this with filter changes, but I do think it's interesting

}

setState(s => ({
...s,
dateRange: {
Expand Down
21 changes: 21 additions & 0 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,12 @@ import {
} from '../datatable_visualization_plugin';
import { App } from './app';
import { EditorFrameInstance } from '../types';
import {
LensReportManager,
setReportManager,
stopReportManager,
trackUiEvent,
} from '../lens_ui_telemetry';

export interface LensPluginStartDependencies {
data: DataPublicPluginStart;
Expand Down Expand Up @@ -63,7 +69,19 @@ export class AppPlugin {

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

// Sets it in memory for future click tracking
// If there was any previous manager set, clear it
stopReportManager();
setReportManager(
wylieconlon marked this conversation as resolved.
Show resolved Hide resolved
new LensReportManager({
storage: new Storage(localStorage),
basePath: core.http.basePath.get(),
http: core.http,
})
);

const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => {
trackUiEvent('loaded');
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this worth tracking? Maybe. 404 certainly is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we don't track some kind of loaded event it won't be possible to create any kind of funnel, because these metrics are in a window and no other metrics are

return (
<App
core={core}
Expand All @@ -85,6 +103,7 @@ export class AppPlugin {
};

function NotFound() {
trackUiEvent('loaded_404');
return <FormattedMessage id="xpack.lens.app404" defaultMessage="404 Not Found" />;
}

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

stopReportManager();

// TODO this will be handled by the plugin platform itself
indexPatternDatasourceStop();
xyVisualizationStop();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { i18n } from '@kbn/i18n';
import { Visualization, FramePublicAPI, Datasource } from '../../types';
import { Action } from './state_management';
import { getSuggestions, switchToSuggestion, Suggestion } from './suggestion_helpers';
import { trackUiEvent } from '../../lens_ui_telemetry';

interface VisualizationSelection {
visualizationId: string;
Expand Down Expand Up @@ -76,6 +77,8 @@ export function ChartSwitch(props: Props) {
const commitSelection = (selection: VisualizationSelection) => {
setFlyoutOpen(false);

trackUiEvent(`chart_switch`);

switchToSuggestion(
props.framePublicAPI,
props.dispatch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { getSuggestions, switchToSuggestion } from './suggestion_helpers';
import { ExpressionRenderer } from '../../../../../../../src/legacy/core_plugins/expressions/public';
import { prependDatasourceExpression, prependKibanaContext } from './expression_helpers';
import { debouncedComponent } from '../../debounced_component';
import { trackUiEvent, trackSuggestionEvent } from '../../lens_ui_telemetry';

const MAX_SUGGESTIONS_DISPLAYED = 5;

Expand Down Expand Up @@ -227,6 +228,7 @@ export function SuggestionPanel({

function rollbackToCurrentVisualization() {
if (lastSelectedSuggestion !== -1) {
trackSuggestionEvent('back_to_current');
setLastSelectedSuggestion(-1);
dispatch({
type: 'ROLLBACK_SUGGESTION',
Expand Down Expand Up @@ -261,6 +263,7 @@ export function SuggestionPanel({
data-test-subj="lensSubmitSuggestion"
size="xs"
onClick={() => {
trackUiEvent('suggestion_confirmed');
dispatch({
type: 'SUBMIT_SUGGESTION',
});
Expand Down Expand Up @@ -307,9 +310,11 @@ export function SuggestionPanel({
ExpressionRenderer={ExpressionRendererComponent}
key={index}
onSelect={() => {
trackUiEvent('suggestion_clicked');
if (lastSelectedSuggestion === index) {
rollbackToCurrentVisualization();
} else {
trackSuggestionEvent(`index_${index}_of_${suggestions.length}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe suggestion_${index}_of_${suggestions.length}? I had to stare at this for a bit to realize that index was not an Elasticsearch index name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's why it's a different function call- Raya wanted to track the position of suggestions. I'll rename to position

setLastSelectedSuggestion(index);
switchToSuggestion(frame, dispatch, suggestion);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { DragDrop, DragContext } from '../../drag_drop';
import { getSuggestions, switchToSuggestion } from './suggestion_helpers';
import { buildExpression } from './expression_helpers';
import { debouncedComponent } from '../../debounced_component';
import { trackUiEvent } from '../../lens_ui_telemetry';

export interface WorkspacePanelProps {
activeVisualizationId: string | null;
Expand Down Expand Up @@ -60,6 +61,7 @@ export function InnerWorkspacePanel({
core,
ExpressionRenderer: ExpressionRendererComponent,
}: WorkspacePanelProps) {
const [isEmpty, setIsEmpty] = useState(true);
wylieconlon marked this conversation as resolved.
Show resolved Hide resolved
const IS_DARK_THEME = core.uiSettings.get('theme:darkMode');
const emptyStateGraphicURL = IS_DARK_THEME
? '/plugins/lens/assets/lens_app_graphic_dark_2x.png'
Expand Down Expand Up @@ -93,6 +95,7 @@ export function InnerWorkspacePanel({

function onDrop() {
if (suggestionForDraggedField) {
trackUiEvent(isEmpty ? 'workspace_drop_on_empty' : 'workspace_drop_non_empty');
switchToSuggestion(
framePublicAPI,
dispatch,
Expand Down Expand Up @@ -153,13 +156,19 @@ export function InnerWorkspacePanel({
: null;
const expression = useMemo(() => {
try {
return buildExpression({
const tempExpression = buildExpression({
visualization: activeVisualization,
visualizationState,
datasourceMap,
datasourceStates,
framePublicAPI,
});
if (tempExpression) {
setIsEmpty(false);
} else {
setIsEmpty(true);
}
return tempExpression;
} catch (e) {
setExpressionError(e.toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, { useState } from 'react';
import { EuiButtonEmpty, EuiPopover, EuiSelectable, EuiButtonEmptyProps } from '@elastic/eui';
import { EuiSelectableProps } from '@elastic/eui/src/components/selectable/selectable';
import { IndexPatternRef } from './types';
import { trackUiEvent } from '../lens_ui_telemetry';

export interface ChangeIndexPatternTriggerProps extends EuiButtonEmptyProps {
label: string;
Expand Down Expand Up @@ -74,6 +75,7 @@ export function ChangeIndexPattern({
const choice = (choices.find(({ checked }) => checked) as unknown) as {
value: string;
};
trackUiEvent('indexpattern_changed');
onChangeIndexPattern(choice.value);
setPopoverIsOpen(false);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
IndexPatternField,
IndexPatternRef,
} from './types';
import { trackUiEvent } from '../lens_ui_telemetry';
import { syncExistingFields } from './loader';
import { fieldExists } from './pure_helpers';

Expand Down Expand Up @@ -301,6 +302,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
defaultMessage: 'Clear name and type filters',
}),
onClick: () => {
trackUiEvent('indexpattern_filters_cleared');
setLocalState(s => ({
...s,
nameFilter: '',
Expand Down Expand Up @@ -370,14 +372,15 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
key={type}
icon={localState.typeFilter.includes(type) ? 'check' : 'empty'}
data-test-subj={`typeFilter-${type}`}
onClick={() =>
onClick={() => {
trackUiEvent('indexpattern_type_filter_toggled');
setLocalState(s => ({
...s,
typeFilter: localState.typeFilter.includes(type)
? localState.typeFilter.filter(t => t !== type)
: [...localState.typeFilter, type],
}))
}
}));
}}
>
<LensFieldIcon type={type} /> {fieldTypeNames[type]}
</EuiContextMenuItem>
Expand All @@ -388,6 +391,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({
compressed
checked={!showEmptyFields}
onChange={() => {
trackUiEvent('indexpattern_existence_toggled');
onToggleEmptyFields();
}}
label={i18n.translate('xpack.lens.indexPatterns.toggleEmptyFieldsSwitch', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { DragContextState, ChildDragDropProvider, DragDrop } from '../../drag_dr
import { changeColumn, deleteColumn } from '../state_helpers';
import { isDraggedField, hasField } from '../utils';
import { IndexPatternPrivateState, IndexPatternField } from '../types';
import { trackUiEvent } from '../../lens_ui_telemetry';

export type IndexPatternDimensionPanelProps = DatasourceDimensionPanelProps & {
state: IndexPatternPrivateState;
Expand Down Expand Up @@ -137,6 +138,8 @@ export const IndexPatternDimensionPanel = memo(function IndexPatternDimensionPan
field: droppedItem.field,
});

trackUiEvent('drop_onto_dimension');

props.setState(
changeColumn({
state: props.state,
Expand Down Expand Up @@ -170,6 +173,7 @@ export const IndexPatternDimensionPanel = memo(function IndexPatternDimensionPan
defaultMessage: 'Remove configuration',
})}
onClick={() => {
trackUiEvent('indexpattern_dimension_removed');
props.setState(
deleteColumn({
state: props.state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { LensFieldIcon } from '../lens_field_icon';
import { DataType } from '../../types';
import { OperationFieldSupportMatrix } from './dimension_panel';
import { IndexPattern, IndexPatternField, IndexPatternPrivateState } from '../types';
import { trackUiEvent } from '../../lens_ui_telemetry';
import { fieldExists } from '../pure_helpers';

export type FieldChoice =
Expand Down Expand Up @@ -170,6 +171,8 @@ export function FieldSelect({
return;
}

trackUiEvent('indexpattern_dimension_field_changed');

onChoose((choices[0].value as unknown) as FieldChoice);
}}
renderOption={(option, searchValue) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { FieldSelect } from './field_select';
import { hasField } from '../utils';
import { BucketNestingEditor } from './bucket_nesting_editor';
import { IndexPattern, IndexPatternField } from '../types';
import { trackUiEvent } from '../../lens_ui_telemetry';

const operationPanels = getOperationDisplay();

Expand Down Expand Up @@ -162,10 +163,12 @@ export function PopoverEditor(props: PopoverEditorProps) {
} else {
setInvalidOperationType(operationType);
}
trackUiEvent(`indexpattern_dimension_operation_${operationType}`);
return;
}
if (!compatibleWithCurrentField) {
setInvalidOperationType(operationType);
trackUiEvent(`indexpattern_dimension_operation_${operationType}`);
return;
}
if (incompatibleSelectedOperationType) {
Expand All @@ -182,6 +185,9 @@ export function PopoverEditor(props: PopoverEditorProps) {
indexPattern: currentIndexPattern,
field: hasField(selectedColumn) ? fieldMap[selectedColumn.sourceField] : undefined,
});
trackUiEvent(
`indexpattern_dimension_operation_from_${selectedColumn.operationType}_to_${operationType}`
Copy link
Contributor

Choose a reason for hiding this comment

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

This is fine, but I do wonder if we'd benefit from some systematic naming convention?

{namespace}/{action}/{key:value action params}

index_pattern_dimension_operation/set/date_histogram
index_pattern_dimension_operation/change/from:foo,to:bar

Not saying that ^^^ should be it, but maybe we should think of one, so we can filter systematically?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I might remove the operation tracking, it's not obvious that we can get value from measuring this

);
setState(
changeColumn({
state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { DatasourceDataPanelProps, DataType } from '../types';
import { BucketedAggregation, FieldStatsResponse } from '../../common';
import { IndexPattern, IndexPatternField } from './types';
import { LensFieldIcon, getColorForDataType } from './lens_field_icon';
import { trackUiEvent } from '../lens_ui_telemetry';

export interface FieldItemProps {
core: DatasourceDataPanelProps['core'];
Expand Down Expand Up @@ -141,6 +142,7 @@ export function FieldItem(props: FieldItemProps) {
function togglePopover() {
setOpen(!infoIsOpen);
if (!infoIsOpen) {
trackUiEvent('indexpattern_field_info_click');
fetchData();
}
}
Expand Down
Loading