From 31a8b7bca6c8912cc8d80aa2fedc34cc0e53f358 Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Mon, 27 Nov 2023 07:44:14 -0700 Subject: [PATCH] [Dashboard Navigation] Add Links telemetry (#171877) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/elastic/kibana/issues/164305 ## Summary This PR adds two `uiCounters` to keep track of when something is clicked in the new Links panel: 1. `dashboardLink:click` - counts when a dashboard link is clicked 2. `externalLink:click` - counts when an external link is clicked These counters can be tracked via the `kibana-ui-counters` data view on the telemetry clusters, like so: ![Screenshot 2023-11-23 at 1 37 26 PM](https://github.com/elastic/kibana/assets/8698078/fe719121-73e3-4b53-8440-5a725a1a7c98) Note that this **only** applies if the `onClick` method is called; if the user, for example, right clicks on the link and selects "Open in new tab" instead, this "click" will not be tracked. To my knowledge, there is no way to track these types of clicks. ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/links/kibana.jsonc | 2 +- .../dashboard_link_component.tsx | 32 ++++++++++--------- .../external_link/external_link_component.tsx | 16 +++++++--- src/plugins/links/public/plugin.ts | 2 ++ .../links/public/services/kibana_services.ts | 10 +++++- src/plugins/links/tsconfig.json | 2 ++ 6 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/plugins/links/kibana.jsonc b/src/plugins/links/kibana.jsonc index a1e59cff9dc3b..a058db8a03ce2 100644 --- a/src/plugins/links/kibana.jsonc +++ b/src/plugins/links/kibana.jsonc @@ -17,7 +17,7 @@ "uiActionsEnhanced", "visualizations" ], - "optionalPlugins": ["triggersActionsUi"], + "optionalPlugins": ["triggersActionsUi", "usageCollection"], "requiredBundles": ["savedObjects"] } } diff --git a/src/plugins/links/public/components/dashboard_link/dashboard_link_component.tsx b/src/plugins/links/public/components/dashboard_link/dashboard_link_component.tsx index 4ad4608080b52..30977b593238a 100644 --- a/src/plugins/links/public/components/dashboard_link/dashboard_link_component.tsx +++ b/src/plugins/links/public/components/dashboard_link/dashboard_link_component.tsx @@ -12,6 +12,7 @@ import useAsync from 'react-use/lib/useAsync'; import useObservable from 'react-use/lib/useObservable'; import { EuiButtonEmpty, EuiListGroupItem } from '@elastic/eui'; +import { METRIC_TYPE } from '@kbn/analytics'; import { DashboardLocatorParams, getDashboardLocatorParamsFromEmbeddable, @@ -22,7 +23,13 @@ import { DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS, } from '@kbn/presentation-util-plugin/public'; -import { Link, LinksLayoutType, LINKS_VERTICAL_LAYOUT } from '../../../common/content_management'; +import { + DASHBOARD_LINK_TYPE, + Link, + LinksLayoutType, + LINKS_VERTICAL_LAYOUT, +} from '../../../common/content_management'; +import { trackUiMetric } from '../../services/kibana_services'; import { useLinks } from '../links_hooks'; import { DashboardLinkStrings } from './dashboard_link_strings'; import { fetchDashboard } from './dashboard_link_tools'; @@ -97,9 +104,9 @@ export const DashboardLinkComponent = ({ /** * Dashboard-to-dashboard navigation */ - const { loading: loadingOnClickProps, value: onClickProps } = useAsync(async () => { + const onClickProps = useMemo(() => { /** If the link points to the current dashboard, then there should be no `onClick` or `href` prop */ - if (link.destination === parentDashboardId) return; + if (!link.destination || link.destination === parentDashboardId) return; const linkOptions = { ...DEFAULT_DASHBOARD_DRILLDOWN_OPTIONS, @@ -118,6 +125,8 @@ export const DashboardLinkComponent = ({ return { href, onClick: async (event: React.MouseEvent) => { + trackUiMetric?.(METRIC_TYPE.CLICK, `${DASHBOARD_LINK_TYPE}:click`); + /** * If the link is being opened via a modified click, then we should use the default `href` navigation behaviour * by passing all the dashboard state via the URL - this will keep behaviour consistent across all browsers. @@ -132,26 +141,19 @@ export const DashboardLinkComponent = ({ if (linkOptions.openInNewTab) { window.open(href, '_blank'); } else { - locator.navigate(params); + await locator.navigate(params); } }, }; - }, [link]); + }, [link, dashboardContainer.locator, linksEmbeddable, parentDashboardId]); useEffect(() => { - if (loadingDestinationDashboard || loadingOnClickProps) { + if (loadingDestinationDashboard) { onLoading(); } else { onRender(); } - }, [ - link, - linksEmbeddable, - loadingDestinationDashboard, - loadingOnClickProps, - onLoading, - onRender, - ]); + }, [link, linksEmbeddable, loadingDestinationDashboard, onLoading, onRender]); const id = `dashboardLink--${link.id}`; @@ -178,7 +180,7 @@ export const DashboardLinkComponent = ({ }} iconType={error ? 'warning' : undefined} iconProps={{ className: 'dashboardLinkIcon' }} - isDisabled={Boolean(error) || loadingOnClickProps} + isDisabled={Boolean(error)} className={classNames('linksPanelLink', { linkCurrent: link.destination === parentDashboardId, dashboardLinkError: Boolean(error), diff --git a/src/plugins/links/public/components/external_link/external_link_component.tsx b/src/plugins/links/public/components/external_link/external_link_component.tsx index ac409cfbac4cf..4af95c83cc325 100644 --- a/src/plugins/links/public/components/external_link/external_link_component.tsx +++ b/src/plugins/links/public/components/external_link/external_link_component.tsx @@ -9,15 +9,21 @@ import React, { useMemo, useState } from 'react'; import useMount from 'react-use/lib/useMount'; +import { EuiListGroupItem } from '@elastic/eui'; +import { METRIC_TYPE } from '@kbn/analytics'; import { - UrlDrilldownOptions, DEFAULT_URL_DRILLDOWN_OPTIONS, + UrlDrilldownOptions, } from '@kbn/ui-actions-enhanced-plugin/public'; -import { EuiListGroupItem } from '@elastic/eui'; +import { + EXTERNAL_LINK_TYPE, + Link, + LinksLayoutType, + LINKS_VERTICAL_LAYOUT, +} from '../../../common/content_management'; +import { coreServices, trackUiMetric } from '../../services/kibana_services'; import { validateUrl } from './external_link_tools'; -import { coreServices } from '../../services/kibana_services'; -import { Link, LinksLayoutType, LINKS_VERTICAL_LAYOUT } from '../../../common/content_management'; export const ExternalLinkComponent = ({ link, @@ -78,6 +84,8 @@ export const ExternalLinkComponent = ({ onClick={async (event) => { if (!destination) return; + trackUiMetric?.(METRIC_TYPE.CLICK, `${EXTERNAL_LINK_TYPE}:click`); + /** Only use `navigateToUrl` if we **aren't** opening in a new window/tab; otherwise, just use default href handling */ const modifiedClick = event.ctrlKey || event.metaKey || event.shiftKey; if (!modifiedClick) { diff --git a/src/plugins/links/public/plugin.ts b/src/plugins/links/public/plugin.ts index 6569f6d767808..f72f45d4c6a22 100644 --- a/src/plugins/links/public/plugin.ts +++ b/src/plugins/links/public/plugin.ts @@ -15,6 +15,7 @@ import { DashboardStart } from '@kbn/dashboard-plugin/public'; import { DashboardContainer } from '@kbn/dashboard-plugin/public/dashboard_container'; import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; +import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; import { APP_ICON, APP_NAME, CONTENT_ID, LATEST_VERSION } from '../common'; @@ -36,6 +37,7 @@ export interface LinksStartDependencies { dashboard: DashboardStart; presentationUtil: PresentationUtilPluginStart; contentManagement: ContentManagementPublicStart; + usageCollection?: UsageCollectionStart; } export class LinksPlugin diff --git a/src/plugins/links/public/services/kibana_services.ts b/src/plugins/links/public/services/kibana_services.ts index 76acd242f7575..7536c12262792 100644 --- a/src/plugins/links/public/services/kibana_services.ts +++ b/src/plugins/links/public/services/kibana_services.ts @@ -8,12 +8,13 @@ import { BehaviorSubject } from 'rxjs'; +import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; import { CoreStart } from '@kbn/core/public'; import { DashboardStart } from '@kbn/dashboard-plugin/public'; import { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; -import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; +import { CONTENT_ID } from '../../common'; import { LinksStartDependencies } from '../plugin'; export let coreServices: CoreStart; @@ -21,6 +22,11 @@ export let dashboardServices: DashboardStart; export let embeddableService: EmbeddableStart; export let presentationUtil: PresentationUtilPluginStart; export let contentManagement: ContentManagementPublicStart; +export let trackUiMetric: ( + type: string, + eventNames: string | string[], + count?: number +) => void | undefined; const servicesReady$ = new BehaviorSubject(false); @@ -42,6 +48,8 @@ export const setKibanaServices = (kibanaCore: CoreStart, deps: LinksStartDepende embeddableService = deps.embeddable; presentationUtil = deps.presentationUtil; contentManagement = deps.contentManagement; + if (deps.usageCollection) + trackUiMetric = deps.usageCollection.reportUiCounter.bind(deps.usageCollection, CONTENT_ID); servicesReady$.next(true); }; diff --git a/src/plugins/links/tsconfig.json b/src/plugins/links/tsconfig.json index be7692c7fa431..f839243325d07 100644 --- a/src/plugins/links/tsconfig.json +++ b/src/plugins/links/tsconfig.json @@ -27,6 +27,8 @@ "@kbn/core-plugins-server", "@kbn/react-kibana-mount", "@kbn/react-kibana-context-theme", + "@kbn/analytics", + "@kbn/usage-collection-plugin", "@kbn/visualizations-plugin", "@kbn/core-mount-utils-browser" ],