[8.x] [SecuritySolution][Onboarding] Send Telemetry when integration tabs or cards clicked (#196291) (#197297)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[SecuritySolution][Onboarding] Send Telemetry when integration tabs
or cards clicked
(#196291)](https://github.com/elastic/kibana/pull/196291)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Angela
Chuang","email":"6295984+angorayc@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-10-22T15:52:43Z","message":"[SecuritySolution][Onboarding]
Send Telemetry when integration tabs or cards clicked (#196291)\n\n##
Summary\r\nhttps://github.com/elastic/kibana/issues/196145\r\n\r\nTo
verify:\r\n\r\n1. Add these lines to
`kibana.dev.yml`\r\n```\r\nlogging.browser.root.level:
debug\r\ntelemetry.optIn: true\r\n```\r\n2. In the onboarding hub,
expand the integration card.\r\nIt should log `onboarding_tab_${tabId}`
on tabs
clicked.\r\n\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/bd30c9ed-7c99-4ca0-93e7-6d9bf0146e62\r\n\r\n\r\nIt
should log `onboarding_card_${integrationId}` on integration
cards\r\nclicked.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/58750d88-7bbf-4b27-8e54-587f3f6f32c2\r\n\r\n\r\n3.
Manage integrations callout link
clicked::\r\n`onboarding_manage_integrations`;\r\n4. Endpoint callout
link clicked: `onboarding_endpoint_learn_more`;\r\n5. Agentless callout
link clicked: `onboarding_agentless_learn_more`;\r\n6. Agent still
required callout link
clicked:\r\n`onboarding_agent_required`;\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"2b270897a3f22ed5ca04ef173895cfa8660f9ea2","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["backport","release_note:skip","v9.0.0","v6.8.16","Team:Threat
Hunting:Explore","ci:cloud-deploy"],"title":"[SecuritySolution][Onboarding]
Send Telemetry when integration tabs or cards
clicked","number":196291,"url":"https://github.com/elastic/kibana/pull/196291","mergeCommit":{"message":"[SecuritySolution][Onboarding]
Send Telemetry when integration tabs or cards clicked (#196291)\n\n##
Summary\r\nhttps://github.com/elastic/kibana/issues/196145\r\n\r\nTo
verify:\r\n\r\n1. Add these lines to
`kibana.dev.yml`\r\n```\r\nlogging.browser.root.level:
debug\r\ntelemetry.optIn: true\r\n```\r\n2. In the onboarding hub,
expand the integration card.\r\nIt should log `onboarding_tab_${tabId}`
on tabs
clicked.\r\n\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/bd30c9ed-7c99-4ca0-93e7-6d9bf0146e62\r\n\r\n\r\nIt
should log `onboarding_card_${integrationId}` on integration
cards\r\nclicked.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/58750d88-7bbf-4b27-8e54-587f3f6f32c2\r\n\r\n\r\n3.
Manage integrations callout link
clicked::\r\n`onboarding_manage_integrations`;\r\n4. Endpoint callout
link clicked: `onboarding_endpoint_learn_more`;\r\n5. Agentless callout
link clicked: `onboarding_agentless_learn_more`;\r\n6. Agent still
required callout link
clicked:\r\n`onboarding_agent_required`;\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"2b270897a3f22ed5ca04ef173895cfa8660f9ea2"}},"sourceBranch":"main","suggestedTargetBranches":["6.8"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196291","number":196291,"mergeCommit":{"message":"[SecuritySolution][Onboarding]
Send Telemetry when integration tabs or cards clicked (#196291)\n\n##
Summary\r\nhttps://github.com/elastic/kibana/issues/196145\r\n\r\nTo
verify:\r\n\r\n1. Add these lines to
`kibana.dev.yml`\r\n```\r\nlogging.browser.root.level:
debug\r\ntelemetry.optIn: true\r\n```\r\n2. In the onboarding hub,
expand the integration card.\r\nIt should log `onboarding_tab_${tabId}`
on tabs
clicked.\r\n\r\n\r\n\r\n\r\nhttps://github.com/user-attachments/assets/bd30c9ed-7c99-4ca0-93e7-6d9bf0146e62\r\n\r\n\r\nIt
should log `onboarding_card_${integrationId}` on integration
cards\r\nclicked.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/58750d88-7bbf-4b27-8e54-587f3f6f32c2\r\n\r\n\r\n3.
Manage integrations callout link
clicked::\r\n`onboarding_manage_integrations`;\r\n4. Endpoint callout
link clicked: `onboarding_endpoint_learn_more`;\r\n5. Agentless callout
link clicked: `onboarding_agentless_learn_more`;\r\n6. Agent still
required callout link
clicked:\r\n`onboarding_agent_required`;\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"2b270897a3f22ed5ca04ef173895cfa8660f9ea2"}},{"branch":"6.8","label":"v6.8.16","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Angela Chuang <6295984+angorayc@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2024-10-23 21:06:07 +11:00 committed by GitHub
parent 3defff68eb
commit e42a5d3db6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 185 additions and 12 deletions

View file

@ -35,6 +35,8 @@ export enum TELEMETRY_EVENT {
DASHBOARD = 'navigate_to_dashboard', DASHBOARD = 'navigate_to_dashboard',
CREATE_DASHBOARD = 'create_dashboard', CREATE_DASHBOARD = 'create_dashboard',
ONBOARDING = 'onboarding',
// value list // value list
OPEN_VALUE_LIST_MODAL = 'open_value_list_modal', OPEN_VALUE_LIST_MODAL = 'open_value_list_modal',
CREATE_VALUE_LIST_ITEM = 'create_value_list_item', CREATE_VALUE_LIST_ITEM = 'create_value_list_item',

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export const trackOnboardingLinkClick = jest.fn();

View file

@ -0,0 +1,12 @@
/*
* 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 { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../common/lib/telemetry';
export const trackOnboardingLinkClick = (linkId: string) => {
track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.ONBOARDING}_${linkId}`);
};

View file

@ -14,8 +14,10 @@ import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { AgentRequiredCallout } from './agent_required_callout'; import { AgentRequiredCallout } from './agent_required_callout';
import { TestProviders } from '../../../../../../common/mock/test_providers'; import { TestProviders } from '../../../../../../common/mock/test_providers';
import { trackOnboardingLinkClick } from '../../../../../common/lib/telemetry';
jest.mock('../../../../../../common/lib/kibana'); jest.mock('../../../../../../common/lib/kibana');
jest.mock('../../../../../common/lib/telemetry');
describe('AgentRequiredCallout', () => { describe('AgentRequiredCallout', () => {
beforeEach(() => { beforeEach(() => {
@ -30,4 +32,12 @@ describe('AgentRequiredCallout', () => {
).toBeInTheDocument(); ).toBeInTheDocument();
expect(getByTestId('agentLink')).toBeInTheDocument(); expect(getByTestId('agentLink')).toBeInTheDocument();
}); });
it('should track the agent link click', () => {
const { getByTestId } = render(<AgentRequiredCallout />, { wrapper: TestProviders });
getByTestId('agentLink').click();
expect(trackOnboardingLinkClick).toHaveBeenCalledWith('agent_required');
});
}); });

View file

@ -11,16 +11,22 @@ import { EuiIcon } from '@elastic/eui';
import { LinkAnchor } from '../../../../../../common/components/links'; import { LinkAnchor } from '../../../../../../common/components/links';
import { CardCallOut } from '../../common/card_callout'; import { CardCallOut } from '../../common/card_callout';
import { useNavigation } from '../../../../../../common/lib/kibana'; import { useNavigation } from '../../../../../../common/lib/kibana';
import { FLEET_APP_ID, ADD_AGENT_PATH } from '../constants'; import { FLEET_APP_ID, ADD_AGENT_PATH, TELEMETRY_AGENT_REQUIRED } from '../constants';
import { trackOnboardingLinkClick } from '../../../../../common/lib/telemetry';
const fleetAgentLinkProps = { appId: FLEET_APP_ID, path: ADD_AGENT_PATH }; const fleetAgentLinkProps = { appId: FLEET_APP_ID, path: ADD_AGENT_PATH };
export const AgentRequiredCallout = React.memo(() => { export const AgentRequiredCallout = React.memo(() => {
const { getAppUrl, navigateTo } = useNavigation(); const { getAppUrl, navigateTo } = useNavigation();
const addAgentLink = getAppUrl(fleetAgentLinkProps); const addAgentLink = getAppUrl(fleetAgentLinkProps);
const onAddAgentClick = useCallback(() => { const onAddAgentClick = useCallback(
navigateTo(fleetAgentLinkProps); (e: React.MouseEvent<HTMLAnchorElement>) => {
}, [navigateTo]); e.preventDefault();
trackOnboardingLinkClick(TELEMETRY_AGENT_REQUIRED);
navigateTo(fleetAgentLinkProps);
},
[navigateTo]
);
return ( return (
<CardCallOut <CardCallOut

View file

@ -10,10 +10,10 @@ import React from 'react';
import { TestProviders } from '../../../../../../common/mock/test_providers'; import { TestProviders } from '../../../../../../common/mock/test_providers';
import { AgentlessAvailableCallout } from './agentless_available_callout'; import { AgentlessAvailableCallout } from './agentless_available_callout';
import { useKibana } from '../../../../../../common/lib/kibana'; import { useKibana } from '../../../../../../common/lib/kibana';
import { trackOnboardingLinkClick } from '../../../../../common/lib/telemetry';
jest.mock('../../../../../../common/lib/kibana', () => ({ jest.mock('../../../../../../common/lib/kibana');
useKibana: jest.fn(), jest.mock('../../../../../common/lib/telemetry');
}));
describe('AgentlessAvailableCallout', () => { describe('AgentlessAvailableCallout', () => {
const mockUseKibana = useKibana as jest.Mock; const mockUseKibana = useKibana as jest.Mock;
@ -62,4 +62,14 @@ describe('AgentlessAvailableCallout', () => {
).toBeInTheDocument(); ).toBeInTheDocument();
expect(getByTestId('agentlessLearnMoreLink')).toBeInTheDocument(); expect(getByTestId('agentlessLearnMoreLink')).toBeInTheDocument();
}); });
it('should track the agentless learn more link click', () => {
const { getByTestId } = render(<AgentlessAvailableCallout />, {
wrapper: TestProviders,
});
getByTestId('agentlessLearnMoreLink').click();
expect(trackOnboardingLinkClick).toHaveBeenCalledWith('agentless_learn_more');
});
}); });

View file

@ -5,19 +5,25 @@
* 2.0. * 2.0.
*/ */
import React from 'react'; import React, { useCallback } from 'react';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import { EuiIcon, useEuiTheme } from '@elastic/eui'; import { EuiIcon, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
import { useKibana } from '../../../../../../common/lib/kibana'; import { useKibana } from '../../../../../../common/lib/kibana';
import { LinkAnchor } from '../../../../../../common/components/links'; import { LinkAnchor } from '../../../../../../common/components/links';
import { trackOnboardingLinkClick } from '../../../../../common/lib/telemetry';
import { CardCallOut } from '../../common/card_callout'; import { CardCallOut } from '../../common/card_callout';
import { TELEMETRY_AGENTLESS_LEARN_MORE } from '../constants';
export const AgentlessAvailableCallout = React.memo(() => { export const AgentlessAvailableCallout = React.memo(() => {
const { euiTheme } = useEuiTheme(); const { euiTheme } = useEuiTheme();
const { docLinks } = useKibana().services; const { docLinks } = useKibana().services;
const onClick = useCallback(() => {
trackOnboardingLinkClick(TELEMETRY_AGENTLESS_LEARN_MORE);
}, []);
/* @ts-expect-error: add the blog link to `packages/kbn-doc-links/src/get_doc_links.ts` when it is ready and remove this exit condition*/ /* @ts-expect-error: add the blog link to `packages/kbn-doc-links/src/get_doc_links.ts` when it is ready and remove this exit condition*/
if (!docLinks.links.fleet.agentlessBlog) { if (!docLinks.links.fleet.agentlessBlog) {
return null; return null;
@ -54,6 +60,7 @@ export const AgentlessAvailableCallout = React.memo(() => {
<LinkAnchor <LinkAnchor
/* @ts-expect-error-next-line */ /* @ts-expect-error-next-line */
href={docLinks.links.fleet.agentlessBlog} href={docLinks.links.fleet.agentlessBlog}
onClick={onClick}
data-test-subj="agentlessLearnMoreLink" data-test-subj="agentlessLearnMoreLink"
external={true} external={true}
target="_blank" target="_blank"

View file

@ -0,0 +1,43 @@
/*
* 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.
*/
/*
* 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 React from 'react';
import { render } from '@testing-library/react';
import { EndpointCallout } from './endpoint_callout';
import { TestProviders } from '../../../../../../common/mock/test_providers';
import { trackOnboardingLinkClick } from '../../../../../common/lib/telemetry';
jest.mock('../../../../../../common/lib/kibana');
jest.mock('../../../../../common/lib/telemetry');
describe('EndpointCallout', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders the callout', () => {
const { getByTestId, getByText } = render(<EndpointCallout />, { wrapper: TestProviders });
expect(
getByText('Orchestrate response across endpoint vendors with bidirectional integrations')
).toBeInTheDocument();
expect(getByTestId('endpointLearnMoreLink')).toBeInTheDocument();
});
it('should track the agent link click', () => {
const { getByTestId } = render(<EndpointCallout />, { wrapper: TestProviders });
getByTestId('endpointLearnMoreLink').click();
expect(trackOnboardingLinkClick).toHaveBeenCalledWith('endpoint_learn_more');
});
});

View file

@ -5,7 +5,7 @@
* 2.0. * 2.0.
*/ */
import React from 'react'; import React, { useCallback } from 'react';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import { EuiIcon, useEuiTheme } from '@elastic/eui'; import { EuiIcon, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react'; import { css } from '@emotion/react';
@ -13,10 +13,15 @@ import { css } from '@emotion/react';
import { useKibana } from '../../../../../../common/lib/kibana/kibana_react'; import { useKibana } from '../../../../../../common/lib/kibana/kibana_react';
import { LinkAnchor } from '../../../../../../common/components/links'; import { LinkAnchor } from '../../../../../../common/components/links';
import { CardCallOut } from '../../common/card_callout'; import { CardCallOut } from '../../common/card_callout';
import { trackOnboardingLinkClick } from '../../../../../common/lib/telemetry';
import { TELEMETRY_ENDPOINT_LEARN_MORE } from '../constants';
export const EndpointCallout = React.memo(() => { export const EndpointCallout = React.memo(() => {
const { euiTheme } = useEuiTheme(); const { euiTheme } = useEuiTheme();
const { docLinks } = useKibana().services; const { docLinks } = useKibana().services;
const onClick = useCallback(() => {
trackOnboardingLinkClick(TELEMETRY_ENDPOINT_LEARN_MORE);
}, []);
return ( return (
<CardCallOut <CardCallOut
@ -51,6 +56,7 @@ export const EndpointCallout = React.memo(() => {
data-test-subj="endpointLearnMoreLink" data-test-subj="endpointLearnMoreLink"
external={true} external={true}
target="_blank" target="_blank"
onClick={onClick}
> >
<FormattedMessage <FormattedMessage
id="xpack.securitySolution.onboarding.integrationsCard.callout.button.endpointLearnMoreLink" id="xpack.securitySolution.onboarding.integrationsCard.callout.button.endpointLearnMoreLink"

View file

@ -4,18 +4,29 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import React from 'react'; import React, { useCallback } from 'react';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import { EuiIcon } from '@elastic/eui'; import { EuiIcon } from '@elastic/eui';
import { LinkAnchor } from '../../../../../../common/components/links'; import { LinkAnchor } from '../../../../../../common/components/links';
import { CardCallOut } from '../../common/card_callout'; import { CardCallOut } from '../../common/card_callout';
import { useAddIntegrationsUrl } from '../../../../../../common/hooks/use_add_integrations_url'; import { useAddIntegrationsUrl } from '../../../../../../common/hooks/use_add_integrations_url';
import { trackOnboardingLinkClick } from '../../../../../common/lib/telemetry';
import { TELEMETRY_MANAGE_INTEGRATIONS } from '../constants';
export const ManageIntegrationsCallout = React.memo( export const ManageIntegrationsCallout = React.memo(
({ installedIntegrationsCount }: { installedIntegrationsCount: number }) => { ({ installedIntegrationsCount }: { installedIntegrationsCount: number }) => {
const { href: integrationUrl, onClick: onAddIntegrationClicked } = useAddIntegrationsUrl(); const { href: integrationUrl, onClick: onAddIntegrationClicked } = useAddIntegrationsUrl();
const onClick = useCallback(
(e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
trackOnboardingLinkClick(TELEMETRY_MANAGE_INTEGRATIONS);
onAddIntegrationClicked(e);
},
[onAddIntegrationClicked]
);
if (!installedIntegrationsCount) { if (!installedIntegrationsCount) {
return null; return null;
} }
@ -41,7 +52,7 @@ export const ManageIntegrationsCallout = React.memo(
), ),
link: ( link: (
<LinkAnchor <LinkAnchor
onClick={onAddIntegrationClicked} onClick={onClick}
href={integrationUrl} href={integrationUrl}
data-test-subj="manageIntegrationsLink" data-test-subj="manageIntegrationsLink"
> >

View file

@ -24,3 +24,9 @@ export const SCROLL_ELEMENT_ID = 'integrations-scroll-container';
export const SEARCH_FILTER_CATEGORIES: CategoryFacet[] = []; export const SEARCH_FILTER_CATEGORIES: CategoryFacet[] = [];
export const WITH_SEARCH_BOX_HEIGHT = '568px'; export const WITH_SEARCH_BOX_HEIGHT = '568px';
export const WITHOUT_SEARCH_BOX_HEIGHT = '513px'; export const WITHOUT_SEARCH_BOX_HEIGHT = '513px';
export const TELEMETRY_MANAGE_INTEGRATIONS = `manage_integrations`;
export const TELEMETRY_ENDPOINT_LEARN_MORE = `endpoint_learn_more`;
export const TELEMETRY_AGENTLESS_LEARN_MORE = `agentless_learn_more`;
export const TELEMETRY_AGENT_REQUIRED = `agent_required`;
export const TELEMETRY_INTEGRATION_CARD = `card`;
export const TELEMETRY_INTEGRATION_TAB = `tab`;

View file

@ -15,9 +15,11 @@ import {
useStoredIntegrationTabId, useStoredIntegrationTabId,
} from '../../../../hooks/use_stored_state'; } from '../../../../hooks/use_stored_state';
import { DEFAULT_TAB } from './constants'; import { DEFAULT_TAB } from './constants';
import { trackOnboardingLinkClick } from '../../../../common/lib/telemetry';
jest.mock('../../../onboarding_context'); jest.mock('../../../onboarding_context');
jest.mock('../../../../hooks/use_stored_state'); jest.mock('../../../../hooks/use_stored_state');
jest.mock('../../../../common/lib/telemetry');
jest.mock('../../../../../common/lib/kibana', () => ({ jest.mock('../../../../../common/lib/kibana', () => ({
...jest.requireActual('../../../../../common/lib/kibana'), ...jest.requireActual('../../../../../common/lib/kibana'),
@ -118,6 +120,33 @@ describe('IntegrationsCardGridTabsComponent', () => {
expect(mockSetTabId).toHaveBeenCalledWith('user'); expect(mockSetTabId).toHaveBeenCalledWith('user');
}); });
it('tracks the tab clicks', () => {
(useStoredIntegrationTabId as jest.Mock).mockReturnValue(['recommended', mockSetTabId]);
mockUseAvailablePackages.mockReturnValue({
isLoading: false,
filteredCards: [],
setCategory: mockSetCategory,
setSelectedSubCategory: mockSetSelectedSubCategory,
setSearchTerm: mockSetSearchTerm,
});
const { getByTestId } = render(
<IntegrationsCardGridTabsComponent
{...props}
useAvailablePackages={mockUseAvailablePackages}
/>
);
const tabButton = getByTestId('user');
act(() => {
fireEvent.click(tabButton);
});
expect(trackOnboardingLinkClick).toHaveBeenCalledWith('tab_user');
});
it('renders no search tools when showSearchTools is false', async () => { it('renders no search tools when showSearchTools is false', async () => {
mockUseAvailablePackages.mockReturnValue({ mockUseAvailablePackages.mockReturnValue({
isLoading: false, isLoading: false,

View file

@ -21,6 +21,7 @@ import {
LOADING_SKELETON_TEXT_LINES, LOADING_SKELETON_TEXT_LINES,
SCROLL_ELEMENT_ID, SCROLL_ELEMENT_ID,
SEARCH_FILTER_CATEGORIES, SEARCH_FILTER_CATEGORIES,
TELEMETRY_INTEGRATION_TAB,
WITHOUT_SEARCH_BOX_HEIGHT, WITHOUT_SEARCH_BOX_HEIGHT,
WITH_SEARCH_BOX_HEIGHT, WITH_SEARCH_BOX_HEIGHT,
} from './constants'; } from './constants';
@ -28,6 +29,7 @@ import { INTEGRATION_TABS, INTEGRATION_TABS_BY_ID } from './integration_tabs_con
import { useIntegrationCardList } from './use_integration_card_list'; import { useIntegrationCardList } from './use_integration_card_list';
import { IntegrationTabId } from './types'; import { IntegrationTabId } from './types';
import { IntegrationCardTopCallout } from './callouts/integration_card_top_callout'; import { IntegrationCardTopCallout } from './callouts/integration_card_top_callout';
import { trackOnboardingLinkClick } from '../../../../common/lib/telemetry';
export interface IntegrationsCardGridTabsProps { export interface IntegrationsCardGridTabsProps {
installedIntegrationsCount: number; installedIntegrationsCount: number;
@ -55,8 +57,10 @@ export const IntegrationsCardGridTabsComponent = React.memo<IntegrationsCardGrid
const onTabChange = useCallback( const onTabChange = useCallback(
(stringId: string) => { (stringId: string) => {
const id = stringId as IntegrationTabId; const id = stringId as IntegrationTabId;
const trackId = `${TELEMETRY_INTEGRATION_TAB}_${id}`;
scrollElement.current?.scrollTo?.(0, 0); scrollElement.current?.scrollTo?.(0, 0);
setSelectedTabIdToStorage(id); setSelectedTabIdToStorage(id);
trackOnboardingLinkClick(trackId);
}, },
[setSelectedTabIdToStorage] [setSelectedTabIdToStorage]
); );

View file

@ -6,12 +6,14 @@
*/ */
import { renderHook } from '@testing-library/react-hooks'; import { renderHook } from '@testing-library/react-hooks';
import { useIntegrationCardList } from './use_integration_card_list'; import { useIntegrationCardList } from './use_integration_card_list';
import { trackOnboardingLinkClick } from '../../../../common/lib/telemetry';
jest.mock('../../../../common/lib/telemetry');
jest.mock('../../../../../common/lib/kibana', () => ({ jest.mock('../../../../../common/lib/kibana', () => ({
...jest.requireActual('../../../../../common/lib/kibana'), ...jest.requireActual('../../../../../common/lib/kibana'),
useNavigation: jest.fn().mockReturnValue({ useNavigation: jest.fn().mockReturnValue({
navigateTo: jest.fn(), navigateTo: jest.fn(),
getAppUrl: jest.fn(), getAppUrl: jest.fn().mockReturnValue(''),
}), }),
})); }));
@ -73,4 +75,17 @@ describe('useIntegrationCardList', () => {
expect(result.current).toEqual([mockFilteredCards.featuredCards['epr:endpoint']]); expect(result.current).toEqual([mockFilteredCards.featuredCards['epr:endpoint']]);
}); });
it('tracks integration card click', () => {
const { result } = renderHook(() =>
useIntegrationCardList({
integrationsList: mockIntegrationsList,
})
);
const card = result.current[0];
card.onCardClick?.();
expect(trackOnboardingLinkClick).toHaveBeenCalledWith('card_epr:endpoint');
});
}); });

View file

@ -20,8 +20,10 @@ import {
MAX_CARD_HEIGHT_IN_PX, MAX_CARD_HEIGHT_IN_PX,
ONBOARDING_APP_ID, ONBOARDING_APP_ID,
ONBOARDING_LINK, ONBOARDING_LINK,
TELEMETRY_INTEGRATION_CARD,
} from './constants'; } from './constants';
import type { GetAppUrl, NavigateTo } from '../../../../../common/lib/kibana'; import type { GetAppUrl, NavigateTo } from '../../../../../common/lib/kibana';
import { trackOnboardingLinkClick } from '../../../../common/lib/telemetry';
const addPathParamToUrl = (url: string, onboardingLink: string) => { const addPathParamToUrl = (url: string, onboardingLink: string) => {
const encoded = encodeURIComponent(onboardingLink); const encoded = encodeURIComponent(onboardingLink);
@ -97,6 +99,8 @@ const addSecuritySpecificProps = ({
showInstallationStatus: true, showInstallationStatus: true,
url, url,
onCardClick: () => { onCardClick: () => {
const trackId = `${TELEMETRY_INTEGRATION_CARD}_${card.id}`;
trackOnboardingLinkClick(trackId);
if (url.startsWith(APP_INTEGRATIONS_PATH)) { if (url.startsWith(APP_INTEGRATIONS_PATH)) {
navigateTo({ navigateTo({
appId: INTEGRATION_APP_ID, appId: INTEGRATION_APP_ID,