Skip to content

Commit

Permalink
Telemetry for Dyanmic Actions (Drilldowns) (#84580)
Browse files Browse the repository at this point in the history
* feat: ๐ŸŽธ set up telemetry for UiActions

* feat: ๐ŸŽธ improve ui_actions_enhanced collector

* feat: ๐ŸŽธ namespace ui actions telemetry stats

* refactor: ๐Ÿ’ก improve dynamic actions collector setup

* feat: ๐ŸŽธ add tests for dynamicActionsCollector

* feat: ๐ŸŽธ collect dynamic action trigger statistics

* refactor: ๐Ÿ’ก standartize metric naming

* feat: ๐ŸŽธ aggregate action x trigger counts

* test: ๐Ÿ’ add tests for factory stats

* docs: โœ๏ธ add ui actions enhanced telemetry docs

* fix: ๐Ÿ› revert type change

* refactor: ๐Ÿ’ก make dynamic action stats global

* refactor: ๐Ÿ’ก use global telemetry stats in action factories
  • Loading branch information
streamich authored Dec 2, 2020
1 parent 90a18cc commit cc341b3
Show file tree
Hide file tree
Showing 7 changed files with 536 additions and 7 deletions.
63 changes: 63 additions & 0 deletions x-pack/plugins/ui_actions_enhanced/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,66 @@
Registers commercially licensed generic actions like per panel time range and contains some code that supports drilldown work.

- [__Dashboard drilldown user docs__](https://www.elastic.co/guide/en/kibana/master/drilldowns.html)

## Dynamic Actions Telemetry

Dynamic actions (drilldowns) report telemetry. Below is the summary of dynamic action metrics that are reported using telemetry.

### Dynamic action count

Total count of dynamic actions (drilldowns) on a saved object.

```
dynamicActions.count
```

### Count by factory ID

Count of active dynamic actions (drilldowns) on a saved object by factory ID (drilldown type).

```
dynamicActions.actions.<factory_id>.count
```

For example:

```
dynamicActions.actions.DASHBOARD_TO_DASHBOARD_DRILLDOWN.count
dynamicActions.actions.URL_DRILLDOWN.count
```

### Count by trigger

Count of active dynamic actions (drilldowns) on a saved object by a trigger to which they are attached.

```
dynamicActions.triggers.<trigger>.count
```

For example:

```
dynamicActions.triggers.VALUE_CLICK_TRIGGER.count
dynamicActions.triggers.RANGE_SELECT_TRIGGER.count
```

### Count by factory and trigger

Count of active dynamic actions (drilldowns) on a saved object by a factory ID and trigger ID.

```
dynamicActions.action_triggers.<factory_id>_<trigger>.count
```

For example:

```
dynamicActions.action_triggers.DASHBOARD_TO_DASHBOARD_DRILLDOWN_VALUE_CLICK_TRIGGER.count
dynamicActions.action_triggers.DASHBOARD_TO_DASHBOARD_DRILLDOWN_RANGE_SELECT_TRIGGER.count
dynamicActions.action_triggers.URL_DRILLDOWN_VALUE_CLICK_TRIGGER.count
```

### Factory metrics

Each dynamic action factory (drilldown type) can report its own stats, which is
done using the `.telemetry()` method on dynamic action factories.
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@ import { EnhancementRegistryDefinition } from '../../../../src/plugins/embeddabl
import { SavedObjectReference } from '../../../../src/core/types';
import { ActionFactory, DynamicActionsState, SerializedEvent } from './types';
import { SerializableState } from '../../../../src/plugins/kibana_utils/common';
import { dynamicActionsCollector } from './telemetry/dynamic_actions_collector';
import { dynamicActionFactoriesCollector } from './telemetry/dynamic_action_factories_collector';

export const dynamicActionEnhancement = (
getActionFactory: (id: string) => undefined | ActionFactory
): EnhancementRegistryDefinition => {
return {
id: 'dynamicActions',
telemetry: (state: SerializableState, telemetry: Record<string, any>) => {
let telemetryData = telemetry;
(state as DynamicActionsState).events.forEach((event: SerializedEvent) => {
const factory = getActionFactory(event.action.factoryId);
if (factory) telemetryData = factory.telemetry(event, telemetryData);
});
return telemetryData;
telemetry: (serializableState: SerializableState, stats: Record<string, any>) => {
const state = serializableState as DynamicActionsState;
stats = dynamicActionsCollector(state, stats);
stats = dynamicActionFactoriesCollector(getActionFactory, state, stats);

return stats;
},
extract: (state: SerializableState) => {
const references: SavedObjectReference[] = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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.
*/

/* eslint-disable @typescript-eslint/naming-convention */

import { dynamicActionFactoriesCollector } from './dynamic_action_factories_collector';
import { DynamicActionsState } from '../../common';
import { ActionFactory } from '../types';

type GetActionFactory = (id: string) => undefined | ActionFactory;

const factories: Record<string, ActionFactory> = {
FACTORY_ID_1: ({
id: 'FACTORY_ID_1',
telemetry: jest.fn((state: DynamicActionsState, stats: Record<string, any>) => {
stats.myStat_1 = 1;
stats.myStat_2 = 123;
return stats;
}),
} as unknown) as ActionFactory,
FACTORY_ID_2: ({
id: 'FACTORY_ID_2',
telemetry: jest.fn((state: DynamicActionsState, stats: Record<string, any>) => stats),
} as unknown) as ActionFactory,
FACTORY_ID_3: ({
id: 'FACTORY_ID_3',
telemetry: jest.fn((state: DynamicActionsState, stats: Record<string, any>) => {
stats.myStat_1 = 2;
stats.stringStat = 'abc';
return stats;
}),
} as unknown) as ActionFactory,
};

const getActionFactory: GetActionFactory = (id: string) => factories[id];

const state: DynamicActionsState = {
events: [
{
eventId: 'eventId-1',
triggers: ['TRIGGER_1'],
action: {
factoryId: 'FACTORY_ID_1',
name: 'Click me!',
config: {},
},
},
{
eventId: 'eventId-2',
triggers: ['TRIGGER_2', 'TRIGGER_3'],
action: {
factoryId: 'FACTORY_ID_2',
name: 'Click me, too!',
config: {
doCleanup: true,
},
},
},
{
eventId: 'eventId-3',
triggers: ['TRIGGER_4', 'TRIGGER_1'],
action: {
factoryId: 'FACTORY_ID_3',
name: 'Go to documentation',
config: {
url: 'http://google.com',
iamFeelingLucky: true,
},
},
},
],
};

beforeEach(() => {
Object.values(factories).forEach((factory) => {
((factory.telemetry as unknown) as jest.SpyInstance).mockClear();
});
});

describe('dynamicActionFactoriesCollector', () => {
test('returns empty stats when there are not dynamic actions', () => {
const stats = dynamicActionFactoriesCollector(
getActionFactory,
{
events: [],
},
{}
);

expect(stats).toEqual({});
});

test('calls .telemetry() method of a supplied factory', () => {
const currentState = {
events: [state.events[0]],
};
dynamicActionFactoriesCollector(getActionFactory, currentState, {});

const spy1 = (factories.FACTORY_ID_1.telemetry as unknown) as jest.SpyInstance;
const spy2 = (factories.FACTORY_ID_2.telemetry as unknown) as jest.SpyInstance;

expect(spy1).toHaveBeenCalledTimes(1);
expect(spy2).toHaveBeenCalledTimes(0);

expect(spy1.mock.calls[0][0]).toEqual(currentState.events[0]);
expect(typeof spy1.mock.calls[0][1]).toBe('object');
expect(!!spy1.mock.calls[0][1]).toBe(true);
});

test('returns stats received from factory', () => {
const currentState = {
events: [state.events[0]],
};
const stats = dynamicActionFactoriesCollector(getActionFactory, currentState, {});

expect(stats).toEqual({
myStat_1: 1,
myStat_2: 123,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 { DynamicActionsState } from '../../common';
import { ActionFactory } from '../types';

export const dynamicActionFactoriesCollector = (
getActionFactory: (id: string) => undefined | ActionFactory,
state: DynamicActionsState,
stats: Record<string, any>
): Record<string, any> => {
for (const event of state.events) {
const factory = getActionFactory(event.action.factoryId);

if (factory) {
stats = factory.telemetry(event, stats);
}
}

return stats;
};
Loading

0 comments on commit cc341b3

Please sign in to comment.