Skip to content

Commit

Permalink
[CCR] Instrument CCR and Remote Clusters to track UI metric telemetry (
Browse files Browse the repository at this point in the history
  • Loading branch information
cjcenizal authored Apr 12, 2019
1 parent 4e3d182 commit 4d130c7
Show file tree
Hide file tree
Showing 23 changed files with 245 additions and 38 deletions.
1 change: 1 addition & 0 deletions src/legacy/server/usage/classes/collector_set.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ describe('<AutoFollowPatternList />', () => {
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
({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ describe('<FollowerIndicesList />', () => {
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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ describe('<CrossClusterReplicationHome />', () => {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@

export { API_STATUS } from './api';
export { SECTIONS } from './sections';
export * from './ui_metric';
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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 } }) => {
Expand Down Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -75,7 +76,10 @@ export class AutoFollowPatternTable extends PureComponent {
render: (name) => {
return (
<EuiLink
onClick={() => selectAutoFollowPattern(name)}
onClick={() => {
trackUiMetric(UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK);
selectAutoFollowPattern(name);
}}
data-test-subj="ccrAutoFollowPatternListPatternLink"
>
{name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -189,7 +190,10 @@ export class FollowerIndicesTable extends PureComponent {
render: (name) => {
return (
<EuiLink
onClick={() => selectFollowerIndex(name)}
onClick={() => {
trackUiMetric(UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK);
selectFollowerIndex(name);
}}
data-test-subj="ccrFollowerIndexListFollowerIndexLink"
>
{name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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
Expand Down
94 changes: 71 additions & 23 deletions x-pack/plugins/cross_cluster_replication/public/app/services/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;

Expand All @@ -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)
Expand All @@ -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 */
Expand All @@ -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 = () => (
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ describe('<RemoteClusterList />', () => {
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));
Expand Down
8 changes: 6 additions & 2 deletions x-pack/plugins/remote_clusters/public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/remote_clusters/public/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
export {
CRUD_APP_BASE_PATH,
} from './paths';

export * from './ui_metric';
14 changes: 14 additions & 0 deletions x-pack/plugins/remote_clusters/public/constants/ui_metric.js
Original file line number Diff line number Diff line change
@@ -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';
Loading

0 comments on commit 4d130c7

Please sign in to comment.