From e38b4df83db4c56a747b88642c7458a88dd10822 Mon Sep 17 00:00:00 2001 From: Agustina Nahir Ruidiaz <61565784+agusruidiazgd@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:42:36 +0200 Subject: [PATCH] [SecuritySolution][Onboarding] Send Telemetry when header/footer cards are clicked (#196495) ## Summary #196145 To verify: 1. Add these lines to kibana.dev.yml ``` logging.browser.root.level: debug telemetry.optIn: true ``` \ 2. In the onboarding hub, click on header cards. It should log `onboarding_card_${cardId}` on cards clicked. Screenshot 2024-10-16 at 10 30 58 Screenshot 2024-10-15 at 16 54 32 \ 3. It should log `onboarding_footer_link_${footerLinkId}` on footer links visited. Screenshot 2024-10-16 at 10 31 26 Screenshot 2024-10-15 at 17 29 59 ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../public/onboarding/components/constants.ts | 14 ++++ .../components/onboarding_footer/constants.ts | 16 +++++ .../onboarding_footer/footer_items.ts | 9 +-- .../onboarding_footer.test.tsx | 52 ++++++++++++++ .../onboarding_footer/onboarding_footer.tsx | 72 ++++++++++++++----- .../cards/common/link_card.test.tsx | 23 ++++++ .../cards/common/link_card.tsx | 18 +++-- .../cards/demo_card/demo_card.tsx | 2 + .../cards/teammates_card/teammates_card.tsx | 2 + .../cards/video_card/video_card.tsx | 3 + 10 files changed, 186 insertions(+), 25 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/onboarding/components/constants.ts create mode 100644 x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/constants.ts create mode 100644 x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/onboarding_footer.test.tsx diff --git a/x-pack/plugins/security_solution/public/onboarding/components/constants.ts b/x-pack/plugins/security_solution/public/onboarding/components/constants.ts new file mode 100644 index 0000000000000..5862eadb32051 --- /dev/null +++ b/x-pack/plugins/security_solution/public/onboarding/components/constants.ts @@ -0,0 +1,14 @@ +/* + * 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 TELEMETRY_HEADER_CARD = `header_card`; + +export enum OnboardingHeaderCardId { + video = 'video', + teammates = 'teammates', + demo = 'demo', +} diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/constants.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/constants.ts new file mode 100644 index 0000000000000..f67b991e0ea75 --- /dev/null +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/constants.ts @@ -0,0 +1,16 @@ +/* + * 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 TELEMETRY_FOOTER_LINK = `footer_link`; + +export enum OnboardingFooterLinkItemId { + video = 'video', + documentation = 'documentation', + demo = 'demo', + forum = 'forum', + labs = 'labs', +} diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/footer_items.ts b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/footer_items.ts index e0e2b272da3aa..f064947f657a4 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/footer_items.ts +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/footer_items.ts @@ -9,11 +9,12 @@ import documentation from './images/documentation.png'; import forum from './images/forum.png'; import demo from './images/demo.png'; import labs from './images/labs.png'; +import { OnboardingFooterLinkItemId } from './constants'; export const footerItems = [ { icon: documentation, - key: 'documentation', + id: OnboardingFooterLinkItemId.documentation, title: i18n.translate('xpack.securitySolution.onboarding.footer.documentation.title', { defaultMessage: 'Browse documentation', }), @@ -32,7 +33,7 @@ export const footerItems = [ }, { icon: forum, - key: 'forum', + id: OnboardingFooterLinkItemId.forum, title: i18n.translate('xpack.securitySolution.onboarding.footer.forum.title', { defaultMessage: 'Explore forum', }), @@ -48,7 +49,7 @@ export const footerItems = [ }, { icon: demo, - key: 'demo', + id: OnboardingFooterLinkItemId.demo, title: i18n.translate('xpack.securitySolution.onboarding.footer.demo.title', { defaultMessage: 'View demo project', }), @@ -64,7 +65,7 @@ export const footerItems = [ }, { icon: labs, - key: 'labs', + id: OnboardingFooterLinkItemId.labs, title: i18n.translate('xpack.securitySolution.onboarding.footer.labs.title', { defaultMessage: 'Elastic Security Labs', }), diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/onboarding_footer.test.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/onboarding_footer.test.tsx new file mode 100644 index 0000000000000..ae80d0c9273c3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/onboarding_footer.test.tsx @@ -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 React from 'react'; +import { render } from '@testing-library/react'; +import { trackOnboardingLinkClick } from '../../common/lib/telemetry'; +import { FooterLinkItem } from './onboarding_footer'; +import { OnboardingFooterLinkItemId, TELEMETRY_FOOTER_LINK } from './constants'; + +jest.mock('../../common/lib/telemetry'); + +describe('OnboardingFooterComponent', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('FooterLinkItems should render the title and description', () => { + const { getByText } = render( + + ); + + expect(getByText('Mock Title')).toBeInTheDocument(); + expect(getByText('Mock Description')).toBeInTheDocument(); + }); + + it('FooterLinkItems should track the link click', () => { + const { getByTestId } = render( + + ); + + getByTestId('footerLinkItem').click(); + expect(trackOnboardingLinkClick).toHaveBeenCalledWith( + `${TELEMETRY_FOOTER_LINK}_${OnboardingFooterLinkItemId.documentation}` + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/onboarding_footer.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/onboarding_footer.tsx index af0f7bdd6e5ef..fdf743d339a60 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/onboarding_footer.tsx +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_footer/onboarding_footer.tsx @@ -5,33 +5,71 @@ * 2.0. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import { useFooterStyles } from './onboarding_footer.styles'; import { footerItems } from './footer_items'; +import { trackOnboardingLinkClick } from '../../common/lib/telemetry'; +import type { OnboardingFooterLinkItemId } from './constants'; +import { TELEMETRY_FOOTER_LINK } from './constants'; export const OnboardingFooter = React.memo(() => { const styles = useFooterStyles(); return ( - {footerItems.map((item) => ( - - {item.title} - - -

{item.title}

-
- - {item.description} - - - - {item.link.title} - - -
+ {footerItems.map(({ id, title, icon, description, link }) => ( + ))}
); }); OnboardingFooter.displayName = 'OnboardingFooter'; + +interface FooterLinkItemProps { + id: OnboardingFooterLinkItemId; + title: string; + icon: string; + description: string; + link: { href: string; title: string }; +} + +export const FooterLinkItem = React.memo( + ({ id, title, icon, description, link }) => { + const onClickWithReport = useCallback(() => { + trackOnboardingLinkClick(`${TELEMETRY_FOOTER_LINK}_${id}`); + }, [id]); + + return ( + + {title} + + +

{title}

+
+ + {description} + + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + {link.title} + + +
+ ); + } +); + +FooterLinkItem.displayName = 'FooterLinkItem'; diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/common/link_card.test.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/common/link_card.test.tsx index 7499e3feaf5f2..83bfa317d8fbb 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/common/link_card.test.tsx +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/common/link_card.test.tsx @@ -8,6 +8,10 @@ import React from 'react'; import { render } from '@testing-library/react'; import { LinkCard } from './link_card'; +import { OnboardingHeaderCardId, TELEMETRY_HEADER_CARD } from '../../../constants'; +import { trackOnboardingLinkClick } from '../../../../common/lib/telemetry'; + +jest.mock('../../../../common/lib/telemetry'); describe('DataIngestionHubHeaderCardComponent', () => { beforeEach(() => { @@ -17,6 +21,7 @@ describe('DataIngestionHubHeaderCardComponent', () => { it('should render the title, description, and icon', () => { const { getByTestId, getByText } = render( { expect(getByTestId('data-ingestion-header-card-icon')).toHaveAttribute('src', 'mockIcon.png'); }); + it('should track the link card click', () => { + const { getByTestId } = render( + + ); + + getByTestId('headerCardLink').click(); + expect(trackOnboardingLinkClick).toHaveBeenCalledWith( + `${TELEMETRY_HEADER_CARD}_${OnboardingHeaderCardId.demo}` + ); + }); + it('should apply dark mode styles when color mode is DARK', () => { const { container } = render( = React.memo( - ({ icon, title, description, onClick, href, target, linkText }) => { + ({ id, icon, title, description, onClick, href, target, linkText }) => { const cardStyles = useCardStyles(); const cardClassName = classNames(cardStyles, 'headerCard'); + + const onClickWithReport = useCallback(() => { + trackOnboardingLinkClick(`${TELEMETRY_HEADER_CARD}_${id}`); + onClick?.(); + }, [id, onClick]); + return ( = React.memo( {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - + {linkText} diff --git a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/demo_card/demo_card.tsx b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/demo_card/demo_card.tsx index dfb8da662060e..b86ae2dcd219d 100644 --- a/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/demo_card/demo_card.tsx +++ b/x-pack/plugins/security_solution/public/onboarding/components/onboarding_header/cards/demo_card/demo_card.tsx @@ -10,10 +10,12 @@ import { LinkCard } from '../common/link_card'; import demoImage from './images/demo_card.png'; import darkDemoImage from './images/demo_card_dark.png'; import * as i18n from './translations'; +import { OnboardingHeaderCardId } from '../../../constants'; export const DemoCard = React.memo<{ isDarkMode: boolean }>(({ isDarkMode }) => { return ( (({ isDarkMode } const usersUrl = useObservable(usersUrl$, undefined); return ( (({ isDarkMode }) => { const [isModalVisible, setIsModalVisible] = useState(false); + const closeVideoModal = useCallback(() => { setIsModalVisible(false); }, []); @@ -24,6 +26,7 @@ export const VideoCard = React.memo<{ isDarkMode: boolean }>(({ isDarkMode }) => return ( <>