From d8c7e18bf150d599697676b5a4159d55e154fea8 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Tue, 26 Mar 2019 15:43:03 -0700 Subject: [PATCH] Instrument ILM with user action telemetry (#33089) * Track user actions for creating, updating, and deleting and index lifecycle policy. * Track user actions for attaching an index, attaching an index template, and detaching an index. * Track app load action. * Track usage of cold phase, warm phase, set priority, and freeze index. * Track retry step user action. * Track clicks on the policy name to edit it. * Refactor constants to be in root of public. * Add support to user actions API for comma-delimited action types. * Refactor rollups trackUserRequest to be easier to understand. * Use underscores instead of dashes for user action app names. * Switch from componentWillMount to componentDidMount/useEffect. - Standardize use of HashRouter within app.js to avoid calling useEffect when leaving the app. --- .../server/routes/api/user_action.ts | 13 ++-- .../apis/user_action/user_action.js | 22 +++++- .../common/constants/index.js | 17 +++++ .../common/constants/user_action.js | 37 +++++++++ .../index_lifecycle_management/index.js | 4 + .../index_lifecycle_management/public/app.js | 27 ++++--- .../constants.js => constants/index.js} | 0 .../cold_phase/cold_phase.container.js | 2 +- .../components/cold_phase/cold_phase.js | 2 +- .../delete_phase/delete_phase.container.js | 2 +- .../components/delete_phase/delete_phase.js | 2 +- .../hot_phase/hot_phase.container.js | 2 +- .../components/hot_phase/hot_phase.js | 2 +- .../edit_policy/components/min_age_input.js | 2 +- .../node_allocation/node_allocation.js | 2 +- .../components/set_priority_input.js | 2 +- .../warm_phase/warm_phase.container.js | 2 +- .../components/warm_phase/warm_phase.js | 2 +- .../sections/edit_policy/edit_policy.js | 2 +- .../components/policy_table/policy_table.js | 16 ++-- .../public/services/api.js | 25 ++++++- .../public/services/index.js | 1 + .../public/services/user_action.js | 75 +++++++++++++++++++ .../public/services/user_action.test.js | 74 ++++++++++++++++++ .../public/store/actions/lifecycle.js | 13 +++- .../public/store/actions/nodes.js | 2 +- .../public/store/actions/policies.js | 2 +- .../public/store/defaults/cold_phase.js | 2 +- .../public/store/defaults/delete_phase.js | 2 +- .../public/store/defaults/hot_phase.js | 2 +- .../public/store/defaults/warm_phase.js | 2 +- .../public/store/reducers/policies.js | 2 +- .../public/store/selectors/lifecycle.js | 2 +- .../public/store/selectors/policies.js | 2 +- .../server/usage/collector.js | 25 +++++++ .../server/usage/index.js | 7 ++ .../__jest__/components/index_table.test.js | 4 +- .../common/constants/user_action.js | 2 +- x-pack/plugins/index_management/public/app.js | 37 ++++----- .../public/register_routes.js | 5 +- x-pack/plugins/rollup/public/crud_app/app.js | 26 +++++-- .../plugins/rollup/public/crud_app/index.js | 5 +- .../crud_app/services/track_user_action.js | 12 ++- 43 files changed, 400 insertions(+), 89 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/common/constants/user_action.js rename x-pack/plugins/index_lifecycle_management/public/{store/constants.js => constants/index.js} (100%) create mode 100644 x-pack/plugins/index_lifecycle_management/public/services/user_action.js create mode 100644 x-pack/plugins/index_lifecycle_management/public/services/user_action.test.js create mode 100644 x-pack/plugins/index_lifecycle_management/server/usage/collector.js create mode 100644 x-pack/plugins/index_lifecycle_management/server/usage/index.js diff --git a/src/legacy/core_plugins/user_action/server/routes/api/user_action.ts b/src/legacy/core_plugins/user_action/server/routes/api/user_action.ts index f7d1067e8024..004f6e2b6953 100644 --- a/src/legacy/core_plugins/user_action/server/routes/api/user_action.ts +++ b/src/legacy/core_plugins/user_action/server/routes/api/user_action.ts @@ -25,20 +25,23 @@ export const registerUserActionRoute = (server: Server) => { * Increment a count on an object representing a specific user action. */ server.route({ - path: '/api/user_action/{appName}/{actionType}', + path: '/api/user_action/{appName}/{actionTypes}', method: 'POST', handler: async (request: any) => { - const { appName, actionType } = request.params; + const { appName, actionTypes } = request.params; try { const { getSavedObjectsRepository } = server.savedObjects; const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); const internalRepository = getSavedObjectsRepository(callWithInternalUser); - const savedObjectId = `${appName}:${actionType}`; - // This object is created if it doesn't already exist. - await internalRepository.incrementCounter('user-action', savedObjectId, 'count'); + const incrementRequests = actionTypes.split(',').map((actionType: string) => { + const savedObjectId = `${appName}:${actionType}`; + // This object is created if it doesn't already exist. + return internalRepository.incrementCounter('user-action', savedObjectId, 'count'); + }); + await Promise.all(incrementRequests); return {}; } catch (error) { return new Boom('Something went wrong', { statusCode: error.status }); diff --git a/test/api_integration/apis/user_action/user_action.js b/test/api_integration/apis/user_action/user_action.js index db4485c8f14f..d0be6d648200 100644 --- a/test/api_integration/apis/user_action/user_action.js +++ b/test/api_integration/apis/user_action/user_action.js @@ -18,7 +18,6 @@ */ import expect from '@kbn/expect'; -import { get } from 'lodash'; export default function ({ getService }) { const supertest = getService('supertest'); @@ -35,9 +34,24 @@ export default function ({ getService }) { index: '.kibana', q: 'type:user-action', }).then(response => { - const doc = get(response, 'hits.hits[0]'); - expect(get(doc, '_source.user-action.count')).to.be(1); - expect(doc._id).to.be('user-action:myApp:myAction'); + const ids = response.hits.hits.map(({ _id }) => _id); + expect(ids.includes('user-action:myApp:myAction')); + }); + }); + + it('supports comma-delimited action types', async () => { + await supertest + .post('/api/user_action/myApp/myAction1,myAction2') + .set('kbn-xsrf', 'kibana') + .expect(200); + + return es.search({ + index: '.kibana', + q: 'type:user-action', + }).then(response => { + const ids = response.hits.hits.map(({ _id }) => _id); + expect(ids.includes('user-action:myApp:myAction1')); + expect(ids.includes('user-action:myApp:myAction2')); }); }); }); diff --git a/x-pack/plugins/index_lifecycle_management/common/constants/index.js b/x-pack/plugins/index_lifecycle_management/common/constants/index.js index 5525b2b86f4d..204bf87e0ae7 100644 --- a/x-pack/plugins/index_lifecycle_management/common/constants/index.js +++ b/x-pack/plugins/index_lifecycle_management/common/constants/index.js @@ -6,3 +6,20 @@ export const BASE_PATH = '/management/elasticsearch/index_lifecycle_management/'; export const PLUGIN_ID = 'index_lifecycle_management'; +export { + UA_APP_NAME, + USER_ACTIONS, + UA_APP_LOAD, + UA_POLICY_CREATE, + UA_POLICY_UPDATE, + UA_POLICY_DELETE, + UA_POLICY_ATTACH_INDEX, + UA_POLICY_ATTACH_INDEX_TEMPLATE, + UA_POLICY_DETACH_INDEX, + UA_CONFIG_COLD_PHASE, + UA_CONFIG_WARM_PHASE, + UA_CONFIG_SET_PRIORITY, + UA_CONFIG_FREEZE_INDEX, + UA_INDEX_RETRY_STEP, + UA_EDIT_CLICK, +} from './user_action'; diff --git a/x-pack/plugins/index_lifecycle_management/common/constants/user_action.js b/x-pack/plugins/index_lifecycle_management/common/constants/user_action.js new file mode 100644 index 000000000000..91f28e855b06 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/common/constants/user_action.js @@ -0,0 +1,37 @@ +/* + * 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 UA_APP_NAME = 'index_lifecycle_management'; + +export const UA_APP_LOAD = 'app_load'; +export const UA_POLICY_CREATE = 'policy_create'; +export const UA_POLICY_UPDATE = 'policy_update'; +export const UA_POLICY_DELETE = 'policy_delete'; +export const UA_POLICY_ATTACH_INDEX = 'policy_attach_index'; +export const UA_POLICY_ATTACH_INDEX_TEMPLATE = 'policy_attach_index_template'; +export const UA_POLICY_DETACH_INDEX = 'policy_detach_index'; +export const UA_CONFIG_COLD_PHASE = 'config_cold_phase'; +export const UA_CONFIG_WARM_PHASE = 'config_warm_phase'; +export const UA_CONFIG_SET_PRIORITY = 'config_set_priority'; +export const UA_CONFIG_FREEZE_INDEX = 'config_freeze_index'; +export const UA_INDEX_RETRY_STEP = 'index_retry_step'; +export const UA_EDIT_CLICK = 'edit_click'; + +export const USER_ACTIONS = [ + UA_APP_LOAD, + UA_POLICY_CREATE, + UA_POLICY_UPDATE, + UA_POLICY_DELETE, + UA_POLICY_ATTACH_INDEX, + UA_POLICY_ATTACH_INDEX_TEMPLATE, + UA_POLICY_DETACH_INDEX, + UA_CONFIG_COLD_PHASE, + UA_CONFIG_WARM_PHASE, + UA_CONFIG_SET_PRIORITY, + UA_CONFIG_FREEZE_INDEX, + UA_INDEX_RETRY_STEP, + UA_EDIT_CLICK, +]; diff --git a/x-pack/plugins/index_lifecycle_management/index.js b/x-pack/plugins/index_lifecycle_management/index.js index fe1fc677e102..384293053c45 100644 --- a/x-pack/plugins/index_lifecycle_management/index.js +++ b/x-pack/plugins/index_lifecycle_management/index.js @@ -13,6 +13,8 @@ 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({ config: (Joi) => { @@ -52,6 +54,8 @@ export function indexLifecycleManagement(kibana) { registerPoliciesRoutes(server); registerLifecycleRoutes(server); registerIndexRoutes(server); + registerIndexLifecycleManagementUsageCollector(server); + if ( server.config().get('xpack.ilm.ui.enabled') && server.plugins.index_management && diff --git a/x-pack/plugins/index_lifecycle_management/public/app.js b/x-pack/plugins/index_lifecycle_management/public/app.js index e316d11017a1..b644ffdcf1df 100644 --- a/x-pack/plugins/index_lifecycle_management/public/app.js +++ b/x-pack/plugins/index_lifecycle_management/public/app.js @@ -4,18 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; +import { BASE_PATH, UA_APP_LOAD } from '../common/constants'; import { EditPolicy } from './sections/edit_policy'; import { PolicyTable } from './sections/policy_table'; -import { BASE_PATH } from '../common/constants'; +import { trackUserAction } from './services'; -export const App = () => ( - - - - - - - -); +export const App = () => { + useEffect(() => trackUserAction(UA_APP_LOAD), []); + + return ( + + + + + + + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/store/constants.js b/x-pack/plugins/index_lifecycle_management/public/constants/index.js similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/store/constants.js rename to x-pack/plugins/index_lifecycle_management/public/constants/index.js diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.container.js index f6470e5c66d3..90c30d943f68 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.container.js @@ -17,7 +17,7 @@ import { PHASE_COLD, PHASE_HOT, PHASE_ROLLOVER_ENABLED -} from '../../../../store/constants'; +} from '../../../../constants'; export const ColdPhase = connect( (state) => ({ diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.js index ee8759f5f6b0..c95ef1ee75d4 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.js @@ -22,7 +22,7 @@ import { PHASE_ENABLED, PHASE_REPLICA_COUNT, PHASE_FREEZE_ENABLED -} from '../../../../store/constants'; +} from '../../../../constants'; import { ErrableFormRow } from '../../form_errors'; import { MinAgeInput } from '../min_age_input'; import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, OptionalLabel } from '../../../components'; diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.container.js index b7eb4164073c..f7933815d9f3 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.container.js @@ -8,7 +8,7 @@ import { connect } from 'react-redux'; import { DeletePhase as PresentationComponent } from './delete_phase'; import { getPhase } from '../../../../store/selectors'; import { setPhaseData } from '../../../../store/actions'; -import { PHASE_DELETE, PHASE_HOT, PHASE_ROLLOVER_ENABLED } from '../../../../store/constants'; +import { PHASE_DELETE, PHASE_HOT, PHASE_ROLLOVER_ENABLED } from '../../../../constants'; export const DeletePhase = connect( state => ({ diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.js index a10ae5ab6559..0e7663a6aef7 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.js @@ -19,7 +19,7 @@ import { import { PHASE_DELETE, PHASE_ENABLED, -} from '../../../../store/constants'; +} from '../../../../constants'; import { ActiveBadge, PhaseErrorMessage } from '../../../components'; export class DeletePhase extends PureComponent { diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.container.js index 0361d287cd40..e1ef58660525 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.container.js @@ -11,7 +11,7 @@ import { connect } from 'react-redux'; import { HotPhase as PresentationComponent } from './hot_phase'; import { getPhase } from '../../../../store/selectors'; import { setPhaseData } from '../../../../store/actions'; -import { PHASE_HOT, PHASE_WARM, WARM_PHASE_ON_ROLLOVER } from '../../../../store/constants'; +import { PHASE_HOT, PHASE_WARM, WARM_PHASE_ON_ROLLOVER } from '../../../../constants'; export const HotPhase = connect( state => ({ diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.js index ccdb2a6479b1..be1023e9042a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.js @@ -27,7 +27,7 @@ import { PHASE_ROLLOVER_MAX_SIZE_STORED, PHASE_ROLLOVER_MAX_SIZE_STORED_UNITS, PHASE_ROLLOVER_ENABLED, -} from '../../../../store/constants'; +} from '../../../../constants'; import { SetPriorityInput } from '../set_priority_input'; import { ErrableFormRow } from '../../form_errors'; diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/min_age_input.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/min_age_input.js index d290a25d0636..d52bc147138a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/min_age_input.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/min_age_input.js @@ -15,7 +15,7 @@ import { import { PHASE_ROLLOVER_MINIMUM_AGE, PHASE_ROLLOVER_MINIMUM_AGE_UNITS, -} from '../../../store/constants'; +} from '../../../constants'; import { ErrableFormRow } from '../form_errors'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; const MinAgeInputUi = props => { diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_allocation/node_allocation.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_allocation/node_allocation.js index 60f37c4be729..0a0f3bf9d35a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_allocation/node_allocation.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_allocation/node_allocation.js @@ -7,7 +7,7 @@ import React, { Component, Fragment } from 'react'; import { EuiSelect, EuiButtonEmpty, EuiCallOut, EuiSpacer, EuiLoadingSpinner } from '@elastic/eui'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -import { PHASE_NODE_ATTRS } from '../../../../store/constants'; +import { PHASE_NODE_ATTRS } from '../../../../constants'; import { ErrableFormRow } from '../../form_errors'; import { LearnMoreLink } from '../../../components/learn_more_link'; const learnMoreLinks = ( diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/set_priority_input.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/set_priority_input.js index af6534817195..3f4c32b05a2d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/set_priority_input.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/set_priority_input.js @@ -12,7 +12,7 @@ import { } from '@elastic/eui'; import { PHASE_INDEX_PRIORITY, -} from '../../../store/constants'; +} from '../../../constants'; import { ErrableFormRow } from '../form_errors'; import { FormattedMessage } from '@kbn/i18n/react'; export const SetPriorityInput = props => { diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.container.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.container.js index 5b66fcae24ec..4e68a51aee93 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.container.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.container.js @@ -13,7 +13,7 @@ import { getPhase, } from '../../../../store/selectors'; import { setPhaseData } from '../../../../store/actions'; -import { PHASE_WARM, PHASE_HOT, PHASE_ROLLOVER_ENABLED } from '../../../../store/constants'; +import { PHASE_WARM, PHASE_HOT, PHASE_ROLLOVER_ENABLED } from '../../../../constants'; export const WarmPhase = connect( state => ({ diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.js index d9c9d0930daf..b25e3db0ceda 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.js @@ -26,7 +26,7 @@ import { PHASE_PRIMARY_SHARD_COUNT, PHASE_REPLICA_COUNT, PHASE_SHRINK_ENABLED, -} from '../../../../store/constants'; +} from '../../../../constants'; import { SetPriorityInput } from '../set_priority_input'; import { NodeAllocation } from '../node_allocation'; import { ErrableFormRow } from '../../form_errors'; diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/edit_policy.js b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/edit_policy.js index 9daa0926b571..8e9fcc281ad2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/edit_policy.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/edit_policy/edit_policy.js @@ -36,7 +36,7 @@ import { PHASE_DELETE, PHASE_WARM, STRUCTURE_POLICY_NAME, -} from '../../store/constants'; +} from '../../constants'; import { findFirstError } from '../../services/find_errors'; import { NodeAttrsDetails } from './components/node_attrs_details'; import { PolicyJsonFlyout } from './components/policy_json_flyout'; diff --git a/x-pack/plugins/index_lifecycle_management/public/sections/policy_table/components/policy_table/policy_table.js b/x-pack/plugins/index_lifecycle_management/public/sections/policy_table/components/policy_table/policy_table.js index 891d608b6dd6..d31aae042fe0 100644 --- a/x-pack/plugins/index_lifecycle_management/public/sections/policy_table/components/policy_table/policy_table.js +++ b/x-pack/plugins/index_lifecycle_management/public/sections/policy_table/components/policy_table/policy_table.js @@ -8,11 +8,7 @@ import React, { Component, Fragment } from 'react'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -import { BASE_PATH } from '../../../../../common/constants'; -import { NoMatch } from '../no_match'; -import { getPolicyPath } from '../../../../services/navigation'; -import { flattenPanelTree } from '../../../../services/flatten_panel_tree'; -import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; + import { EuiBetaBadge, EuiButton, @@ -38,10 +34,17 @@ import { EuiPageBody, EuiPageContent, } from '@elastic/eui'; +import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; +import { getIndexListUri } from '../../../../../../index_management/public/services/navigation'; +import { BASE_PATH, UA_EDIT_CLICK } from '../../../../../common/constants'; +import { getPolicyPath } from '../../../../services/navigation'; +import { flattenPanelTree } from '../../../../services/flatten_panel_tree'; +import { trackUserAction } from '../../../../services'; +import { NoMatch } from '../no_match'; import { ConfirmDelete } from './confirm_delete'; import { AddPolicyToTemplateConfirmModal } from './add_policy_to_template_confirm_modal'; -import { getIndexListUri } from '../../../../../../index_management/public/services/navigation'; + const COLUMNS = { name: { label: i18n.translate('xpack.indexLifecycleMgmt.policyTable.headers.nameHeader', { @@ -176,6 +179,7 @@ export class PolicyTableUi extends Component { className="policyTable__link" data-test-subj="policyTablePolicyNameLink" href={getPolicyPath(value)} + onClick={() => trackUserAction(UA_EDIT_CLICK)} > {value} diff --git a/x-pack/plugins/index_lifecycle_management/public/services/api.js b/x-pack/plugins/index_lifecycle_management/public/services/api.js index a82cd3a0a754..334f986b659d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/services/api.js +++ b/x-pack/plugins/index_lifecycle_management/public/services/api.js @@ -4,12 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ import chrome from 'ui/chrome'; +import { + UA_POLICY_DELETE, + UA_POLICY_ATTACH_INDEX, + UA_POLICY_ATTACH_INDEX_TEMPLATE, + UA_POLICY_DETACH_INDEX, + UA_INDEX_RETRY_STEP, +} from '../../common/constants'; +import { trackUserAction } from './user_action'; let httpClient; export const setHttpClient = (client) => { httpClient = client; }; -const getHttpClient = () => { +export const getHttpClient = () => { return httpClient; }; const apiPrefix = chrome.addBasePath('/api/index_lifecycle_management'); @@ -44,6 +52,8 @@ export async function loadPolicies(withIndices, httpClient = getHttpClient()) { export async function deletePolicy(policyName, httpClient = getHttpClient()) { const response = await httpClient.delete(`${apiPrefix}/policies/${encodeURIComponent(policyName)}`); + // Only track successful actions. + trackUserAction(UA_POLICY_DELETE, httpClient); return response.data; } @@ -52,7 +62,6 @@ export async function saveLifecycle(lifecycle, httpClient = getHttpClient()) { return response.data; } - export async function getAffectedIndices(indexTemplateName, policyName, httpClient = getHttpClient()) { const path = policyName ? `${apiPrefix}/indices/affected/${indexTemplateName}/${encodeURIComponent(policyName)}` @@ -60,19 +69,31 @@ export async function getAffectedIndices(indexTemplateName, policyName, httpClie const response = await httpClient.get(path); return response.data; } + export const retryLifecycleForIndex = async (indexNames, httpClient = getHttpClient()) => { const response = await httpClient.post(`${apiPrefix}/index/retry`, { indexNames }); + // Only track successful actions. + trackUserAction(UA_INDEX_RETRY_STEP, httpClient); return response.data; }; + export const removeLifecycleForIndex = async (indexNames, httpClient = getHttpClient()) => { const response = await httpClient.post(`${apiPrefix}/index/remove`, { indexNames }); + // Only track successful actions. + trackUserAction(UA_POLICY_DETACH_INDEX, httpClient); return response.data; }; + export const addLifecyclePolicyToIndex = async (body, httpClient = getHttpClient()) => { const response = await httpClient.post(`${apiPrefix}/index/add`, body); + // Only track successful actions. + trackUserAction(UA_POLICY_ATTACH_INDEX, httpClient); return response.data; }; + export const addLifecyclePolicyToTemplate = async (body, httpClient = getHttpClient()) => { const response = await httpClient.post(`${apiPrefix}/template`, body); + // Only track successful actions. + trackUserAction(UA_POLICY_ATTACH_INDEX_TEMPLATE, httpClient); return response.data; }; diff --git a/x-pack/plugins/index_lifecycle_management/public/services/index.js b/x-pack/plugins/index_lifecycle_management/public/services/index.js index a82b581309d2..d1160c4af8db 100644 --- a/x-pack/plugins/index_lifecycle_management/public/services/index.js +++ b/x-pack/plugins/index_lifecycle_management/public/services/index.js @@ -6,3 +6,4 @@ export { filterItems } from './filter_items'; export { sortTable } from './sort_table'; +export { trackUserAction, getUserActionsForPhases } from './user_action'; diff --git a/x-pack/plugins/index_lifecycle_management/public/services/user_action.js b/x-pack/plugins/index_lifecycle_management/public/services/user_action.js new file mode 100644 index 000000000000..d45b2f1ce494 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/services/user_action.js @@ -0,0 +1,75 @@ +/* + * 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 { get } from 'lodash'; + +import { createUserActionUri } from '../../../../common/user_action'; + +import { + UA_APP_NAME, + UA_CONFIG_COLD_PHASE, + UA_CONFIG_WARM_PHASE, + UA_CONFIG_SET_PRIORITY, + UA_CONFIG_FREEZE_INDEX, +} from '../../common/constants'; + +import { + PHASE_HOT, + PHASE_WARM, + PHASE_COLD, + PHASE_INDEX_PRIORITY, +} from '../constants'; + +import { + defaultColdPhase, + defaultWarmPhase, + defaultHotPhase, +} from '../store/defaults'; + +import { getHttpClient } from './api'; + +export function trackUserAction(actionType, httpClient = getHttpClient()) { + const userActionUri = createUserActionUri(UA_APP_NAME, actionType); + httpClient.post(userActionUri); +} + +export function getUserActionsForPhases(phases) { + const possibleUserActions = [{ + action: UA_CONFIG_COLD_PHASE, + isExecuted: () => Boolean(phases[PHASE_COLD]), + }, { + action: UA_CONFIG_WARM_PHASE, + isExecuted: () => Boolean(phases[PHASE_WARM]), + }, { + action: UA_CONFIG_SET_PRIORITY, + isExecuted: () => { + const phaseToDefaultIndexPriorityMap = { + [PHASE_HOT]: defaultHotPhase[PHASE_INDEX_PRIORITY], + [PHASE_WARM]: defaultWarmPhase[PHASE_INDEX_PRIORITY], + [PHASE_COLD]: defaultColdPhase[PHASE_INDEX_PRIORITY], + }; + + // We only care about whether the user has interacted with the priority of *any* phase at all. + return [ PHASE_HOT, PHASE_WARM, PHASE_COLD ].some(phase => { + // If the priority is different than the default, we'll consider it a user interaction, + // even if the user has set it to undefined. + return phases[phase] && get(phases[phase], 'actions.set_priority.priority') !== phaseToDefaultIndexPriorityMap[phase]; + }); + }, + }, { + action: UA_CONFIG_FREEZE_INDEX, + isExecuted: () => phases[PHASE_COLD] && get(phases[PHASE_COLD], 'actions.freeze'), + }]; + + const executedUserActions = possibleUserActions.reduce((executed, { action, isExecuted }) => { + if (isExecuted()) { + executed.push(action); + } + return executed; + }, []); + + return executedUserActions; +} diff --git a/x-pack/plugins/index_lifecycle_management/public/services/user_action.test.js b/x-pack/plugins/index_lifecycle_management/public/services/user_action.test.js new file mode 100644 index 000000000000..a66a9140d9a6 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/services/user_action.test.js @@ -0,0 +1,74 @@ +/* + * 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 { + UA_CONFIG_COLD_PHASE, + UA_CONFIG_WARM_PHASE, + UA_CONFIG_SET_PRIORITY, + UA_CONFIG_FREEZE_INDEX, +} from '../../common/constants'; + +import { + defaultColdPhase, + defaultWarmPhase, +} from '../store/defaults'; + +import { + PHASE_INDEX_PRIORITY, +} from '../constants'; + +import { getUserActionsForPhases } from './user_action'; + +describe('getUserActionsForPhases', () => { + test('gets cold phase', () => { + expect(getUserActionsForPhases({ + cold: { + actions: { + set_priority: { + priority: defaultColdPhase[PHASE_INDEX_PRIORITY], + }, + }, + }, + })).toEqual([UA_CONFIG_COLD_PHASE]); + }); + + test('gets warm phase', () => { + expect(getUserActionsForPhases({ + warm: { + actions: { + set_priority: { + priority: defaultWarmPhase[PHASE_INDEX_PRIORITY], + }, + }, + }, + })).toEqual([UA_CONFIG_WARM_PHASE]); + }); + + test(`gets index priority if it's different than the default value`, () => { + expect(getUserActionsForPhases({ + warm: { + actions: { + set_priority: { + priority: defaultWarmPhase[PHASE_INDEX_PRIORITY] + 1, + }, + }, + }, + })).toEqual([UA_CONFIG_WARM_PHASE, UA_CONFIG_SET_PRIORITY]); + }); + + test('gets freeze index', () => { + expect(getUserActionsForPhases({ + cold: { + actions: { + freeze: {}, + set_priority: { + priority: defaultColdPhase[PHASE_INDEX_PRIORITY], + }, + }, + }, + })).toEqual([UA_CONFIG_COLD_PHASE, UA_CONFIG_FREEZE_INDEX]); + }); +}); diff --git a/x-pack/plugins/index_lifecycle_management/public/store/actions/lifecycle.js b/x-pack/plugins/index_lifecycle_management/public/store/actions/lifecycle.js index 9dcd1249de71..c7de1ba2e4ce 100644 --- a/x-pack/plugins/index_lifecycle_management/public/store/actions/lifecycle.js +++ b/x-pack/plugins/index_lifecycle_management/public/store/actions/lifecycle.js @@ -5,9 +5,15 @@ */ import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; + +import { + UA_POLICY_CREATE, + UA_POLICY_UPDATE, +} from '../../../common/constants'; + import { showApiError } from '../../services/api_errors'; import { saveLifecycle as saveLifecycleApi } from '../../services/api'; - +import { trackUserAction, getUserActionsForPhases } from '../../services'; export const saveLifecyclePolicy = (lifecycle, isNew) => async () => { try { @@ -23,6 +29,11 @@ export const saveLifecyclePolicy = (lifecycle, isNew) => async () => { showApiError(err, title); return false; } + + const userActions = getUserActionsForPhases(lifecycle.phases); + userActions.push(isNew ? UA_POLICY_CREATE : UA_POLICY_UPDATE); + trackUserAction(userActions.join(',')); + const message = i18n.translate('xpack.indexLifecycleMgmt.editPolicy.successfulSaveMessage', { defaultMessage: '{verb} lifecycle policy "{lifecycleName}"', diff --git a/x-pack/plugins/index_lifecycle_management/public/store/actions/nodes.js b/x-pack/plugins/index_lifecycle_management/public/store/actions/nodes.js index a2a526e44298..bb1f63c61cb2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/store/actions/nodes.js +++ b/x-pack/plugins/index_lifecycle_management/public/store/actions/nodes.js @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { createAction } from 'redux-actions'; import { showApiError } from '../../services/api_errors'; import { loadNodes, loadNodeDetails } from '../../services/api'; -import { SET_SELECTED_NODE_ATTRS } from '../constants'; +import { SET_SELECTED_NODE_ATTRS } from '../../constants'; export const setSelectedNodeAttrs = createAction(SET_SELECTED_NODE_ATTRS); export const setSelectedPrimaryShardCount = createAction( diff --git a/x-pack/plugins/index_lifecycle_management/public/store/actions/policies.js b/x-pack/plugins/index_lifecycle_management/public/store/actions/policies.js index fb2384855d92..c089dfa7e752 100644 --- a/x-pack/plugins/index_lifecycle_management/public/store/actions/policies.js +++ b/x-pack/plugins/index_lifecycle_management/public/store/actions/policies.js @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { showApiError } from '../../services/api_errors'; import { createAction } from 'redux-actions'; import { loadPolicies } from '../../services/api'; -import { SET_PHASE_DATA } from '../constants'; +import { SET_PHASE_DATA } from '../../constants'; export const fetchedPolicies = createAction('FETCHED_POLICIES'); export const setSelectedPolicy = createAction('SET_SELECTED_POLICY'); export const unsetSelectedPolicy = createAction('UNSET_SELECTED_POLICY'); diff --git a/x-pack/plugins/index_lifecycle_management/public/store/defaults/cold_phase.js b/x-pack/plugins/index_lifecycle_management/public/store/defaults/cold_phase.js index 0a4f4e2206d6..0ff523a43867 100644 --- a/x-pack/plugins/index_lifecycle_management/public/store/defaults/cold_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/store/defaults/cold_phase.js @@ -12,7 +12,7 @@ import { PHASE_ROLLOVER_ALIAS, PHASE_FREEZE_ENABLED, PHASE_INDEX_PRIORITY, -} from '../constants'; +} from '../../constants'; export const defaultColdPhase = { [PHASE_ENABLED]: false, diff --git a/x-pack/plugins/index_lifecycle_management/public/store/defaults/delete_phase.js b/x-pack/plugins/index_lifecycle_management/public/store/defaults/delete_phase.js index c449ba073fd2..901ac4f6a466 100644 --- a/x-pack/plugins/index_lifecycle_management/public/store/defaults/delete_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/store/defaults/delete_phase.js @@ -9,7 +9,7 @@ import { PHASE_ROLLOVER_MINIMUM_AGE, PHASE_ROLLOVER_MINIMUM_AGE_UNITS, PHASE_ROLLOVER_ALIAS, -} from '../constants'; +} from '../../constants'; export const defaultDeletePhase = { [PHASE_ENABLED]: false, diff --git a/x-pack/plugins/index_lifecycle_management/public/store/defaults/hot_phase.js b/x-pack/plugins/index_lifecycle_management/public/store/defaults/hot_phase.js index 43ffc1b565e5..ba8897943525 100644 --- a/x-pack/plugins/index_lifecycle_management/public/store/defaults/hot_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/store/defaults/hot_phase.js @@ -12,7 +12,7 @@ import { PHASE_ROLLOVER_MAX_DOCUMENTS, PHASE_ROLLOVER_MAX_SIZE_STORED_UNITS, PHASE_INDEX_PRIORITY -} from '../constants'; +} from '../../constants'; export const defaultHotPhase = { [PHASE_ENABLED]: true, diff --git a/x-pack/plugins/index_lifecycle_management/public/store/defaults/warm_phase.js b/x-pack/plugins/index_lifecycle_management/public/store/defaults/warm_phase.js index 8e55b5c72c4f..7e6dcd41f487 100644 --- a/x-pack/plugins/index_lifecycle_management/public/store/defaults/warm_phase.js +++ b/x-pack/plugins/index_lifecycle_management/public/store/defaults/warm_phase.js @@ -16,7 +16,7 @@ import { PHASE_SHRINK_ENABLED, WARM_PHASE_ON_ROLLOVER, PHASE_INDEX_PRIORITY -} from '../constants'; +} from '../../constants'; export const defaultWarmPhase = { [PHASE_ENABLED]: false, diff --git a/x-pack/plugins/index_lifecycle_management/public/store/reducers/policies.js b/x-pack/plugins/index_lifecycle_management/public/store/reducers/policies.js index 27b5304fec1f..8fc0bc1b7376 100644 --- a/x-pack/plugins/index_lifecycle_management/public/store/reducers/policies.js +++ b/x-pack/plugins/index_lifecycle_management/public/store/reducers/policies.js @@ -27,7 +27,7 @@ import { PHASE_COLD, PHASE_DELETE, PHASE_ATTRIBUTES_THAT_ARE_NUMBERS, -} from '../constants'; +} from '../../constants'; import { defaultColdPhase, diff --git a/x-pack/plugins/index_lifecycle_management/public/store/selectors/lifecycle.js b/x-pack/plugins/index_lifecycle_management/public/store/selectors/lifecycle.js index a25299d54675..b89e74781b34 100644 --- a/x-pack/plugins/index_lifecycle_management/public/store/selectors/lifecycle.js +++ b/x-pack/plugins/index_lifecycle_management/public/store/selectors/lifecycle.js @@ -25,7 +25,7 @@ import { WARM_PHASE_ON_ROLLOVER, PHASE_INDEX_PRIORITY, PHASE_ROLLOVER_MAX_DOCUMENTS -} from '../constants'; +} from '../../constants'; import { getPhase, getPhases, diff --git a/x-pack/plugins/index_lifecycle_management/public/store/selectors/policies.js b/x-pack/plugins/index_lifecycle_management/public/store/selectors/policies.js index 30649e8a6204..31af91c12201 100644 --- a/x-pack/plugins/index_lifecycle_management/public/store/selectors/policies.js +++ b/x-pack/plugins/index_lifecycle_management/public/store/selectors/policies.js @@ -32,7 +32,7 @@ import { PHASE_FREEZE_ENABLED, PHASE_INDEX_PRIORITY, PHASE_ROLLOVER_MAX_DOCUMENTS -} from '../constants'; +} from '../../constants'; import { defaultEmptyDeletePhase, defaultEmptyColdPhase, diff --git a/x-pack/plugins/index_lifecycle_management/server/usage/collector.js b/x-pack/plugins/index_lifecycle_management/server/usage/collector.js new file mode 100644 index 000000000000..4fb067a2eaa9 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/server/usage/collector.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. + */ + +import { fetchUserActions } from '../../../../server/lib/user_action'; +import { UA_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 userActions = await fetchUserActions(server, UA_APP_NAME, USER_ACTIONS); + + return { + user_actions: userActions, + }; + }, + }); + + server.usage.collectorSet.register(collector); +} diff --git a/x-pack/plugins/index_lifecycle_management/server/usage/index.js b/x-pack/plugins/index_lifecycle_management/server/usage/index.js new file mode 100644 index 000000000000..f0fec917cd56 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/server/usage/index.js @@ -0,0 +1,7 @@ +/* + * 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'; diff --git a/x-pack/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/plugins/index_management/__jest__/components/index_table.test.js index 9e795c38979e..fa7f82be5a2d 100644 --- a/x-pack/plugins/index_management/__jest__/components/index_table.test.js +++ b/x-pack/plugins/index_management/__jest__/components/index_table.test.js @@ -6,7 +6,7 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { App } from '../../public/app'; +import { AppWithoutRouter } from '../../public/app'; import { Provider } from 'react-redux'; import { loadIndicesSuccess } from '../../public/store/actions'; import { indexManagementStore } from '../../public/store'; @@ -123,7 +123,7 @@ describe('index table', () => { component = ( - + ); diff --git a/x-pack/plugins/index_management/common/constants/user_action.js b/x-pack/plugins/index_management/common/constants/user_action.js index 9f491e4918c4..47eda91edfed 100644 --- a/x-pack/plugins/index_management/common/constants/user_action.js +++ b/x-pack/plugins/index_management/common/constants/user_action.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const UA_APP_NAME = 'index-management'; +export const UA_APP_NAME = 'index_management'; export const UA_APP_LOAD = 'app_load'; export const UA_UPDATE_SETTINGS = 'update_settings'; diff --git a/x-pack/plugins/index_management/public/app.js b/x-pack/plugins/index_management/public/app.js index 5446e20f1852..b7302f483629 100644 --- a/x-pack/plugins/index_management/public/app.js +++ b/x-pack/plugins/index_management/public/app.js @@ -4,26 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component } from 'react'; -import { Switch, Route, Redirect } from 'react-router-dom'; +import React, { useEffect } from 'react'; +import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; import { BASE_PATH, UA_APP_LOAD } from '../common/constants'; import { IndexList } from './sections/index_list'; import { trackUserAction } from './services'; -export class App extends Component { - componentWillMount() { - trackUserAction(UA_APP_LOAD); - } +export const App = () => { + useEffect(() => trackUserAction(UA_APP_LOAD), []); - render() { - return ( -
- - - - - -
- ); - } -} + return ( + + + + ); +}; + +// Exoprt this so we can test it with a different router. +export const AppWithoutRouter = () => ( + + + + + +); diff --git a/x-pack/plugins/index_management/public/register_routes.js b/x-pack/plugins/index_management/public/register_routes.js index a926d64d3274..41787befd5c2 100644 --- a/x-pack/plugins/index_management/public/register_routes.js +++ b/x-pack/plugins/index_management/public/register_routes.js @@ -7,7 +7,6 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Provider } from 'react-redux'; -import { HashRouter } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { setHttpClient } from './services/api'; import { setUrlService } from './services/navigation'; @@ -28,9 +27,7 @@ const renderReact = async (elem) => { render( - - - + , elem diff --git a/x-pack/plugins/rollup/public/crud_app/app.js b/x-pack/plugins/rollup/public/crud_app/app.js index 11ac412104db..2e6994a01d3e 100644 --- a/x-pack/plugins/rollup/public/crud_app/app.js +++ b/x-pack/plugins/rollup/public/crud_app/app.js @@ -6,14 +6,14 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { Switch, Route, Redirect } from 'react-router-dom'; +import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; import { UA_APP_LOAD } from '../../common'; import { CRUD_APP_BASE_PATH } from './constants'; import { registerRouter, setUserHasLeftApp, trackUserAction } from './services'; import { JobList, JobCreate } from './sections'; -export class App extends Component { +class ShareRouter extends Component { static contextTypes = { router: PropTypes.shape({ history: PropTypes.shape({ @@ -34,7 +34,13 @@ export class App extends Component { registerRouter(router); } - componentWillMount() { + render() { + return this.props.children; + } +} + +export class App extends Component { // eslint-disable-line react/no-multi-comp + componentDidMount() { trackUserAction(UA_APP_LOAD); } @@ -45,11 +51,15 @@ export class App extends Component { render() { return ( - - - - - + + + + + + + + + ); } } diff --git a/x-pack/plugins/rollup/public/crud_app/index.js b/x-pack/plugins/rollup/public/crud_app/index.js index b43592c8e228..28e9973d2151 100644 --- a/x-pack/plugins/rollup/public/crud_app/index.js +++ b/x-pack/plugins/rollup/public/crud_app/index.js @@ -8,7 +8,6 @@ import React from 'react'; import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; import { render, unmountComponentAtNode } from 'react-dom'; import { Provider } from 'react-redux'; -import { HashRouter } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { I18nContext } from 'ui/i18n'; import { management } from 'ui/management'; @@ -33,9 +32,7 @@ const renderReact = async (elem) => { render( - - - + , elem diff --git a/x-pack/plugins/rollup/public/crud_app/services/track_user_action.js b/x-pack/plugins/rollup/public/crud_app/services/track_user_action.js index c2d588dd4d3a..1db7fe69095e 100644 --- a/x-pack/plugins/rollup/public/crud_app/services/track_user_action.js +++ b/x-pack/plugins/rollup/public/crud_app/services/track_user_action.js @@ -13,8 +13,16 @@ export function trackUserAction(actionType) { getHttp().post(userActionUri); } +/** + * 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. - request.then(() => trackUserAction(actionType)); - return request; + return request.then(response => { + trackUserAction(actionType); + // We return the response immediately without waiting for the tracking request to resolve, + // to avoid adding additional latency. + return response; + }); }