From a63cfbdf5ce1fbc99ef3fe10b660b974ddbd1f02 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 1 Oct 2021 12:51:29 -0700 Subject: [PATCH] Revert "[Upgrade Assistant] Refactor telemetry (#112177)" This reverts commit 991d24bad21ccf4b8350cba2b2ed3ceca6d90cea. --- .../schema/xpack_plugins.json | 38 ++++ x-pack/plugins/upgrade_assistant/README.md | 27 +-- .../plugins/upgrade_assistant/common/types.ts | 29 +++ .../index_settings/flyout.tsx | 16 +- .../deprecation_types/ml_snapshots/flyout.tsx | 8 - .../checklist_step.test.tsx.snap | 4 +- .../reindex/flyout/checklist_step.tsx | 22 +-- .../deprecation_types/reindex/table_row.tsx | 21 +- .../reindex/use_reindex_state.tsx | 4 + .../es_deprecations/es_deprecations.tsx | 22 ++- .../deprecation_details_flyout.tsx | 11 +- .../kibana_deprecations.tsx | 13 +- .../overview/backup_step/cloud_backup.tsx | 8 +- .../overview/backup_step/on_prem_backup.tsx | 11 +- .../deprecations_count_checkpoint.tsx | 5 +- .../overview/fix_logs_step/external_links.tsx | 27 +-- .../components/overview/overview.tsx | 13 +- .../public/application/lib/api.ts | 20 ++ .../public/application/lib/ui_metric.ts | 49 ----- .../upgrade_assistant/public/plugin.ts | 10 +- .../plugins/upgrade_assistant/public/types.ts | 2 - .../lib/telemetry/es_ui_open_apis.test.ts | 48 +++++ .../server/lib/telemetry/es_ui_open_apis.ts | 57 ++++++ .../lib/telemetry/es_ui_reindex_apis.test.ts | 52 +++++ .../lib/telemetry/es_ui_reindex_apis.ts | 63 ++++++ .../lib/telemetry/usage_collector.test.ts | 31 +++ .../server/lib/telemetry/usage_collector.ts | 113 ++++++++++- .../upgrade_assistant/server/plugin.ts | 3 +- .../server/routes/register_routes.ts | 2 + .../server/routes/telemetry.test.ts | 187 ++++++++++++++++++ .../server/routes/telemetry.ts | 64 ++++++ .../telemetry_saved_object_type.ts | 36 ++++ 32 files changed, 809 insertions(+), 207 deletions(-) delete mode 100644 x-pack/plugins/upgrade_assistant/public/application/lib/ui_metric.ts create mode 100644 x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts create mode 100644 x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts create mode 100644 x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.test.ts create mode 100644 x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.ts create mode 100644 x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts create mode 100644 x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 2dd7b96cfef95..a46cc00317463 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -7324,6 +7324,44 @@ } } } + }, + "ui_open": { + "properties": { + "elasticsearch": { + "type": "long", + "_meta": { + "description": "Number of times a user viewed the list of Elasticsearch deprecations." + } + }, + "overview": { + "type": "long", + "_meta": { + "description": "Number of times a user viewed the Overview page." + } + }, + "kibana": { + "type": "long", + "_meta": { + "description": "Number of times a user viewed the list of Kibana deprecations" + } + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "type": "long" + }, + "open": { + "type": "long" + }, + "start": { + "type": "long" + }, + "stop": { + "type": "long" + } + } } } }, diff --git a/x-pack/plugins/upgrade_assistant/README.md b/x-pack/plugins/upgrade_assistant/README.md index 6570e7f8d7617..255eb94a0318c 100644 --- a/x-pack/plugins/upgrade_assistant/README.md +++ b/x-pack/plugins/upgrade_assistant/README.md @@ -226,29 +226,4 @@ This is a non-exhaustive list of different error scenarios in Upgrade Assistant. - **Error updating deprecation logging status.** Mock a `404` status code to `PUT /api/upgrade_assistant/deprecation_logging`. Alternatively, edit [this line](https://github.com/elastic/kibana/blob/545c1420c285af8f5eee56f414bd6eca735aea11/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts#L77) locally and replace `deprecation_logging` with `fake_deprecation_logging`. - **Unauthorized error fetching ES deprecations.** Mock a `403` status code to `GET /api/upgrade_assistant/es_deprecations` with the response payload: `{ "statusCode": 403 }` - **Partially upgraded error fetching ES deprecations.** Mock a `426` status code to `GET /api/upgrade_assistant/es_deprecations` with the response payload: `{ "statusCode": 426, "attributes": { "allNodesUpgraded": false } }` -- **Upgraded error fetching ES deprecations.** Mock a `426` status code to `GET /api/upgrade_assistant/es_deprecations` with the response payload: `{ "statusCode": 426, "attributes": { "allNodesUpgraded": true } }` - -### Telemetry - -The Upgrade Assistant tracks several triggered events in the UI, using Kibana Usage Collection service's [UI counters](https://github.com/elastic/kibana/blob/master/src/plugins/usage_collection/README.mdx#ui-counters). - -**Overview page** -- Component loaded -- Click event for "Create snapshot" button -- Click event for "View deprecation logs in Observability" link -- Click event for "Analyze logs in Discover" link -- Click event for "Reset counter" button - -**ES deprecations page** -- Component loaded -- Click events for starting and stopping reindex tasks -- Click events for upgrading or deleting a Machine Learning snapshot -- Click event for deleting a deprecated index setting - -**Kibana deprecations page** -- Component loaded -- Click event for "Quick resolve" button - -In addition to UI counters, the Upgrade Assistant has a [custom usage collector](https://github.com/elastic/kibana/blob/master/src/plugins/usage_collection/README.mdx#custom-collector). It currently is only responsible for tracking whether the user has deprecation logging enabled or not. - -For testing instructions, refer to the [Kibana Usage Collection service README](https://github.com/elastic/kibana/blob/master/src/plugins/usage_collection/README.mdx#testing). \ No newline at end of file +- **Upgraded error fetching ES deprecations.** Mock a `426` status code to `GET /api/upgrade_assistant/es_deprecations` with the response payload: `{ "statusCode": 426, "attributes": { "allNodesUpgraded": true } }` \ No newline at end of file diff --git a/x-pack/plugins/upgrade_assistant/common/types.ts b/x-pack/plugins/upgrade_assistant/common/types.ts index b8b05efacb590..928c06f0aa5d4 100644 --- a/x-pack/plugins/upgrade_assistant/common/types.ts +++ b/x-pack/plugins/upgrade_assistant/common/types.ts @@ -141,7 +141,32 @@ export interface UIReindex { stop: boolean; } +export interface UpgradeAssistantTelemetrySavedObject { + ui_open: { + overview: number; + elasticsearch: number; + kibana: number; + }; + ui_reindex: { + close: number; + open: number; + start: number; + stop: number; + }; +} + export interface UpgradeAssistantTelemetry { + ui_open: { + overview: number; + elasticsearch: number; + kibana: number; + }; + ui_reindex: { + close: number; + open: number; + start: number; + stop: number; + }; features: { deprecation_logging: { enabled: boolean; @@ -149,6 +174,10 @@ export interface UpgradeAssistantTelemetry { }; } +export interface UpgradeAssistantTelemetrySavedObjectAttributes { + [key: string]: any; +} + export type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; export interface DeprecationInfo { level: MIGRATION_DEPRECATION_LEVEL; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/flyout.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/flyout.tsx index e00edb4f3b11d..24c1897fbdd02 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/flyout.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/flyout.tsx @@ -5,9 +5,8 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; -import { METRIC_TYPE } from '@kbn/analytics'; import { EuiButton, EuiButtonEmpty, @@ -26,7 +25,6 @@ import { } from '@elastic/eui'; import { EnrichedDeprecationInfo, IndexSettingAction } from '../../../../../../common/types'; import type { ResponseError } from '../../../../lib/api'; -import { uiMetricService, UIM_INDEX_SETTINGS_DELETE_CLICK } from '../../../../lib/ui_metric'; import type { Status } from '../../../types'; import { DeprecationBadge } from '../../../shared'; @@ -109,11 +107,6 @@ export const RemoveIndexSettingsFlyout = ({ // Flag used to hide certain parts of the UI if the deprecation has been resolved or is in progress const isResolvable = ['idle', 'error'].includes(statusType); - const onRemoveSettings = useCallback(() => { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_INDEX_SETTINGS_DELETE_CLICK); - removeIndexSettings(index!, (correctiveAction as IndexSettingAction).deprecatedSettings); - }, [correctiveAction, index, removeIndexSettings]); - return ( <> @@ -197,7 +190,12 @@ export const RemoveIndexSettingsFlyout = ({ fill data-test-subj="deleteSettingsButton" color="danger" - onClick={onRemoveSettings} + onClick={() => + removeIndexSettings( + index!, + (correctiveAction as IndexSettingAction).deprecatedSettings + ) + } > {statusType === 'error' ? i18nTexts.retryRemoveButtonLabel diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/ml_snapshots/flyout.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/ml_snapshots/flyout.tsx index 2a36f3e33e83c..4e3d77ba72ae8 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/ml_snapshots/flyout.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/ml_snapshots/flyout.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { METRIC_TYPE } from '@kbn/analytics'; import { EuiButton, @@ -26,11 +25,6 @@ import { } from '@elastic/eui'; import { EnrichedDeprecationInfo } from '../../../../../../common/types'; -import { - uiMetricService, - UIM_ML_SNAPSHOT_UPGRADE_CLICK, - UIM_ML_SNAPSHOT_DELETE_CLICK, -} from '../../../../lib/ui_metric'; import { DeprecationBadge } from '../../../shared'; import { MlSnapshotContext } from './context'; import { useAppContext } from '../../../../app_context'; @@ -179,13 +173,11 @@ export const FixSnapshotsFlyout = ({ const isResolved = snapshotState.status === 'complete'; const onUpgradeSnapshot = () => { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_ML_SNAPSHOT_UPGRADE_CLICK); upgradeSnapshot(); closeFlyout(); }; const onDeleteSnapshot = () => { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_ML_SNAPSHOT_DELETE_CLICK); deleteSnapshot(); closeFlyout(); }; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/__snapshots__/checklist_step.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/__snapshots__/checklist_step.test.tsx.snap index 542cc51e45b9c..26119c2b62040 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/__snapshots__/checklist_step.test.tsx.snap +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/__snapshots__/checklist_step.test.tsx.snap @@ -42,7 +42,7 @@ exports[`ChecklistFlyout renders 1`] = ` { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_REINDEX_START_CLICK); - startReindex(); - }, [startReindex]); - - const onStopReindex = useCallback(() => { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_REINDEX_STOP_CLICK); - cancelReindex(); - }, [cancelReindex]); - return ( @@ -168,7 +152,7 @@ export const ChecklistFlyoutStep: React.FunctionComponent<{ /> - + @@ -186,7 +170,7 @@ export const ChecklistFlyoutStep: React.FunctionComponent<{ fill color={status === ReindexStatus.paused ? 'warning' : 'primary'} iconType={status === ReindexStatus.paused ? 'play' : undefined} - onClick={onStartReindex} + onClick={startReindex} isLoading={loading} disabled={loading || !hasRequiredPrivileges} data-test-subj="startReindexingButton" diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/table_row.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/table_row.tsx index 1059720e66a59..c2a14ca5be858 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/table_row.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/table_row.tsx @@ -7,15 +7,9 @@ import React, { useState, useEffect, useCallback } from 'react'; import { EuiTableRowCell } from '@elastic/eui'; -import { METRIC_TYPE } from '@kbn/analytics'; import { EnrichedDeprecationInfo } from '../../../../../../common/types'; import { GlobalFlyout } from '../../../../../shared_imports'; import { useAppContext } from '../../../../app_context'; -import { - uiMetricService, - UIM_REINDEX_CLOSE_FLYOUT_CLICK, - UIM_REINDEX_OPEN_FLYOUT_CLICK, -} from '../../../../lib/ui_metric'; import { DeprecationTableColumns } from '../../../types'; import { EsDeprecationsTableCells } from '../../es_deprecations_table_cells'; import { ReindexResolutionCell } from './resolution_table_cell'; @@ -35,6 +29,9 @@ const ReindexTableRowCells: React.FunctionComponent = ({ }) => { const [showFlyout, setShowFlyout] = useState(false); const reindexState = useReindexContext(); + const { + services: { api }, + } = useAppContext(); const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } = useGlobalFlyout(); @@ -42,8 +39,8 @@ const ReindexTableRowCells: React.FunctionComponent = ({ const closeFlyout = useCallback(async () => { removeContentFromGlobalFlyout('reindexFlyout'); setShowFlyout(false); - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_REINDEX_CLOSE_FLYOUT_CLICK); - }, [removeContentFromGlobalFlyout]); + await api.sendReindexTelemetryData({ close: true }); + }, [api, removeContentFromGlobalFlyout]); useEffect(() => { if (showFlyout) { @@ -67,9 +64,13 @@ const ReindexTableRowCells: React.FunctionComponent = ({ useEffect(() => { if (showFlyout) { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_REINDEX_OPEN_FLYOUT_CLICK); + async function sendTelemetry() { + await api.sendReindexTelemetryData({ open: true }); + } + + sendTelemetry(); } - }, [showFlyout]); + }, [showFlyout, api]); return ( <> diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/use_reindex_state.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/use_reindex_state.tsx index 8890411cca2a0..87891c5d8d219 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/use_reindex_state.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/use_reindex_state.tsx @@ -132,6 +132,8 @@ export const useReindexStatus = ({ indexName, api }: { indexName: string; api: A cancelLoadingState: undefined, }); + api.sendReindexTelemetryData({ start: true }); + const { data, error } = await api.startReindexTask(indexName); if (error) { @@ -149,6 +151,8 @@ export const useReindexStatus = ({ indexName, api }: { indexName: string; api: A }, [api, indexName, reindexState, updateStatus]); const cancelReindex = useCallback(async () => { + api.sendReindexTelemetryData({ stop: true }); + const { error } = await api.cancelReindexTask(indexName); setReindexState({ diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx index 7c3394d5a9c0f..c7d157c342c77 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx @@ -12,12 +12,10 @@ import { EuiPageHeader, EuiSpacer, EuiPageContent, EuiLink } from '@elastic/eui' import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { DocLinksStart } from 'kibana/public'; -import { METRIC_TYPE } from '@kbn/analytics'; import { EnrichedDeprecationInfo } from '../../../../common/types'; import { SectionLoading } from '../../../shared_imports'; import { useAppContext } from '../../app_context'; -import { uiMetricService, UIM_ES_DEPRECATIONS_PAGE_LOAD } from '../../lib/ui_metric'; import { getEsDeprecationError } from '../../lib/get_es_deprecation_error'; import { DeprecationsPageLoadingError, NoDeprecationsPrompt, DeprecationCount } from '../shared'; import { EsDeprecationsTable } from './es_deprecations_table'; @@ -84,7 +82,13 @@ export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => { }, } = useAppContext(); - const { data: esDeprecations, isLoading, error, resendRequest } = api.useLoadEsDeprecations(); + const { + data: esDeprecations, + isLoading, + error, + resendRequest, + isInitialRequest, + } = api.useLoadEsDeprecations(); const deprecationsCountByLevel: { warningDeprecations: number; @@ -99,8 +103,16 @@ export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => { }, [breadcrumbs]); useEffect(() => { - uiMetricService.trackUiMetric(METRIC_TYPE.LOADED, UIM_ES_DEPRECATIONS_PAGE_LOAD); - }, []); + if (isLoading === false && isInitialRequest) { + async function sendTelemetryData() { + await api.sendPageTelemetryData({ + elasticsearch: true, + }); + } + + sendTelemetryData(); + } + }, [api, isLoading, isInitialRequest]); if (error) { return ( diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/deprecation_details_flyout.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/deprecation_details_flyout.tsx index 37b07bc159577..a1242c2da9b0c 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/deprecation_details_flyout.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/deprecation_details_flyout.tsx @@ -5,10 +5,9 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { METRIC_TYPE } from '@kbn/analytics'; import { EuiButtonEmpty, @@ -25,7 +24,6 @@ import { EuiSpacer, } from '@elastic/eui'; -import { uiMetricService, UIM_KIBANA_QUICK_RESOLVE_CLICK } from '../../lib/ui_metric'; import type { DeprecationResolutionState, KibanaDeprecationDetails } from './kibana_deprecations'; import { DeprecationBadge } from '../shared'; @@ -136,11 +134,6 @@ export const DeprecationDetailsFlyout = ({ const isCurrent = deprecationResolutionState?.id === deprecation.id; const isResolved = isCurrent && deprecationResolutionState?.resolveDeprecationStatus === 'ok'; - const onResolveDeprecation = useCallback(() => { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_KIBANA_QUICK_RESOLVE_CLICK); - resolveDeprecation(deprecation); - }, [deprecation, resolveDeprecation]); - return ( <> @@ -242,7 +235,7 @@ export const DeprecationDetailsFlyout = ({ resolveDeprecation(deprecation)} isLoading={Boolean( deprecationResolutionState?.resolveDeprecationStatus === 'in_progress' )} diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecations.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecations.tsx index b488c84f255cc..23697b00923c8 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecations.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecations.tsx @@ -11,12 +11,10 @@ import { withRouter, RouteComponentProps } from 'react-router-dom'; import { EuiPageContent, EuiPageHeader, EuiSpacer, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { METRIC_TYPE } from '@kbn/analytics'; import type { DomainDeprecationDetails } from 'kibana/public'; import { SectionLoading, GlobalFlyout } from '../../../shared_imports'; import { useAppContext } from '../../app_context'; -import { uiMetricService, UIM_KIBANA_DEPRECATIONS_PAGE_LOAD } from '../../lib/ui_metric'; import { DeprecationsPageLoadingError, NoDeprecationsPrompt, DeprecationCount } from '../shared'; import { KibanaDeprecationsTable } from './kibana_deprecations_table'; import { @@ -118,6 +116,7 @@ export const KibanaDeprecations = withRouter(({ history }: RouteComponentProps) services: { core: { deprecations }, breadcrumbs, + api, }, } = useAppContext(); @@ -226,8 +225,14 @@ export const KibanaDeprecations = withRouter(({ history }: RouteComponentProps) ]); useEffect(() => { - uiMetricService.trackUiMetric(METRIC_TYPE.LOADED, UIM_KIBANA_DEPRECATIONS_PAGE_LOAD); - }, []); + async function sendTelemetryData() { + await api.sendPageTelemetryData({ + kibana: true, + }); + } + + sendTelemetryData(); + }, [api]); useEffect(() => { breadcrumbs.setBreadcrumbs('kibanaDeprecations'); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/cloud_backup.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/cloud_backup.tsx index 4ab860a0bf6a7..55a6ee8e5c73f 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/cloud_backup.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/cloud_backup.tsx @@ -9,7 +9,6 @@ import React, { useEffect } from 'react'; import moment from 'moment-timezone'; import { FormattedDate, FormattedTime, FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { METRIC_TYPE } from '@kbn/analytics'; import { EuiLoadingContent, EuiFlexGroup, @@ -22,7 +21,6 @@ import { } from '@elastic/eui'; import { useAppContext } from '../../../app_context'; -import { uiMetricService, UIM_BACKUP_DATA_CLOUD_CLICK } from '../../../lib/ui_metric'; interface Props { cloudSnapshotsUrl: string; @@ -130,13 +128,11 @@ export const CloudBackup: React.FunctionComponent = ({ return ( <> {statusMessage} + - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_BACKUP_DATA_CLOUD_CLICK); - }} data-test-subj="cloudSnapshotsLink" target="_blank" iconType="popout" diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/on_prem_backup.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/on_prem_backup.tsx index 69100b36db7eb..2e2e2bd5ce48e 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/on_prem_backup.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/backup_step/on_prem_backup.tsx @@ -8,11 +8,9 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { METRIC_TYPE } from '@kbn/analytics'; import { EuiText, EuiButton, EuiSpacer } from '@elastic/eui'; import { useAppContext } from '../../../app_context'; -import { uiMetricService, UIM_BACKUP_DATA_ON_PREM_CLICK } from '../../../lib/ui_metric'; const SnapshotRestoreAppLink: React.FunctionComponent = () => { const { @@ -24,14 +22,7 @@ const SnapshotRestoreAppLink: React.FunctionComponent = () => { ?.useUrl({ page: 'snapshots' }); return ( - // eslint-disable-next-line @elastic/eui/href-or-on-click - { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_BACKUP_DATA_ON_PREM_CLICK); - }} - data-test-subj="snapshotRestoreLink" - > + ( @@ -73,7 +71,6 @@ export const DeprecationsCountCheckpoint: FunctionComponent = ({ const onResetClick = () => { const now = moment().toISOString(); - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_RESET_LOGS_COUNTER_CLICK); setCheckpoint(now); }; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/external_links.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/external_links.tsx index 69f1f14d4eb58..d027b2f262e9e 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/external_links.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/external_links.tsx @@ -9,17 +9,10 @@ import { encode } from 'rison-node'; import React, { FunctionComponent, useState, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { METRIC_TYPE } from '@kbn/analytics'; import { EuiLink, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiPanel, EuiText } from '@elastic/eui'; -import { DataPublicPluginStart } from '../../../../shared_imports'; import { useAppContext } from '../../../app_context'; -import { - uiMetricService, - UIM_OBSERVABILITY_CLICK, - UIM_DISCOVER_CLICK, -} from '../../../lib/ui_metric'; - +import { DataPublicPluginStart } from '../../../../shared_imports'; import { DEPRECATION_LOGS_INDEX_PATTERN, DEPRECATION_LOGS_SOURCE_ID, @@ -80,14 +73,7 @@ const DiscoverAppLink: FunctionComponent = ({ checkpoint }) => { }, [dataService, checkpoint, share.url.locators]); return ( - // eslint-disable-next-line @elastic/eui/href-or-on-click - { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_DISCOVER_CLICK); - }} - data-test-subj="viewDiscoverLogs" - > + = ({ checkpoint }) => { ); return ( - // eslint-disable-next-line @elastic/eui/href-or-on-click - { - uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_OBSERVABILITY_CLICK); - }} - data-test-subj="viewObserveLogs" - > + { kibanaVersionInfo: { nextMajor }, services: { breadcrumbs, + api, core: { docLinks }, }, plugins: { cloud }, } = useAppContext(); useEffect(() => { - uiMetricService.trackUiMetric(METRIC_TYPE.LOADED, UIM_OVERVIEW_PAGE_LOAD); - }, []); + async function sendTelemetryData() { + await api.sendPageTelemetryData({ + overview: true, + }); + } + + sendTelemetryData(); + }, [api]); useEffect(() => { breadcrumbs.setBreadcrumbs('overview'); diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts index 1d51510333ef4..da4f87f497467 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts @@ -65,6 +65,16 @@ export class ApiService { }); } + public async sendPageTelemetryData(telemetryData: { [tabName: string]: boolean }) { + const result = await this.sendRequest({ + path: `${API_BASE_PATH}/stats/ui_open`, + method: 'put', + body: JSON.stringify(telemetryData), + }); + + return result; + } + public useLoadDeprecationLogging() { return this.useRequest<{ isDeprecationLogIndexingEnabled: boolean; @@ -140,6 +150,16 @@ export class ApiService { }); } + public async sendReindexTelemetryData(telemetryData: { [key: string]: boolean }) { + const result = await this.sendRequest({ + path: `${API_BASE_PATH}/stats/ui_reindex`, + method: 'put', + body: JSON.stringify(telemetryData), + }); + + return result; + } + public async getReindexStatus(indexName: string) { return await this.sendRequest({ path: `${API_BASE_PATH}/reindex/${indexName}`, diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/ui_metric.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/ui_metric.ts deleted file mode 100644 index 394f046a8bafe..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/lib/ui_metric.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { UiCounterMetricType } from '@kbn/analytics'; -import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; - -export const UIM_APP_NAME = 'upgrade_assistant'; -export const UIM_ES_DEPRECATIONS_PAGE_LOAD = 'es_deprecations_page_load'; -export const UIM_KIBANA_DEPRECATIONS_PAGE_LOAD = 'kibana_deprecations_page_load'; -export const UIM_OVERVIEW_PAGE_LOAD = 'overview_page_load'; -export const UIM_REINDEX_OPEN_FLYOUT_CLICK = 'reindex_open_flyout_click'; -export const UIM_REINDEX_CLOSE_FLYOUT_CLICK = 'reindex_close_flyout_click'; -export const UIM_REINDEX_START_CLICK = 'reindex_start_click'; -export const UIM_REINDEX_STOP_CLICK = 'reindex_stop_click'; -export const UIM_BACKUP_DATA_CLOUD_CLICK = 'backup_data_cloud_click'; -export const UIM_BACKUP_DATA_ON_PREM_CLICK = 'backup_data_on_prem_click'; -export const UIM_RESET_LOGS_COUNTER_CLICK = 'reset_logs_counter_click'; -export const UIM_OBSERVABILITY_CLICK = 'observability_click'; -export const UIM_DISCOVER_CLICK = 'discover_click'; -export const UIM_ML_SNAPSHOT_UPGRADE_CLICK = 'ml_snapshot_upgrade_click'; -export const UIM_ML_SNAPSHOT_DELETE_CLICK = 'ml_snapshot_delete_click'; -export const UIM_INDEX_SETTINGS_DELETE_CLICK = 'index_settings_delete_click'; -export const UIM_KIBANA_QUICK_RESOLVE_CLICK = 'kibana_quick_resolve_click'; - -export class UiMetricService { - private usageCollection: UsageCollectionSetup | undefined; - - public setup(usageCollection: UsageCollectionSetup) { - this.usageCollection = usageCollection; - } - - private track(metricType: UiCounterMetricType, eventName: string | string[]) { - if (!this.usageCollection) { - // Usage collection might be disabled in Kibana config. - return; - } - return this.usageCollection.reportUiCounter(UIM_APP_NAME, metricType, eventName); - } - - public trackUiMetric(metricType: UiCounterMetricType, eventName: string | string[]) { - return this.track(metricType, eventName); - } -} - -export const uiMetricService = new UiMetricService(); diff --git a/x-pack/plugins/upgrade_assistant/public/plugin.ts b/x-pack/plugins/upgrade_assistant/public/plugin.ts index d688ee510ce1f..64c21997b12c4 100644 --- a/x-pack/plugins/upgrade_assistant/public/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/public/plugin.ts @@ -11,7 +11,6 @@ import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/public'; import { apiService } from './application/lib/api'; import { breadcrumbService } from './application/lib/breadcrumbs'; -import { uiMetricService } from './application/lib/ui_metric'; import { SetupDependencies, StartDependencies, AppDependencies } from './types'; import { Config } from '../common/config'; @@ -19,10 +18,7 @@ export class UpgradeAssistantUIPlugin implements Plugin { constructor(private ctx: PluginInitializerContext) {} - setup( - coreSetup: CoreSetup, - { management, cloud, share, usageCollection }: SetupDependencies - ) { + setup(coreSetup: CoreSetup, { management, cloud, share }: SetupDependencies) { const { readonly } = this.ctx.config.get(); const appRegistrar = management.sections.section.stack; @@ -38,10 +34,6 @@ export class UpgradeAssistantUIPlugin defaultMessage: 'Upgrade Assistant', }); - if (usageCollection) { - uiMetricService.setup(usageCollection); - } - appRegistrar.registerApp({ id: 'upgrade_assistant', title: pluginName, diff --git a/x-pack/plugins/upgrade_assistant/public/types.ts b/x-pack/plugins/upgrade_assistant/public/types.ts index e58c90336d856..de5f29593b7c6 100644 --- a/x-pack/plugins/upgrade_assistant/public/types.ts +++ b/x-pack/plugins/upgrade_assistant/public/types.ts @@ -10,7 +10,6 @@ import { ManagementSetup } from 'src/plugins/management/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { SharePluginSetup } from 'src/plugins/share/public'; import { CoreStart } from 'src/core/public'; -import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { CloudSetup } from '../../cloud/public'; import { LicensingPluginStart } from '../../licensing/public'; import { BreadcrumbService } from './application/lib/breadcrumbs'; @@ -26,7 +25,6 @@ export interface SetupDependencies { management: ManagementSetup; share: SharePluginSetup; cloud?: CloudSetup; - usageCollection?: UsageCollectionSetup; } export interface StartDependencies { diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts new file mode 100644 index 0000000000000..caff78390b9d1 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { savedObjectsRepositoryMock } from 'src/core/server/mocks'; +import { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../common/types'; + +import { upsertUIOpenOption } from './es_ui_open_apis'; + +/** + * Since these route callbacks are so thin, these serve simply as integration tests + * to ensure they're wired up to the lib functions correctly. Business logic is tested + * more thoroughly in the lib/telemetry tests. + */ +describe('Upgrade Assistant Telemetry SavedObject UIOpen', () => { + describe('Upsert UIOpen Option', () => { + it('call saved objects internal repository with the correct info', async () => { + const internalRepo = savedObjectsRepositoryMock.create(); + + await upsertUIOpenOption({ + overview: true, + elasticsearch: true, + kibana: true, + savedObjects: { createInternalRepository: () => internalRepo } as any, + }); + + expect(internalRepo.incrementCounter).toHaveBeenCalledTimes(3); + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( + UPGRADE_ASSISTANT_TYPE, + UPGRADE_ASSISTANT_DOC_ID, + ['ui_open.overview'] + ); + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( + UPGRADE_ASSISTANT_TYPE, + UPGRADE_ASSISTANT_DOC_ID, + ['ui_open.elasticsearch'] + ); + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( + UPGRADE_ASSISTANT_TYPE, + UPGRADE_ASSISTANT_DOC_ID, + ['ui_open.kibana'] + ); + }); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts new file mode 100644 index 0000000000000..3d463fe4b03ed --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsServiceStart } from 'src/core/server'; +import { + UIOpen, + UIOpenOption, + UPGRADE_ASSISTANT_DOC_ID, + UPGRADE_ASSISTANT_TYPE, +} from '../../../common/types'; + +interface IncrementUIOpenDependencies { + uiOpenOptionCounter: UIOpenOption; + savedObjects: SavedObjectsServiceStart; +} + +async function incrementUIOpenOptionCounter({ + savedObjects, + uiOpenOptionCounter, +}: IncrementUIOpenDependencies) { + const internalRepository = savedObjects.createInternalRepository(); + + await internalRepository.incrementCounter(UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, [ + `ui_open.${uiOpenOptionCounter}`, + ]); +} + +type UpsertUIOpenOptionDependencies = UIOpen & { savedObjects: SavedObjectsServiceStart }; + +export async function upsertUIOpenOption({ + overview, + elasticsearch, + savedObjects, + kibana, +}: UpsertUIOpenOptionDependencies): Promise { + if (overview) { + await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'overview' }); + } + + if (elasticsearch) { + await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'elasticsearch' }); + } + + if (kibana) { + await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'kibana' }); + } + + return { + overview, + elasticsearch, + kibana, + }; +} diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.test.ts new file mode 100644 index 0000000000000..6a05e8a697bb8 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.test.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { savedObjectsRepositoryMock } from 'src/core/server/mocks'; +import { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE } from '../../../common/types'; +import { upsertUIReindexOption } from './es_ui_reindex_apis'; + +/** + * Since these route callbacks are so thin, these serve simply as integration tests + * to ensure they're wired up to the lib functions correctly. Business logic is tested + * more thoroughly in the lib/telemetry tests. + */ +describe('Upgrade Assistant Telemetry SavedObject UIReindex', () => { + describe('Upsert UIReindex Option', () => { + it('call saved objects internal repository with the correct info', async () => { + const internalRepo = savedObjectsRepositoryMock.create(); + await upsertUIReindexOption({ + close: true, + open: true, + start: true, + stop: true, + savedObjects: { createInternalRepository: () => internalRepo } as any, + }); + + expect(internalRepo.incrementCounter).toHaveBeenCalledTimes(4); + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( + UPGRADE_ASSISTANT_TYPE, + UPGRADE_ASSISTANT_DOC_ID, + [`ui_reindex.close`] + ); + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( + UPGRADE_ASSISTANT_TYPE, + UPGRADE_ASSISTANT_DOC_ID, + [`ui_reindex.open`] + ); + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( + UPGRADE_ASSISTANT_TYPE, + UPGRADE_ASSISTANT_DOC_ID, + [`ui_reindex.start`] + ); + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( + UPGRADE_ASSISTANT_TYPE, + UPGRADE_ASSISTANT_DOC_ID, + [`ui_reindex.stop`] + ); + }); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.ts new file mode 100644 index 0000000000000..caee1a58a4006 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_reindex_apis.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsServiceStart } from 'src/core/server'; +import { + UIReindex, + UIReindexOption, + UPGRADE_ASSISTANT_DOC_ID, + UPGRADE_ASSISTANT_TYPE, +} from '../../../common/types'; + +interface IncrementUIReindexOptionDependencies { + uiReindexOptionCounter: UIReindexOption; + savedObjects: SavedObjectsServiceStart; +} + +async function incrementUIReindexOptionCounter({ + savedObjects, + uiReindexOptionCounter, +}: IncrementUIReindexOptionDependencies) { + const internalRepository = savedObjects.createInternalRepository(); + + await internalRepository.incrementCounter(UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, [ + `ui_reindex.${uiReindexOptionCounter}`, + ]); +} + +type UpsertUIReindexOptionDepencies = UIReindex & { savedObjects: SavedObjectsServiceStart }; + +export async function upsertUIReindexOption({ + start, + close, + open, + stop, + savedObjects, +}: UpsertUIReindexOptionDepencies): Promise { + if (close) { + await incrementUIReindexOptionCounter({ savedObjects, uiReindexOptionCounter: 'close' }); + } + + if (open) { + await incrementUIReindexOptionCounter({ savedObjects, uiReindexOptionCounter: 'open' }); + } + + if (start) { + await incrementUIReindexOptionCounter({ savedObjects, uiReindexOptionCounter: 'start' }); + } + + if (stop) { + await incrementUIReindexOptionCounter({ savedObjects, uiReindexOptionCounter: 'stop' }); + } + + return { + close, + open, + start, + stop, + }; +} diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts index 34d329557f11e..50c5b358aa5cb 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts @@ -47,6 +47,26 @@ describe('Upgrade Assistant Usage Collector', () => { }; dependencies = { usageCollection, + savedObjects: { + createInternalRepository: jest.fn().mockImplementation(() => { + return { + get: () => { + return { + attributes: { + 'ui_open.overview': 10, + 'ui_open.elasticsearch': 20, + 'ui_open.kibana': 15, + 'ui_reindex.close': 1, + 'ui_reindex.open': 4, + 'ui_reindex.start': 2, + 'ui_reindex.stop': 1, + 'ui_reindex.not_defined': 1, + }, + }; + }, + }; + }), + }, elasticsearch: { client: clusterClient, }, @@ -71,6 +91,17 @@ describe('Upgrade Assistant Usage Collector', () => { callClusterStub ); expect(upgradeAssistantStats).toEqual({ + ui_open: { + overview: 10, + elasticsearch: 20, + kibana: 15, + }, + ui_reindex: { + close: 1, + open: 4, + start: 2, + stop: 1, + }, features: { deprecation_logging: { enabled: true, diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts index c535cd14f104d..56932f5e54b06 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts @@ -5,14 +5,43 @@ * 2.0. */ -import { ElasticsearchClient, ElasticsearchServiceStart } from 'src/core/server'; +import { get } from 'lodash'; +import { + ElasticsearchClient, + ElasticsearchServiceStart, + ISavedObjectsRepository, + SavedObjectsServiceStart, +} from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { UpgradeAssistantTelemetry } from '../../../common/types'; +import { + UPGRADE_ASSISTANT_DOC_ID, + UPGRADE_ASSISTANT_TYPE, + UpgradeAssistantTelemetry, + UpgradeAssistantTelemetrySavedObject, + UpgradeAssistantTelemetrySavedObjectAttributes, +} from '../../../common/types'; import { isDeprecationLogIndexingEnabled, isDeprecationLoggingEnabled, } from '../es_deprecation_logging_apis'; +async function getSavedObjectAttributesFromRepo( + savedObjectsRepository: ISavedObjectsRepository, + docType: string, + docID: string +) { + try { + return ( + await savedObjectsRepository.get( + docType, + docID + ) + ).attributes; + } catch (e) { + return null; + } +} + async function getDeprecationLoggingStatusValue(esClient: ElasticsearchClient): Promise { try { const { body: loggerDeprecationCallResult } = await esClient.cluster.getSettings({ @@ -28,14 +57,58 @@ async function getDeprecationLoggingStatusValue(esClient: ElasticsearchClient): } } -export async function fetchUpgradeAssistantMetrics({ - client: esClient, -}: ElasticsearchServiceStart): Promise { +export async function fetchUpgradeAssistantMetrics( + { client: esClient }: ElasticsearchServiceStart, + savedObjects: SavedObjectsServiceStart +): Promise { + const savedObjectsRepository = savedObjects.createInternalRepository(); + const upgradeAssistantSOAttributes = await getSavedObjectAttributesFromRepo( + savedObjectsRepository, + UPGRADE_ASSISTANT_TYPE, + UPGRADE_ASSISTANT_DOC_ID + ); const deprecationLoggingStatusValue = await getDeprecationLoggingStatusValue( esClient.asInternalUser ); + const getTelemetrySavedObject = ( + upgradeAssistantTelemetrySavedObjectAttrs: UpgradeAssistantTelemetrySavedObjectAttributes | null + ): UpgradeAssistantTelemetrySavedObject => { + const defaultTelemetrySavedObject = { + ui_open: { + overview: 0, + elasticsearch: 0, + kibana: 0, + }, + ui_reindex: { + close: 0, + open: 0, + start: 0, + stop: 0, + }, + }; + + if (!upgradeAssistantTelemetrySavedObjectAttrs) { + return defaultTelemetrySavedObject; + } + + return { + ui_open: { + overview: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.overview', 0), + elasticsearch: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.elasticsearch', 0), + kibana: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.kibana', 0), + }, + ui_reindex: { + close: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_reindex.close', 0), + open: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_reindex.open', 0), + start: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_reindex.start', 0), + stop: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_reindex.stop', 0), + }, + } as UpgradeAssistantTelemetrySavedObject; + }; + return { + ...getTelemetrySavedObject(upgradeAssistantSOAttributes), features: { deprecation_logging: { enabled: deprecationLoggingStatusValue, @@ -46,12 +119,14 @@ export async function fetchUpgradeAssistantMetrics({ interface Dependencies { elasticsearch: ElasticsearchServiceStart; + savedObjects: SavedObjectsServiceStart; usageCollection: UsageCollectionSetup; } export function registerUpgradeAssistantUsageCollector({ elasticsearch, usageCollection, + savedObjects, }: Dependencies) { const upgradeAssistantUsageCollector = usageCollection.makeUsageCollector({ @@ -68,8 +143,34 @@ export function registerUpgradeAssistantUsageCollector({ }, }, }, + ui_open: { + elasticsearch: { + type: 'long', + _meta: { + description: 'Number of times a user viewed the list of Elasticsearch deprecations.', + }, + }, + overview: { + type: 'long', + _meta: { + description: 'Number of times a user viewed the Overview page.', + }, + }, + kibana: { + type: 'long', + _meta: { + description: 'Number of times a user viewed the list of Kibana deprecations', + }, + }, + }, + ui_reindex: { + close: { type: 'long' }, + open: { type: 'long' }, + start: { type: 'long' }, + stop: { type: 'long' }, + }, }, - fetch: async () => fetchUpgradeAssistantMetrics(elasticsearch), + fetch: async () => fetchUpgradeAssistantMetrics(elasticsearch, savedObjects), }); usageCollection.registerCollector(upgradeAssistantUsageCollector); diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts index 717f03758f825..2062cf982d15f 100644 --- a/x-pack/plugins/upgrade_assistant/server/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts @@ -142,10 +142,11 @@ export class UpgradeAssistantServerPlugin implements Plugin { registerRoutes(dependencies, this.getWorker.bind(this)); if (usageCollection) { - getStartServices().then(([{ elasticsearch }]) => { + getStartServices().then(([{ savedObjects: savedObjectsService, elasticsearch }]) => { registerUpgradeAssistantUsageCollector({ elasticsearch, usageCollection, + savedObjects: savedObjectsService, }); }); } diff --git a/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts b/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts index 002f34a489cff..8b63233a124a7 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts @@ -12,6 +12,7 @@ import { registerCloudBackupStatusRoutes } from './cloud_backup_status'; import { registerESDeprecationRoutes } from './es_deprecations'; import { registerDeprecationLoggingRoutes } from './deprecation_logging'; import { registerReindexIndicesRoutes } from './reindex_indices'; +import { registerTelemetryRoutes } from './telemetry'; import { registerUpdateSettingsRoute } from './update_index_settings'; import { registerMlSnapshotRoutes } from './ml_snapshots'; import { ReindexWorker } from '../lib/reindexing'; @@ -23,6 +24,7 @@ export function registerRoutes(dependencies: RouteDependencies, getWorker: () => registerESDeprecationRoutes(dependencies); registerDeprecationLoggingRoutes(dependencies); registerReindexIndicesRoutes(dependencies, getWorker); + registerTelemetryRoutes(dependencies); registerUpdateSettingsRoute(dependencies); registerMlSnapshotRoutes(dependencies); // Route for cloud to retrieve the upgrade status for ES and Kibana diff --git a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts new file mode 100644 index 0000000000000..578cceb702751 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.test.ts @@ -0,0 +1,187 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kibanaResponseFactory } from 'src/core/server'; +import { savedObjectsServiceMock } from 'src/core/server/mocks'; +import { createMockRouter, MockRouter, routeHandlerContextMock } from './__mocks__/routes.mock'; +import { createRequestMock } from './__mocks__/request.mock'; + +jest.mock('../lib/telemetry/es_ui_open_apis', () => ({ + upsertUIOpenOption: jest.fn(), +})); + +jest.mock('../lib/telemetry/es_ui_reindex_apis', () => ({ + upsertUIReindexOption: jest.fn(), +})); + +import { upsertUIOpenOption } from '../lib/telemetry/es_ui_open_apis'; +import { upsertUIReindexOption } from '../lib/telemetry/es_ui_reindex_apis'; +import { registerTelemetryRoutes } from './telemetry'; + +/** + * Since these route callbacks are so thin, these serve simply as integration tests + * to ensure they're wired up to the lib functions correctly. Business logic is tested + * more thoroughly in the lib/telemetry tests. + */ +describe('Upgrade Assistant Telemetry API', () => { + let routeDependencies: any; + let mockRouter: MockRouter; + beforeEach(() => { + mockRouter = createMockRouter(); + routeDependencies = { + getSavedObjectsService: () => savedObjectsServiceMock.create(), + router: mockRouter, + }; + registerTelemetryRoutes(routeDependencies); + }); + afterEach(() => jest.clearAllMocks()); + + describe('PUT /api/upgrade_assistant/stats/ui_open', () => { + it('returns correct payload with single option', async () => { + const returnPayload = { + overview: true, + elasticsearch: false, + kibana: false, + }; + + (upsertUIOpenOption as jest.Mock).mockResolvedValue(returnPayload); + + const resp = await routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/stats/ui_open', + })( + routeHandlerContextMock, + createRequestMock({ body: returnPayload }), + kibanaResponseFactory + ); + + expect(resp.payload).toEqual(returnPayload); + }); + + it('returns correct payload with multiple option', async () => { + const returnPayload = { + overview: true, + elasticsearch: true, + kibana: true, + }; + + (upsertUIOpenOption as jest.Mock).mockResolvedValue(returnPayload); + + const resp = await routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/stats/ui_open', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + overview: true, + elasticsearch: true, + kibana: true, + }, + }), + kibanaResponseFactory + ); + + expect(resp.payload).toEqual(returnPayload); + }); + + it('returns an error if it throws', async () => { + (upsertUIOpenOption as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + + await expect( + routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/stats/ui_open', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + overview: false, + }, + }), + kibanaResponseFactory + ) + ).rejects.toThrowError('scary error!'); + }); + }); + + describe('PUT /api/upgrade_assistant/stats/ui_reindex', () => { + it('returns correct payload with single option', async () => { + const returnPayload = { + close: false, + open: false, + start: true, + stop: false, + }; + + (upsertUIReindexOption as jest.Mock).mockResolvedValue(returnPayload); + + const resp = await routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/stats/ui_reindex', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + overview: false, + }, + }), + kibanaResponseFactory + ); + + expect(resp.payload).toEqual(returnPayload); + }); + + it('returns correct payload with multiple option', async () => { + const returnPayload = { + close: true, + open: true, + start: true, + stop: true, + }; + + (upsertUIReindexOption as jest.Mock).mockResolvedValue(returnPayload); + + const resp = await routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/stats/ui_reindex', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + close: true, + open: true, + start: true, + stop: true, + }, + }), + kibanaResponseFactory + ); + + expect(resp.payload).toEqual(returnPayload); + }); + + it('returns an error if it throws', async () => { + (upsertUIReindexOption as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + + await expect( + routeDependencies.router.getHandler({ + method: 'put', + pathPattern: '/api/upgrade_assistant/stats/ui_reindex', + })( + routeHandlerContextMock, + createRequestMock({ + body: { + start: false, + }, + }), + kibanaResponseFactory + ) + ).rejects.toThrowError('scary error!'); + }); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts new file mode 100644 index 0000000000000..d083b38c7c240 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { API_BASE_PATH } from '../../common/constants'; +import { upsertUIOpenOption } from '../lib/telemetry/es_ui_open_apis'; +import { upsertUIReindexOption } from '../lib/telemetry/es_ui_reindex_apis'; +import { RouteDependencies } from '../types'; + +export function registerTelemetryRoutes({ router, getSavedObjectsService }: RouteDependencies) { + router.put( + { + path: `${API_BASE_PATH}/stats/ui_open`, + validate: { + body: schema.object({ + overview: schema.boolean({ defaultValue: false }), + elasticsearch: schema.boolean({ defaultValue: false }), + kibana: schema.boolean({ defaultValue: false }), + }), + }, + }, + async (ctx, request, response) => { + const { elasticsearch, overview, kibana } = request.body; + return response.ok({ + body: await upsertUIOpenOption({ + savedObjects: getSavedObjectsService(), + elasticsearch, + overview, + kibana, + }), + }); + } + ); + + router.put( + { + path: `${API_BASE_PATH}/stats/ui_reindex`, + validate: { + body: schema.object({ + close: schema.boolean({ defaultValue: false }), + open: schema.boolean({ defaultValue: false }), + start: schema.boolean({ defaultValue: false }), + stop: schema.boolean({ defaultValue: false }), + }), + }, + }, + async (ctx, request, response) => { + const { close, open, start, stop } = request.body; + return response.ok({ + body: await upsertUIReindexOption({ + savedObjects: getSavedObjectsService(), + close, + open, + start, + stop, + }), + }); + } + ); +} diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts index cb3fbcaef59b7..42d5d339dd050 100644 --- a/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts +++ b/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts @@ -15,6 +15,42 @@ export const telemetrySavedObjectType: SavedObjectsType = { namespaceType: 'agnostic', mappings: { properties: { + ui_open: { + properties: { + overview: { + type: 'long', + null_value: 0, + }, + elasticsearch: { + type: 'long', + null_value: 0, + }, + kibana: { + type: 'long', + null_value: 0, + }, + }, + }, + ui_reindex: { + properties: { + close: { + type: 'long', + null_value: 0, + }, + open: { + type: 'long', + null_value: 0, + }, + start: { + type: 'long', + null_value: 0, + }, + stop: { + type: 'long', + null_value: 0, + }, + }, + }, features: { properties: { deprecation_logging: {