[Dashboard Navigation] Add Links telemetry (#171877)

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](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>
This commit is contained in:
Hannah Mudge 2023-11-27 07:44:14 -07:00 committed by GitHub
parent 618cc48bc5
commit 31a8b7bca6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 45 additions and 23 deletions

View file

@ -17,7 +17,7 @@
"uiActionsEnhanced",
"visualizations"
],
"optionalPlugins": ["triggersActionsUi"],
"optionalPlugins": ["triggersActionsUi", "usageCollection"],
"requiredBundles": ["savedObjects"]
}
}

View file

@ -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),

View file

@ -9,15 +9,21 @@
import React, { useMemo, useState } from 'react';
import useMount from 'react-use/lib/useMount';
import {
UrlDrilldownOptions,
DEFAULT_URL_DRILLDOWN_OPTIONS,
} from '@kbn/ui-actions-enhanced-plugin/public';
import { EuiListGroupItem } from '@elastic/eui';
import { METRIC_TYPE } from '@kbn/analytics';
import {
DEFAULT_URL_DRILLDOWN_OPTIONS,
UrlDrilldownOptions,
} from '@kbn/ui-actions-enhanced-plugin/public';
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) {

View file

@ -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

View file

@ -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);
};

View file

@ -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"
],