diff --git a/src/legacy/server/usage/classes/collector_set.js b/src/legacy/server/usage/classes/collector_set.js index 8bcb75e39959f..eb2944df853b6 100644 --- a/src/legacy/server/usage/classes/collector_set.js +++ b/src/legacy/server/usage/classes/collector_set.js @@ -107,6 +107,7 @@ export class CollectorSet { // convert an array of fetched stats results into key/object toObject(statsData) { + if (!statsData) return {}; return statsData.reduce((accumulatedStats, { type, result }) => { return { ...accumulatedStats, diff --git a/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js b/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js index 3e95e457ce5d4..cd0d6d47bc5e6 100644 --- a/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js @@ -37,6 +37,9 @@ describe('', () => { beforeEach(() => { server = sinon.fakeServer.create(); server.respondImmediately = true; + // We make requests to APIs which don't impact the UX, e.g. UI metric telemetry, + // and we can mock them all with a 200 instead of mocking each one individually. + server.respondWith([200, {}, '']); // Register helpers to mock Http Requests ({ diff --git a/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js b/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js index 645791da10dba..7baee35e65835 100644 --- a/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js @@ -34,6 +34,9 @@ describe('', () => { beforeEach(() => { server = sinon.fakeServer.create(); server.respondImmediately = true; + // We make requests to APIs which don't impact the UX, e.g. UI metric telemetry, + // and we can mock them all with a 200 instead of mocking each one individually. + server.respondWith([200, {}, '']); // Register helpers to mock Http Requests ({ setLoadFollowerIndicesResponse } = registerHttpRequestMockHelpers(server)); diff --git a/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js b/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js index f99c138b4ea9b..3c8372ffba8dc 100644 --- a/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js +++ b/x-pack/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js @@ -39,6 +39,9 @@ describe('', () => { beforeEach(() => { server = sinon.fakeServer.create(); server.respondImmediately = true; + // We make requests to APIs which don't impact the UX, e.g. UI metric telemetry, + // and we can mock them all with a 200 instead of mocking each one individually. + server.respondWith([200, {}, '']); // Register helpers to mock Http Requests const { setLoadFollowerIndicesResponse } = registerHttpRequestMockHelpers(server); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/constants/index.js b/x-pack/plugins/cross_cluster_replication/public/app/constants/index.js index 11fd188374b53..ed2ae52ce162f 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/constants/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/constants/index.js @@ -6,3 +6,4 @@ export { API_STATUS } from './api'; export { SECTIONS } from './sections'; +export * from './ui_metric'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/constants/ui_metric.js b/x-pack/plugins/cross_cluster_replication/public/app/constants/ui_metric.js new file mode 100644 index 0000000000000..d5c9d4dc70cfc --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/constants/ui_metric.js @@ -0,0 +1,25 @@ +/* + * 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 const UIM_APP_NAME = 'cross_cluster_replication'; + +export const UIM_FOLLOWER_INDEX_LIST_LOAD = 'follower_index_list_load'; +export const UIM_FOLLOWER_INDEX_CREATE = 'follower_index_create'; +export const UIM_FOLLOWER_INDEX_UPDATE = 'follower_index_update'; +export const UIM_FOLLOWER_INDEX_PAUSE = 'follower_index_pause'; +export const UIM_FOLLOWER_INDEX_PAUSE_MANY = 'follower_index_pause_many'; +export const UIM_FOLLOWER_INDEX_RESUME = 'follower_index_resume'; +export const UIM_FOLLOWER_INDEX_RESUME_MANY = 'follower_index_resume_many'; +export const UIM_FOLLOWER_INDEX_UNFOLLOW = 'follower_index_unfollow'; +export const UIM_FOLLOWER_INDEX_UNFOLLOW_MANY = 'follower_index_unfollow_many'; +export const UIM_FOLLOWER_INDEX_USE_ADVANCED_OPTIONS = 'follower_index_use_advanced_options'; +export const UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK = 'follower_index_show_details_click'; +export const UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD = 'auto_follow_patter_list_load'; +export const UIM_AUTO_FOLLOW_PATTERN_CREATE = 'auto_follow_pattern_create'; +export const UIM_AUTO_FOLLOW_PATTERN_UPDATE = 'auto_follow_pattern_update'; +export const UIM_AUTO_FOLLOW_PATTERN_DELETE = 'auto_follow_pattern_delete'; +export const UIM_AUTO_FOLLOW_PATTERN_DELETE_MANY = 'auto_follow_pattern_delete_many'; +export const UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK = 'auto_follow_pattern_show_details_click'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js index b3551998070b8..80e3b74c522b4 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js @@ -19,10 +19,12 @@ import { import routing from '../../../services/routing'; import { extractQueryParams } from '../../../services/query_params'; -import { API_STATUS } from '../../../constants'; +import { trackUiMetric } from '../../../services/track_ui_metric'; +import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD } from '../../../constants'; import { SectionLoading, SectionError, SectionUnauthorized } from '../../../components'; import { AutoFollowPatternTable, DetailPanel } from './components'; + const REFRESH_RATE_MS = 30000; const getQueryParamPattern = ({ location: { search } }) => { @@ -58,6 +60,7 @@ export class AutoFollowPatternList extends PureComponent { componentDidMount() { const { loadAutoFollowPatterns, loadAutoFollowStats, selectAutoFollowPattern, history } = this.props; + trackUiMetric(UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD); loadAutoFollowPatterns(); loadAutoFollowStats(); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js index a5b46ff3e17f0..8a18bba64960d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js @@ -17,9 +17,10 @@ import { EuiToolTip, EuiOverlayMask, } from '@elastic/eui'; -import { API_STATUS } from '../../../../../constants'; +import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK } from '../../../../../constants'; import { AutoFollowPatternDeleteProvider } from '../../../../../components'; import routing from '../../../../../services/routing'; +import { trackUiMetric } from '../../../../../services/track_ui_metric'; export class AutoFollowPatternTable extends PureComponent { static propTypes = { @@ -75,7 +76,10 @@ export class AutoFollowPatternTable extends PureComponent { render: (name) => { return ( selectAutoFollowPattern(name)} + onClick={() => { + trackUiMetric(UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK); + selectAutoFollowPattern(name); + }} data-test-subj="ccrAutoFollowPatternListPatternLink" > {name} diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js index 0f663bc71313a..03151a5672aeb 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js @@ -16,13 +16,14 @@ import { EuiLoadingKibana, EuiOverlayMask, } from '@elastic/eui'; -import { API_STATUS } from '../../../../../constants'; +import { API_STATUS, UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK } from '../../../../../constants'; import { FollowerIndexPauseProvider, FollowerIndexResumeProvider, FollowerIndexUnfollowProvider } from '../../../../../components'; import routing from '../../../../../services/routing'; +import { trackUiMetric } from '../../../../../services/track_ui_metric'; import { ContextMenu } from '../context_menu'; export class FollowerIndicesTable extends PureComponent { @@ -189,7 +190,10 @@ export class FollowerIndicesTable extends PureComponent { render: (name) => { return ( selectFollowerIndex(name)} + onClick={() => { + trackUiMetric(UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK); + selectFollowerIndex(name); + }} data-test-subj="ccrFollowerIndexListFollowerIndexLink" > {name} diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js index 16f6e89ec054d..5ff4e2972fee6 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js @@ -19,7 +19,8 @@ import { import routing from '../../../services/routing'; import { extractQueryParams } from '../../../services/query_params'; -import { API_STATUS } from '../../../constants'; +import { trackUiMetric } from '../../../services/track_ui_metric'; +import { API_STATUS, UIM_FOLLOWER_INDEX_LIST_LOAD } from '../../../constants'; import { SectionLoading, SectionError, SectionUnauthorized } from '../../../components'; import { FollowerIndicesTable, DetailPanel } from './components'; @@ -57,6 +58,7 @@ export class FollowerIndicesList extends PureComponent { componentDidMount() { const { loadFollowerIndices, selectFollowerIndex, history } = this.props; + trackUiMetric(UIM_FOLLOWER_INDEX_LIST_LOAD); loadFollowerIndices(); // Select the pattern in the URL query params diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/api.js b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js index 1f81c4b02aad5..cfbfcbb61966c 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/services/api.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js @@ -11,6 +11,23 @@ import { API_INDEX_MANAGEMENT_BASE_PATH, } from '../../../common/constants'; import { arrify } from '../../../common/services/utils'; +import { + UIM_FOLLOWER_INDEX_CREATE, + UIM_FOLLOWER_INDEX_UPDATE, + UIM_FOLLOWER_INDEX_PAUSE, + UIM_FOLLOWER_INDEX_PAUSE_MANY, + UIM_FOLLOWER_INDEX_RESUME, + UIM_FOLLOWER_INDEX_RESUME_MANY, + UIM_FOLLOWER_INDEX_UNFOLLOW, + UIM_FOLLOWER_INDEX_UNFOLLOW_MANY, + UIM_FOLLOWER_INDEX_USE_ADVANCED_OPTIONS, + UIM_AUTO_FOLLOW_PATTERN_CREATE, + UIM_AUTO_FOLLOW_PATTERN_UPDATE, + UIM_AUTO_FOLLOW_PATTERN_DELETE, + UIM_AUTO_FOLLOW_PATTERN_DELETE_MANY, +} from '../constants'; +import { trackUserRequest } from './track_ui_metric'; +import { areAllSettingsDefault } from './follower_index_default_settings'; const apiPrefix = chrome.addBasePath(API_BASE_PATH); const apiPrefixRemoteClusters = chrome.addBasePath(API_REMOTE_CLUSTERS_BASE_PATH); @@ -20,8 +37,8 @@ const apiPrefixIndexManagement = chrome.addBasePath(API_INDEX_MANAGEMENT_BASE_PA // to access it within our React app. let httpClient; -// The deffered AngularJS api allows us to create deferred promise -// to be resolved later. This allows us to cancel in flight Http Requests +// The deferred AngularJS api allows us to create a deferred promise +// to be resolved later. This allows us to cancel in-flight http Requests. // https://docs.angularjs.org/api/ng/service/$q#the-deferred-api let $q; @@ -30,10 +47,16 @@ export function setHttpClient(client, $deffered) { $q = $deffered; } +export const getHttpClient = () => { + return httpClient; +}; + // --- const extractData = (response) => response.data; +const createIdString = (ids) => ids.map(id => encodeURIComponent(id)).join(','); + /* Auto Follow Pattern */ export const loadAutoFollowPatterns = () => ( httpClient.get(`${apiPrefix}/auto_follow_patterns`).then(extractData) @@ -47,18 +70,22 @@ export const loadRemoteClusters = () => ( httpClient.get(apiPrefixRemoteClusters).then(extractData) ); -export const createAutoFollowPattern = (autoFollowPattern) => ( - httpClient.post(`${apiPrefix}/auto_follow_patterns`, autoFollowPattern).then(extractData) -); +export const createAutoFollowPattern = (autoFollowPattern) => { + const request = httpClient.post(`${apiPrefix}/auto_follow_patterns`, autoFollowPattern); + return trackUserRequest(request, UIM_AUTO_FOLLOW_PATTERN_CREATE).then(extractData); +}; -export const updateAutoFollowPattern = (id, autoFollowPattern) => ( - httpClient.put(`${apiPrefix}/auto_follow_patterns/${encodeURIComponent(id)}`, autoFollowPattern).then(extractData) -); +export const updateAutoFollowPattern = (id, autoFollowPattern) => { + const request = httpClient.put(`${apiPrefix}/auto_follow_patterns/${encodeURIComponent(id)}`, autoFollowPattern); + return trackUserRequest(request, UIM_AUTO_FOLLOW_PATTERN_UPDATE).then(extractData); +}; export const deleteAutoFollowPattern = (id) => { - const ids = arrify(id).map(_id => encodeURIComponent(_id)).join(','); - - return httpClient.delete(`${apiPrefix}/auto_follow_patterns/${ids}`).then(extractData); + const ids = arrify(id); + const idString = ids.map(_id => encodeURIComponent(_id)).join(','); + const request = httpClient.delete(`${apiPrefix}/auto_follow_patterns/${idString}`); + const uiMetric = ids.length > 1 ? UIM_AUTO_FOLLOW_PATTERN_DELETE_MANY : UIM_AUTO_FOLLOW_PATTERN_DELETE; + return trackUserRequest(request, uiMetric).then(extractData); }; /* Follower Index */ @@ -70,28 +97,49 @@ export const getFollowerIndex = (id) => ( httpClient.get(`${apiPrefix}/follower_indices/${encodeURIComponent(id)}`).then(extractData) ); -export const createFollowerIndex = (followerIndex) => ( - httpClient.post(`${apiPrefix}/follower_indices`, followerIndex).then(extractData) -); +export const createFollowerIndex = (followerIndex) => { + const uiMetrics = [UIM_FOLLOWER_INDEX_CREATE]; + const isUsingAdvancedSettings = !areAllSettingsDefault(followerIndex); + if (isUsingAdvancedSettings) { + uiMetrics.push(UIM_FOLLOWER_INDEX_USE_ADVANCED_OPTIONS); + } + const request = httpClient.post(`${apiPrefix}/follower_indices`, followerIndex); + return trackUserRequest(request, uiMetrics.join(',')).then(extractData); +}; export const pauseFollowerIndex = (id) => { - const ids = arrify(id).map(_id => encodeURIComponent(_id)).join(','); - return httpClient.put(`${apiPrefix}/follower_indices/${ids}/pause`).then(extractData); + const ids = arrify(id); + const idString = createIdString(ids); + const request = httpClient.put(`${apiPrefix}/follower_indices/${idString}/pause`); + const uiMetric = ids.length > 1 ? UIM_FOLLOWER_INDEX_PAUSE_MANY : UIM_FOLLOWER_INDEX_PAUSE; + return trackUserRequest(request, uiMetric).then(extractData); }; export const resumeFollowerIndex = (id) => { - const ids = arrify(id).map(_id => encodeURIComponent(_id)).join(','); - return httpClient.put(`${apiPrefix}/follower_indices/${ids}/resume`).then(extractData); + const ids = arrify(id); + const idString = createIdString(ids); + const request = httpClient.put(`${apiPrefix}/follower_indices/${idString}/resume`); + const uiMetric = ids.length > 1 ? UIM_FOLLOWER_INDEX_RESUME_MANY : UIM_FOLLOWER_INDEX_RESUME; + return trackUserRequest(request, uiMetric).then(extractData); }; export const unfollowLeaderIndex = (id) => { - const ids = arrify(id).map(_id => encodeURIComponent(_id)).join(','); - return httpClient.put(`${apiPrefix}/follower_indices/${ids}/unfollow`).then(extractData); + const ids = arrify(id); + const idString = createIdString(ids); + const request = httpClient.put(`${apiPrefix}/follower_indices/${idString}/unfollow`); + const uiMetric = ids.length > 1 ? UIM_FOLLOWER_INDEX_UNFOLLOW_MANY : UIM_FOLLOWER_INDEX_UNFOLLOW; + return trackUserRequest(request, uiMetric).then(extractData); }; -export const updateFollowerIndex = (id, followerIndex) => ( - httpClient.put(`${apiPrefix}/follower_indices/${encodeURIComponent(id)}`, followerIndex).then(extractData) -); +export const updateFollowerIndex = (id, followerIndex) => { + const uiMetrics = [UIM_FOLLOWER_INDEX_UPDATE]; + const isUsingAdvancedSettings = !areAllSettingsDefault(followerIndex); + if (isUsingAdvancedSettings) { + uiMetrics.push(UIM_FOLLOWER_INDEX_USE_ADVANCED_OPTIONS); + } + const request = httpClient.put(`${apiPrefix}/follower_indices/${encodeURIComponent(id)}`, followerIndex); + return trackUserRequest(request, uiMetrics.join(',')).then(extractData); +}; /* Stats */ export const loadAutoFollowStats = () => ( diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/track_ui_metric.js b/x-pack/plugins/cross_cluster_replication/public/app/services/track_ui_metric.js new file mode 100644 index 0000000000000..0cae59fbea5c7 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/track_ui_metric.js @@ -0,0 +1,28 @@ +/* + * 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 { createUiMetricUri } from '../../../../../common/ui_metric'; +import { UIM_APP_NAME } from '../constants'; +import { getHttpClient } from './api'; + +export function trackUiMetric(actionType) { + const uiMetricUri = createUiMetricUri(UIM_APP_NAME, actionType); + getHttpClient().post(uiMetricUri); +} + +/** + * Transparently return provided request Promise, while allowing us to track + * a successful completion of the request. + */ +export function trackUserRequest(request, actionType) { + // Only track successful actions. + return request.then(response => { + trackUiMetric(actionType); + // We return the response immediately without waiting for the tracking request to resolve, + // to avoid adding additional latency. + return response; + }); +} diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js index 27b013cdd200d..dd3044dc491bd 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/remote_clusters_list.test.js @@ -37,6 +37,9 @@ describe('', () => { beforeEach(() => { server = sinon.fakeServer.create(); server.respondImmediately = true; + // We make requests to APIs which don't impact the UX, e.g. UI metric telemetry, + // and we can mock them all with a 200 instead of mocking each one individually. + server.respondWith([200, {}, '']); // Register helpers to mock Http Requests ({ setLoadRemoteClustersResponse, setDeleteRemoteClusterResponse } = registerHttpRequestMockHelpers(server)); diff --git a/x-pack/plugins/remote_clusters/public/app.js b/x-pack/plugins/remote_clusters/public/app.js index 1a517e860bbbe..4062f6e476b8f 100644 --- a/x-pack/plugins/remote_clusters/public/app.js +++ b/x-pack/plugins/remote_clusters/public/app.js @@ -8,8 +8,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { Switch, Route, Redirect } from 'react-router-dom'; -import { CRUD_APP_BASE_PATH } from './constants'; -import { registerRouter, setUserHasLeftApp } from './services'; +import { CRUD_APP_BASE_PATH, UIM_APP_LOAD } from './constants'; +import { registerRouter, setUserHasLeftApp, trackUiMetric } from './services'; import { RemoteClusterList, RemoteClusterAdd, RemoteClusterEdit } from './sections'; export class App extends Component { @@ -33,6 +33,10 @@ export class App extends Component { registerRouter(router); } + componentDidMount() { + trackUiMetric(UIM_APP_LOAD); + } + componentWillUnmount() { // Set internal flag so we can prevent reacting to route changes internally. setUserHasLeftApp(true); diff --git a/x-pack/plugins/remote_clusters/public/constants/index.js b/x-pack/plugins/remote_clusters/public/constants/index.js index f71725d23b4e0..949ad61e0d17d 100644 --- a/x-pack/plugins/remote_clusters/public/constants/index.js +++ b/x-pack/plugins/remote_clusters/public/constants/index.js @@ -7,3 +7,5 @@ export { CRUD_APP_BASE_PATH, } from './paths'; + +export * from './ui_metric'; diff --git a/x-pack/plugins/remote_clusters/public/constants/ui_metric.js b/x-pack/plugins/remote_clusters/public/constants/ui_metric.js new file mode 100644 index 0000000000000..50e4fb9a19275 --- /dev/null +++ b/x-pack/plugins/remote_clusters/public/constants/ui_metric.js @@ -0,0 +1,14 @@ +/* + * 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 const UIM_APP_NAME = 'remote_clusters'; + +export const UIM_APP_LOAD = 'app_load'; +export const UIM_CLUSTER_ADD = 'cluster_add'; +export const UIM_CLUSTER_UPDATE = 'cluster_update'; +export const UIM_CLUSTER_REMOVE = 'cluster_remove'; +export const UIM_CLUSTER_REMOVE_MANY = 'cluster_remove_many'; +export const UIM_SHOW_DETAILS_CLICK = 'show_details_click'; diff --git a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_edit/remote_cluster_edit.js b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_edit/remote_cluster_edit.js index c4c40374b42b2..ebe2331c95a9d 100644 --- a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_edit/remote_cluster_edit.js +++ b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_edit/remote_cluster_edit.js @@ -23,7 +23,14 @@ import { } from '@elastic/eui'; import { CRUD_APP_BASE_PATH } from '../../constants'; -import { buildListBreadcrumb, editBreadcrumb, extractQueryParams, getRouter, getRouterLinkProps, redirect } from '../../services'; +import { + buildListBreadcrumb, + editBreadcrumb, + extractQueryParams, + getRouter, + getRouterLinkProps, + redirect, +} from '../../services'; import { RemoteClusterPageTitle, RemoteClusterForm, ConfiguredByNodeWarning } from '../components'; const disabledFields = { diff --git a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js index 7b0d6f94c4bd0..194cf7075af60 100644 --- a/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js +++ b/x-pack/plugins/remote_clusters/public/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js @@ -20,8 +20,8 @@ import { EuiToolTip, } from '@elastic/eui'; -import { CRUD_APP_BASE_PATH } from '../../../constants'; -import { getRouterLinkProps } from '../../../services'; +import { CRUD_APP_BASE_PATH, UIM_SHOW_DETAILS_CLICK } from '../../../constants'; +import { getRouterLinkProps, trackUiMetric } from '../../../services'; import { ConnectionStatus, RemoveClusterButtonProvider } from '../components'; export const RemoteClusterTable = injectI18n( @@ -93,7 +93,10 @@ export const RemoteClusterTable = injectI18n( const link = ( openDetailPanel(name)} + onClick={() => { + trackUiMetric(UIM_SHOW_DETAILS_CLICK); + openDetailPanel(name); + }} > {name} diff --git a/x-pack/plugins/remote_clusters/public/services/api.js b/x-pack/plugins/remote_clusters/public/services/api.js index 1d4246b51a414..853c3adf638a8 100644 --- a/x-pack/plugins/remote_clusters/public/services/api.js +++ b/x-pack/plugins/remote_clusters/public/services/api.js @@ -5,10 +5,19 @@ */ import chrome from 'ui/chrome'; +import { UIM_CLUSTER_ADD, UIM_CLUSTER_UPDATE } from '../constants'; +import { trackUserRequest } from './track_ui_metric'; + let httpClient; + export const setHttpClient = (client) => { httpClient = client; }; + +export const getHttpClient = () => { + return httpClient; +}; + const apiPrefix = chrome.addBasePath('/api/remote_clusters'); export async function loadClusters() { @@ -17,7 +26,8 @@ export async function loadClusters() { } export async function addCluster(cluster) { - return await httpClient.post(apiPrefix, cluster); + const request = httpClient.post(apiPrefix, cluster); + return await trackUserRequest(request, UIM_CLUSTER_ADD); } export async function editCluster(cluster) { @@ -26,7 +36,8 @@ export async function editCluster(cluster) { ...rest } = cluster; - return await httpClient.put(`${apiPrefix}/${name}`, rest); + const request = httpClient.put(`${apiPrefix}/${name}`, rest); + return await trackUserRequest(request, UIM_CLUSTER_UPDATE); } export function removeClusterRequest(name) { diff --git a/x-pack/plugins/remote_clusters/public/services/index.js b/x-pack/plugins/remote_clusters/public/services/index.js index 5eff4f41d0840..bf01f806f3393 100644 --- a/x-pack/plugins/remote_clusters/public/services/index.js +++ b/x-pack/plugins/remote_clusters/public/services/index.js @@ -45,3 +45,7 @@ export { getRouter, getRouterLinkProps, } from './routing'; + +export { + trackUiMetric, +} from './track_ui_metric'; diff --git a/x-pack/plugins/remote_clusters/public/services/track_ui_metric.js b/x-pack/plugins/remote_clusters/public/services/track_ui_metric.js new file mode 100644 index 0000000000000..d98815e95ff49 --- /dev/null +++ b/x-pack/plugins/remote_clusters/public/services/track_ui_metric.js @@ -0,0 +1,28 @@ +/* + * 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 { createUiMetricUri } from '../../../../common/ui_metric'; +import { UIM_APP_NAME } from '../constants'; +import { getHttpClient } from './api'; + +export function trackUiMetric(actionType) { + const uiMetricUri = createUiMetricUri(UIM_APP_NAME, actionType); + getHttpClient().post(uiMetricUri); +} + +/** + * Transparently return provided request Promise, while allowing us to track + * a successful completion of the request. + */ +export function trackUserRequest(request, actionType) { + // Only track successful actions. + return request.then(response => { + trackUiMetric(actionType); + // We return the response immediately without waiting for the tracking request to resolve, + // to avoid adding additional latency. + return response; + }); +} diff --git a/x-pack/plugins/remote_clusters/public/store/actions/remove_clusters.js b/x-pack/plugins/remote_clusters/public/store/actions/remove_clusters.js index d72b6a5d1264a..0155070cb3d94 100644 --- a/x-pack/plugins/remote_clusters/public/store/actions/remove_clusters.js +++ b/x-pack/plugins/remote_clusters/public/store/actions/remove_clusters.js @@ -7,8 +7,11 @@ import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; +import { UIM_CLUSTER_REMOVE, UIM_CLUSTER_REMOVE_MANY } from '../../constants'; + import { removeClusterRequest as sendRemoveClusterRequest, + trackUiMetric, } from '../../services'; import { @@ -79,6 +82,9 @@ export const removeClusters = (names) => async (dispatch, getState) => { } if (itemsDeleted.length > 0) { + // Only track successful requests. + trackUiMetric(names.length > 1 ? UIM_CLUSTER_REMOVE_MANY : UIM_CLUSTER_REMOVE); + if (itemsDeleted.length === 1) { toastNotifications.addSuccess(i18n.translate('xpack.remoteClusters.removeAction.successSingleNotificationTitle', { defaultMessage: `Remote cluster '{name}' was removed`, diff --git a/x-pack/plugins/rollup/common/ui_metric.js b/x-pack/plugins/rollup/common/ui_metric.js index 557b925dbbc51..acfd22d83f963 100644 --- a/x-pack/plugins/rollup/common/ui_metric.js +++ b/x-pack/plugins/rollup/common/ui_metric.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const UIM_APP_NAME = 'rollup_job_wizard'; +export const UIM_APP_NAME = 'rollup_jobs'; export const UIM_APP_LOAD = 'app_load'; export const UIM_JOB_CREATE = 'job_create';