Add collector to ui_metric app so that it reports its own telemetry. (#34210)
* Add collector to ui_metric app so that it reports its own telemetry, so app maintainers don't need to update their apps' collectors. * Remove UI metrics and some collectors from Index Manamgement, ILM, and Rollups.
This commit is contained in:
parent
3ebc1ae941
commit
a6ea8474c5
57
src/legacy/core_plugins/ui_metric/README.md
Normal file
57
src/legacy/core_plugins/ui_metric/README.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
# UI Metric app
|
||||
|
||||
## Purpose
|
||||
|
||||
The purpose of the UI Metric app is to provide a tool for gathering data on how users interact with
|
||||
various UIs within Kibana. It's useful for gathering _aggregate_ information, e.g. "How many times
|
||||
has Button X been clicked" or "How many times has Page Y been viewed".
|
||||
|
||||
With some finagling, it's even possible to add more meaning to the info you gather, such as "How many
|
||||
visualizations were created in less than 5 minutes".
|
||||
|
||||
### What it doesn't do
|
||||
|
||||
The UI Metric app doesn't gather any metadata around a user interaction, e.g. the user's identity,
|
||||
the name of a dashboard they've viewed, or the timestamp of the interaction.
|
||||
|
||||
## How to use it
|
||||
|
||||
To track a user interaction, simply send a `POST` request to `/api/ui_metric/{APP_NAME}/{ACTION_NAME}`,
|
||||
where `APP_MAME` and `ACTION_NAME` are underscore-delimited strings, e.g. `my_app` and `my_action`.
|
||||
|
||||
That's all you need to do!
|
||||
|
||||
### Tracking timed interactions
|
||||
|
||||
If you want to track how long it takes a user to do something, you'll need to implement the timing
|
||||
logic yourself. You'll also need to predefine some buckets into which the UI metric can fall.
|
||||
For example, if you're timing how long it takes to create a visualization, you may decide to
|
||||
measure interactions that take less than 1 minute, 1-5 minutes, 5-20 minutes, and longer than 20 minutes.
|
||||
To track these interactions, you'd use the timed length of the interaction to determine whether to
|
||||
hit `/api/ui_metric/visualize/create_vis_1m`, `/api/ui_metric/visualize/create_vis_5m`,
|
||||
`/api/ui_metric/visualize/create_vis_20m`, etc.
|
||||
|
||||
## How it works
|
||||
|
||||
Under the hood, your app and action will be stored in a saved object of type `user-metric` and the
|
||||
ID `my_app:my_action`. This saved object will have a `count` property which will be incremented every
|
||||
time the above URI is hit.
|
||||
|
||||
These saved objects are automatically consumed by the stats API and surfaced under the
|
||||
`ui_metric` namespace.
|
||||
|
||||
```json
|
||||
{
|
||||
"ui_metric":{
|
||||
"my_app":[
|
||||
{
|
||||
"key":"my_action",
|
||||
"value":3
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
By storing these actions and their counts as key-value pairs, we can add more actions without having
|
||||
to worry about exceeding the 1000-field soft limit in Elasticsearch.
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import { registerUserActionRoute } from './server/routes/api/ui_metric';
|
||||
import { registerUiMetricUsageCollector } from './server/usage/index';
|
||||
|
||||
export default function (kibana) {
|
||||
return new kibana.Plugin({
|
||||
|
@ -30,6 +31,7 @@ export default function (kibana) {
|
|||
|
||||
init: function (server) {
|
||||
registerUserActionRoute(server);
|
||||
registerUiMetricUsageCollector(server);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
58
src/legacy/core_plugins/ui_metric/server/usage/collector.ts
Normal file
58
src/legacy/core_plugins/ui_metric/server/usage/collector.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const UI_METRIC_USAGE_TYPE = 'ui_metric';
|
||||
|
||||
export function registerUiMetricUsageCollector(server: any) {
|
||||
const collector = server.usage.collectorSet.makeUsageCollector({
|
||||
type: UI_METRIC_USAGE_TYPE,
|
||||
fetch: async (callCluster: any) => {
|
||||
const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects;
|
||||
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
|
||||
const savedObjectsClient = new SavedObjectsClient(internalRepository);
|
||||
|
||||
const { saved_objects: rawUiMetrics } = await savedObjectsClient.find({
|
||||
type: 'ui-metric',
|
||||
fields: ['count'],
|
||||
});
|
||||
|
||||
const uiMetricsByAppName = rawUiMetrics.reduce((accum: any, rawUiMetric: any) => {
|
||||
const {
|
||||
id,
|
||||
attributes: { count },
|
||||
} = rawUiMetric;
|
||||
|
||||
const [appName, metricType] = id.split(':');
|
||||
|
||||
if (!accum[appName]) {
|
||||
accum[appName] = [];
|
||||
}
|
||||
|
||||
const pair = { key: metricType, value: count };
|
||||
accum[appName].push(pair);
|
||||
return accum;
|
||||
}, {});
|
||||
|
||||
return uiMetricsByAppName;
|
||||
},
|
||||
});
|
||||
|
||||
server.usage.collectorSet.register(collector);
|
||||
}
|
20
src/legacy/core_plugins/ui_metric/server/usage/index.ts
Normal file
20
src/legacy/core_plugins/ui_metric/server/usage/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { registerUiMetricUsageCollector } from './collector';
|
|
@ -8,7 +8,6 @@ export const BASE_PATH = '/management/elasticsearch/index_lifecycle_management/'
|
|||
export const PLUGIN_ID = 'index_lifecycle_management';
|
||||
export {
|
||||
UIM_APP_NAME,
|
||||
USER_ACTIONS,
|
||||
UIM_APP_LOAD,
|
||||
UIM_POLICY_CREATE,
|
||||
UIM_POLICY_UPDATE,
|
||||
|
|
|
@ -19,19 +19,3 @@ export const UIM_CONFIG_SET_PRIORITY = 'config_set_priority';
|
|||
export const UIM_CONFIG_FREEZE_INDEX = 'config_freeze_index';
|
||||
export const UIM_INDEX_RETRY_STEP = 'index_retry_step';
|
||||
export const UIM_EDIT_CLICK = 'edit_click';
|
||||
|
||||
export const USER_ACTIONS = [
|
||||
UIM_APP_LOAD,
|
||||
UIM_POLICY_CREATE,
|
||||
UIM_POLICY_UPDATE,
|
||||
UIM_POLICY_DELETE,
|
||||
UIM_POLICY_ATTACH_INDEX,
|
||||
UIM_POLICY_ATTACH_INDEX_TEMPLATE,
|
||||
UIM_POLICY_DETACH_INDEX,
|
||||
UIM_CONFIG_COLD_PHASE,
|
||||
UIM_CONFIG_WARM_PHASE,
|
||||
UIM_CONFIG_SET_PRIORITY,
|
||||
UIM_CONFIG_FREEZE_INDEX,
|
||||
UIM_INDEX_RETRY_STEP,
|
||||
UIM_EDIT_CLICK,
|
||||
];
|
||||
|
|
|
@ -13,7 +13,6 @@ import { registerIndexRoutes } from './server/routes/api/index';
|
|||
import { registerLicenseChecker } from './server/lib/register_license_checker';
|
||||
import { PLUGIN_ID } from './common/constants';
|
||||
import { indexLifecycleDataEnricher } from './index_lifecycle_data';
|
||||
import { registerIndexLifecycleManagementUsageCollector } from './server/usage';
|
||||
|
||||
export function indexLifecycleManagement(kibana) {
|
||||
return new kibana.Plugin({
|
||||
|
@ -54,7 +53,6 @@ export function indexLifecycleManagement(kibana) {
|
|||
registerPoliciesRoutes(server);
|
||||
registerLifecycleRoutes(server);
|
||||
registerIndexRoutes(server);
|
||||
registerIndexLifecycleManagementUsageCollector(server);
|
||||
|
||||
if (
|
||||
server.config().get('xpack.ilm.ui.enabled') &&
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* 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 { fetchUiMetrics } from '../../../../server/lib/ui_metric';
|
||||
import { UIM_APP_NAME, USER_ACTIONS } from '../../common/constants';
|
||||
|
||||
const INDEX_LIFECYCLE_MANAGEMENT_USAGE_TYPE = 'index_lifecycle_management';
|
||||
|
||||
export function registerIndexLifecycleManagementUsageCollector(server) {
|
||||
const collector = server.usage.collectorSet.makeUsageCollector({
|
||||
type: INDEX_LIFECYCLE_MANAGEMENT_USAGE_TYPE,
|
||||
fetch: async () => {
|
||||
const uiMetrics = await fetchUiMetrics(server, UIM_APP_NAME, USER_ACTIONS);
|
||||
|
||||
return {
|
||||
ui_metrics: uiMetrics,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
server.usage.collectorSet.register(collector);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
/*
|
||||
* 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 { registerIndexLifecycleManagementUsageCollector } from './collector';
|
|
@ -11,7 +11,6 @@ export * from './index_statuses';
|
|||
export {
|
||||
UIM_APP_NAME,
|
||||
UIM_APP_LOAD,
|
||||
USER_ACTIONS,
|
||||
UIM_UPDATE_SETTINGS,
|
||||
UIM_INDEX_CLEAR_CACHE,
|
||||
UIM_INDEX_CLEAR_CACHE_MANY,
|
||||
|
|
|
@ -33,33 +33,3 @@ export const UIM_DETAIL_PANEL_MAPPING_TAB = 'detail_panel_mapping_tab';
|
|||
export const UIM_DETAIL_PANEL_SETTINGS_TAB = 'detail_panel_settings_tab';
|
||||
export const UIM_DETAIL_PANEL_STATS_TAB = 'detail_panel_stats_tab';
|
||||
export const UIM_DETAIL_PANEL_SUMMARY_TAB = 'detail_panel_summary_tab';
|
||||
|
||||
export const USER_ACTIONS = [
|
||||
UIM_APP_LOAD,
|
||||
UIM_UPDATE_SETTINGS,
|
||||
UIM_INDEX_CLEAR_CACHE,
|
||||
UIM_INDEX_CLEAR_CACHE_MANY,
|
||||
UIM_INDEX_CLOSE,
|
||||
UIM_INDEX_CLOSE_MANY,
|
||||
UIM_INDEX_DELETE,
|
||||
UIM_INDEX_DELETE_MANY,
|
||||
UIM_INDEX_FLUSH,
|
||||
UIM_INDEX_FLUSH_MANY,
|
||||
UIM_INDEX_FORCE_MERGE,
|
||||
UIM_INDEX_FORCE_MERGE_MANY,
|
||||
UIM_INDEX_FREEZE,
|
||||
UIM_INDEX_FREEZE_MANY,
|
||||
UIM_INDEX_OPEN,
|
||||
UIM_INDEX_OPEN_MANY,
|
||||
UIM_INDEX_REFRESH,
|
||||
UIM_INDEX_REFRESH_MANY,
|
||||
UIM_INDEX_SETTINGS_EDIT,
|
||||
UIM_INDEX_UNFREEZE,
|
||||
UIM_INDEX_UNFREEZE_MANY,
|
||||
UIM_SHOW_DETAILS_CLICK,
|
||||
UIM_DETAIL_PANEL_EDIT_SETTINGS_TAB,
|
||||
UIM_DETAIL_PANEL_MAPPING_TAB,
|
||||
UIM_DETAIL_PANEL_SETTINGS_TAB,
|
||||
UIM_DETAIL_PANEL_STATS_TAB,
|
||||
UIM_DETAIL_PANEL_SUMMARY_TAB,
|
||||
];
|
||||
|
|
|
@ -13,7 +13,6 @@ import { registerStatsRoute } from './server/routes/api/stats';
|
|||
import { registerLicenseChecker } from '../../server/lib/register_license_checker';
|
||||
import { PLUGIN } from './common/constants';
|
||||
import { addIndexManagementDataEnricher } from './index_management_data';
|
||||
import { registerIndexManagementUsageCollector } from './server/usage';
|
||||
|
||||
export function indexManagement(kibana) {
|
||||
return new kibana.Plugin({
|
||||
|
@ -35,7 +34,6 @@ export function indexManagement(kibana) {
|
|||
registerSettingsRoutes(router);
|
||||
registerStatsRoute(router);
|
||||
registerMappingRoute(router);
|
||||
registerIndexManagementUsageCollector(server);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* 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 { fetchUiMetrics } from '../../../../server/lib/ui_metric';
|
||||
import { UIM_APP_NAME, USER_ACTIONS } from '../../common/constants';
|
||||
|
||||
const INDEX_MANAGEMENT_USAGE_TYPE = 'index_management';
|
||||
|
||||
export function registerIndexManagementUsageCollector(server) {
|
||||
const collector = server.usage.collectorSet.makeUsageCollector({
|
||||
type: INDEX_MANAGEMENT_USAGE_TYPE,
|
||||
fetch: async () => {
|
||||
const uiMetrics = await fetchUiMetrics(server, UIM_APP_NAME, USER_ACTIONS);
|
||||
|
||||
return {
|
||||
ui_metrics: uiMetrics,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
server.usage.collectorSet.register(collector);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
/*
|
||||
* 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 { registerIndexManagementUsageCollector } from './collector';
|
|
@ -12,7 +12,6 @@ export const CONFIG_ROLLUPS = 'rollups:enableIndexPatterns';
|
|||
|
||||
export {
|
||||
UIM_APP_NAME,
|
||||
USER_ACTIONS,
|
||||
UIM_APP_LOAD,
|
||||
UIM_JOB_CREATE,
|
||||
UIM_JOB_DELETE,
|
||||
|
|
|
@ -20,20 +20,3 @@ export const UIM_DETAIL_PANEL_TERMS_TAB_CLICK = 'detail_panel_terms_tab_click';
|
|||
export const UIM_DETAIL_PANEL_HISTOGRAM_TAB_CLICK = 'detail_panel_histogram_tab_click';
|
||||
export const UIM_DETAIL_PANEL_METRICS_TAB_CLICK = 'detail_panel_metrics_tab_click';
|
||||
export const UIM_DETAIL_PANEL_JSON_TAB_CLICK = 'detail_panel_json_tab_click';
|
||||
|
||||
export const USER_ACTIONS = [
|
||||
UIM_APP_LOAD,
|
||||
UIM_JOB_CREATE,
|
||||
UIM_JOB_DELETE,
|
||||
UIM_JOB_DELETE_MANY,
|
||||
UIM_JOB_START,
|
||||
UIM_JOB_START_MANY,
|
||||
UIM_JOB_STOP,
|
||||
UIM_JOB_STOP_MANY,
|
||||
UIM_SHOW_DETAILS_CLICK,
|
||||
UIM_DETAIL_PANEL_SUMMARY_TAB_CLICK,
|
||||
UIM_DETAIL_PANEL_TERMS_TAB_CLICK,
|
||||
UIM_DETAIL_PANEL_HISTOGRAM_TAB_CLICK,
|
||||
UIM_DETAIL_PANEL_METRICS_TAB_CLICK,
|
||||
UIM_DETAIL_PANEL_JSON_TAB_CLICK,
|
||||
];
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { fetchUiMetrics } from '../../../../server/lib/ui_metric';
|
||||
import { UIM_APP_NAME, USER_ACTIONS } from '../../common';
|
||||
|
||||
const ROLLUP_USAGE_TYPE = 'rollups';
|
||||
|
||||
|
@ -182,8 +180,6 @@ export function registerRollupUsageCollector(server) {
|
|||
rollupVisualizationsFromSavedSearches,
|
||||
} = await fetchRollupVisualizations(kibanaIndex, callCluster, rollupIndexPatternToFlagMap, rollupSavedSearchesToFlagMap);
|
||||
|
||||
const uiMetrics = await fetchUiMetrics(server, UIM_APP_NAME, USER_ACTIONS);
|
||||
|
||||
return {
|
||||
index_patterns: {
|
||||
total: rollupIndexPatterns.length,
|
||||
|
@ -197,7 +193,6 @@ export function registerRollupUsageCollector(server) {
|
|||
total: rollupVisualizationsFromSavedSearches,
|
||||
},
|
||||
},
|
||||
ui_metrics: uiMetrics,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* 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 { Server } from 'hapi';
|
||||
|
||||
export interface UiMetricRecord {
|
||||
metricType: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface UiMetricAndCountKeyValuePair {
|
||||
key: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
// This is a helper method for retrieving user interaction telemetry data stored via the OSS
|
||||
// ui_metric API.
|
||||
export function fetchUiMetrics(
|
||||
server: Server,
|
||||
appName: string,
|
||||
metricTypes: string[]
|
||||
): Promise<UiMetricAndCountKeyValuePair[]> {
|
||||
const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects;
|
||||
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
|
||||
const savedObjectsClient = new SavedObjectsClient(internalRepository);
|
||||
|
||||
async function fetchUiMetric(metricType: string): Promise<UiMetricRecord | undefined> {
|
||||
try {
|
||||
const savedObjectId = `${appName}:${metricType}`;
|
||||
const savedObject = await savedObjectsClient.get('ui-metric', savedObjectId);
|
||||
return { metricType, count: savedObject.attributes.count };
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(metricTypes.map(fetchUiMetric)).then(
|
||||
(userActions): UiMetricAndCountKeyValuePair[] => {
|
||||
const userActionAndCountKeyValuePairs = userActions.reduce(
|
||||
(pairs: UiMetricAndCountKeyValuePair[], uiMetric: UiMetricRecord | undefined) => {
|
||||
// UI metric is undefined if nobody has performed this interaction on the client yet.
|
||||
if (uiMetric !== undefined) {
|
||||
const { metricType, count } = uiMetric;
|
||||
const pair: UiMetricAndCountKeyValuePair = { key: metricType, value: count };
|
||||
pairs.push(pair);
|
||||
}
|
||||
return pairs;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return userActionAndCountKeyValuePairs;
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
/*
|
||||
* 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 { fetchUiMetrics } from './fetch_ui_metrics';
|
Loading…
Reference in a new issue