Skip to content

Commit f00f837

Browse files
agusruidiazgdelasticmachinekibanamachine
authored
[Security Solution][Onboarding] Adding telemetry to video selectors (#217280)
## Summary New event created for the video selectors inside rules, dashboards and alerts cards. ``` export interface OnboardingHubSelectorCardClickedParams { originStepId: string; selectorId: string; } ``` To verify: Add these lines to kibana.dev.yml ``` logging.browser.root.level: debug telemetry.optIn: true ``` 1. In the onboarding hub, expand the rules card 2. It should log `Report event "Onboarding Hub Step Selector Clicked"`. https://github.com/user-attachments/assets/c1b1084e-4917-4412-93ed-984a74b6b6b4 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] [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 --------- Co-authored-by: Elastic Machine <[email protected]> Co-authored-by: kibanamachine <[email protected]>
1 parent bf7389f commit f00f837

File tree

11 files changed

+139
-20
lines changed

11 files changed

+139
-20
lines changed

x-pack/solutions/security/plugins/security_solution/public/common/lib/telemetry/events/onboarding/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,29 @@ export const onboardingHubStepFinishedEvent: OnboardingHubTelemetryEvent = {
7575
},
7676
};
7777

78+
export const onboardingHubStepSelectorClickedEvent: OnboardingHubTelemetryEvent = {
79+
eventType: OnboardingHubEventTypes.OnboardingHubStepSelectorClicked,
80+
schema: {
81+
originStepId: {
82+
type: 'keyword',
83+
_meta: {
84+
description: 'Active step ID',
85+
optional: false,
86+
},
87+
},
88+
selectorId: {
89+
type: 'keyword',
90+
_meta: {
91+
description: 'Clicked Selector ID',
92+
optional: false,
93+
},
94+
},
95+
},
96+
};
97+
7898
export const onboardingHubTelemetryEvents = [
7999
onboardingHubStepOpenEvent,
80100
onboardingHubStepLinkClickedEvent,
81101
onboardingHubStepFinishedEvent,
102+
onboardingHubStepSelectorClickedEvent,
82103
];

x-pack/solutions/security/plugins/security_solution/public/common/lib/telemetry/events/onboarding/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export enum OnboardingHubEventTypes {
1010
OnboardingHubStepOpen = 'Onboarding Hub Step Open',
1111
OnboardingHubStepFinished = 'Onboarding Hub Step Finished',
1212
OnboardingHubStepLinkClicked = 'Onboarding Hub Step Link Clicked',
13+
OnboardingHubStepSelectorClicked = 'Onboarding Hub Step Selector Clicked',
1314
}
1415

1516
type OnboardingHubStepOpenTrigger = 'navigation' | 'click';
@@ -24,6 +25,11 @@ export interface OnboardingHubStepLinkClickedParams {
2425
stepLinkId: string;
2526
}
2627

28+
export interface OnboardingHubSelectorCardClickedParams {
29+
originStepId: string;
30+
selectorId: string;
31+
}
32+
2733
export type OnboardingHubStepFinishedTrigger = 'auto_check' | 'click';
2834

2935
export interface OnboardingHubStepFinishedParams {
@@ -36,6 +42,7 @@ export interface OnboardingHubTelemetryEventsMap {
3642
[OnboardingHubEventTypes.OnboardingHubStepOpen]: OnboardingHubStepOpenParams;
3743
[OnboardingHubEventTypes.OnboardingHubStepFinished]: OnboardingHubStepFinishedParams;
3844
[OnboardingHubEventTypes.OnboardingHubStepLinkClicked]: OnboardingHubStepLinkClickedParams;
45+
[OnboardingHubEventTypes.OnboardingHubStepSelectorClicked]: OnboardingHubSelectorCardClickedParams;
3946
}
4047

4148
export interface OnboardingHubTelemetryEvent {

x-pack/solutions/security/plugins/security_solution/public/onboarding/components/__mocks__/mocks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
export const mockReportCardOpen = jest.fn();
1515
export const mockReportCardComplete = jest.fn();
1616
export const mockReportCardLinkClicked = jest.fn();
17+
export const mockReportCardSelectorClicked = jest.fn();
1718

1819
export const telemetry = {
1920
reportCardOpen: mockReportCardOpen,
2021
reportCardComplete: mockReportCardComplete,
2122
reportCardLinkClicked: mockReportCardLinkClicked,
23+
reportCardSelectorClicked: mockReportCardSelectorClicked,
2224
};
2325
export const mockTelemetry = jest.fn(() => telemetry);
2426

x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/alerts/alerts_card.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const AlertsCard: OnboardingCardComponent = ({
8484
items={ALERTS_CARD_ITEMS}
8585
onSelect={onSelectCard}
8686
selectedItem={selectedCardItem}
87+
cardId={OnboardingCardId.alerts}
8788
/>
8889
{isIntegrationsCardAvailable && !isIntegrationsCardComplete && (
8990
<>

x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_selector_list.test.tsx

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,23 @@
66
*/
77

88
import React from 'react';
9+
910
import { render, fireEvent } from '@testing-library/react';
1011
import { CardSelectorList } from './card_selector_list';
1112
import type { CardSelectorListItem } from './card_selector_list';
1213
import { RulesCardItemId } from '../rules/types';
14+
import { OnboardingCardId } from '../../../../constants';
15+
import { OnboardingContextProvider } from '../../../onboarding_context';
16+
import { ExperimentalFeaturesService } from '../../../../../common/experimental_features_service';
17+
import { TestProviders } from '../../../../../common/mock';
1318

1419
const mockOnSelect = jest.fn();
1520

21+
jest.mock('../../../../../common/experimental_features_service', () => ({
22+
ExperimentalFeaturesService: { get: jest.fn() },
23+
}));
24+
const mockExperimentalFeatures = ExperimentalFeaturesService.get as jest.Mock;
25+
1626
const items: CardSelectorListItem[] = [
1727
{
1828
id: RulesCardItemId.install,
@@ -31,6 +41,7 @@ const defaultProps = {
3141
onSelect: mockOnSelect,
3242
selectedItem: items[0],
3343
title: 'Select a Rule',
44+
cardId: OnboardingCardId.rules,
3445
};
3546

3647
describe('CardSelectorList', () => {
@@ -40,53 +51,89 @@ describe('CardSelectorList', () => {
4051
Element.prototype.scrollIntoView = scrollIntoViewMock;
4152
});
4253
beforeEach(() => {
54+
mockExperimentalFeatures.mockReturnValue({});
4355
jest.clearAllMocks();
4456
});
4557
afterAll(() => {
4658
jest.useRealTimers();
4759
});
4860

4961
it('renders the component with the correct title', () => {
50-
const { getByText } = render(<CardSelectorList {...defaultProps} />);
62+
const { getByText } = render(
63+
<TestProviders>
64+
<OnboardingContextProvider spaceId="default">
65+
<CardSelectorList {...defaultProps} />
66+
</OnboardingContextProvider>
67+
</TestProviders>
68+
);
5169
expect(getByText('Select a Rule')).toBeInTheDocument();
5270
});
5371

5472
it('renders all card items', () => {
55-
const { getByTestId } = render(<CardSelectorList {...defaultProps} />);
73+
const { getByTestId } = render(
74+
<TestProviders>
75+
<OnboardingContextProvider spaceId="default">
76+
<CardSelectorList {...defaultProps} />
77+
</OnboardingContextProvider>
78+
</TestProviders>
79+
);
5680
items.forEach((item) => {
5781
expect(getByTestId(`cardSelectorItem-${item.id}`)).toBeInTheDocument();
5882
});
5983
});
6084

6185
it('applies the "selected" class to the selected item', () => {
62-
const { getByTestId } = render(<CardSelectorList {...defaultProps} />);
86+
const { getByTestId } = render(
87+
<TestProviders>
88+
<OnboardingContextProvider spaceId="default">
89+
<CardSelectorList {...defaultProps} />
90+
</OnboardingContextProvider>
91+
</TestProviders>
92+
);
6393
const selectedItem = getByTestId(`cardSelectorItem-${RulesCardItemId.install}`);
6494
expect(selectedItem).toHaveClass('selectedCardPanelItem');
6595
});
6696

6797
it('does not apply the "selected" class to unselected items', () => {
68-
const { getByTestId } = render(<CardSelectorList {...defaultProps} />);
98+
const { getByTestId } = render(
99+
<TestProviders>
100+
<OnboardingContextProvider spaceId="default">
101+
<CardSelectorList {...defaultProps} />
102+
</OnboardingContextProvider>
103+
</TestProviders>
104+
);
69105
const unselectedItem = getByTestId(`cardSelectorItem-${RulesCardItemId.create}`);
70106
expect(unselectedItem).not.toHaveClass('selectedCardPanelItem');
71107
});
72108

73109
it('calls onSelect with the correct item when an item is clicked', () => {
74-
const { getByTestId } = render(<CardSelectorList {...defaultProps} />);
110+
const { getByTestId } = render(
111+
<TestProviders>
112+
<OnboardingContextProvider spaceId="default">
113+
<CardSelectorList {...defaultProps} />
114+
</OnboardingContextProvider>
115+
</TestProviders>
116+
);
75117
const unselectedItem = getByTestId(`cardSelectorItem-${RulesCardItemId.create}`);
76118
fireEvent.click(unselectedItem);
77119
expect(mockOnSelect).toHaveBeenCalledWith(items[1]);
78120
});
79121

80-
it('scrolls to the selected item on initial render', () => {
81-
jest.useFakeTimers();
82-
render(<CardSelectorList {...defaultProps} />);
83-
jest.runAllTimers();
84-
expect(scrollIntoViewMock).toHaveBeenCalled();
85-
});
86-
87122
it('updates the selected item visually when onSelect is called', () => {
88-
const { rerender, getByTestId } = render(<CardSelectorList {...defaultProps} />);
89-
rerender(<CardSelectorList {...defaultProps} selectedItem={items[1]} />);
123+
const { rerender, getByTestId } = render(
124+
<TestProviders>
125+
<OnboardingContextProvider spaceId="default">
126+
<CardSelectorList {...defaultProps} />
127+
</OnboardingContextProvider>
128+
</TestProviders>
129+
);
130+
rerender(
131+
<TestProviders>
132+
<OnboardingContextProvider spaceId="default">
133+
<CardSelectorList {...defaultProps} selectedItem={items[1]} />
134+
</OnboardingContextProvider>
135+
</TestProviders>
136+
);
90137

91138
const newlySelectedItem = getByTestId(`cardSelectorItem-${RulesCardItemId.create}`);
92139
const previouslySelectedItem = getByTestId(`cardSelectorItem-${RulesCardItemId.install}`);

x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/common/card_selector_list.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
* 2.0; you may not use this file except in compliance with the Elastic License
55
* 2.0.
66
*/
7-
import React, { useEffect } from 'react';
7+
import React, { useCallback, useEffect } from 'react';
88
import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiText, EuiSpacer } from '@elastic/eui';
9+
import type { OnboardingCardId } from '../../../../constants';
910
import type { RulesCardItemId } from '../rules/types';
1011
import type { AlertsCardItemId } from '../alerts/types';
1112
import type { DashboardsCardItemId } from '../dashboards/types';
1213
import { useCardSelectorListStyles } from './card_selector_list.styles';
1314
import { HEIGHT_ANIMATION_DURATION } from '../../onboarding_card_panel.styles';
15+
import { useOnboardingContext } from '../../../onboarding_context';
1416

1517
export interface CardSelectorListItem {
1618
id: RulesCardItemId | AlertsCardItemId | DashboardsCardItemId;
@@ -23,6 +25,7 @@ export interface CardSelectorListProps {
2325
onSelect: (item: CardSelectorListItem) => void;
2426
selectedItem: CardSelectorListItem;
2527
title?: string;
28+
cardId: OnboardingCardId;
2629
}
2730

2831
const scrollToSelectedItem = (cardId: string) => {
@@ -35,14 +38,23 @@ const scrollToSelectedItem = (cardId: string) => {
3538
};
3639

3740
export const CardSelectorList = React.memo<CardSelectorListProps>(
38-
({ items, onSelect, selectedItem, title }) => {
41+
({ items, onSelect, selectedItem, title, cardId }) => {
42+
const { telemetry } = useOnboardingContext();
3943
const styles = useCardSelectorListStyles();
4044

4145
useEffect(() => {
4246
scrollToSelectedItem(selectedItem.id);
4347
// eslint-disable-next-line react-hooks/exhaustive-deps
4448
}, []);
4549

50+
const handleSelect = useCallback(
51+
(item: CardSelectorListItem) => {
52+
onSelect(item);
53+
telemetry.reportCardSelectorClicked(cardId, item.id);
54+
},
55+
[cardId, onSelect, telemetry]
56+
);
57+
4658
return (
4759
<EuiFlexGroup
4860
data-test-subj="cardSelectorList"
@@ -72,9 +84,7 @@ export const CardSelectorList = React.memo<CardSelectorListProps>(
7284
className={selectedItem.id === item.id ? 'selectedCardPanelItem' : ''}
7385
color={selectedItem.id === item.id ? 'subdued' : 'plain'}
7486
element="button"
75-
onClick={() => {
76-
onSelect(item);
77-
}}
87+
onClick={() => handleSelect(item)}
7888
>
7989
<EuiTitle size="xxs">
8090
<h5>{item.title}</h5>

x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/dashboards/dashboards_card.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export const DashboardsCard: OnboardingCardComponent = ({
8080
items={DASHBOARDS_CARD_ITEMS}
8181
onSelect={onSelectCard}
8282
selectedItem={selectedCardItem}
83+
cardId={OnboardingCardId.dashboards}
8384
/>
8485
{isIntegrationsCardAvailable && !isIntegrationsCardComplete && (
8586
<>

x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_body/cards/rules/rules_card.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export const RulesCard: OnboardingCardComponent = ({
8080
items={RULES_CARD_ITEMS}
8181
onSelect={onSelectCard}
8282
selectedItem={selectedCardItem}
83+
cardId={OnboardingCardId.rules}
8384
/>
8485
{isIntegrationsCardAvailable && !isIntegrationsCardComplete && (
8586
<>

x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_telemetry.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,26 @@ describe('useOnboardingTelemetry', () => {
112112
);
113113
});
114114
});
115+
116+
describe('when clicking a card selector', () => {
117+
it('should report card selector clicked event on the default topic', () => {
118+
const { result } = renderHook(useOnboardingTelemetry);
119+
result.current.reportCardSelectorClicked('testCard' as OnboardingCardId, 'selector1');
120+
121+
expect(telemetryMock.reportEvent).toHaveBeenCalledWith(
122+
OnboardingHubEventTypes.OnboardingHubStepSelectorClicked,
123+
{ originStepId: 'testCard', selectorId: 'selector1' }
124+
);
125+
});
126+
127+
it('should report card selector clicked event on another topic', () => {
128+
const { result } = renderHook(useOnboardingTelemetry);
129+
result.current.reportCardSelectorClicked('testCard2' as OnboardingCardId, 'selector2');
130+
131+
expect(telemetryMock.reportEvent).toHaveBeenCalledWith(
132+
OnboardingHubEventTypes.OnboardingHubStepSelectorClicked,
133+
{ originStepId: 'testTopic#testCard2', selectorId: 'selector2' }
134+
);
135+
});
136+
});
115137
});

x-pack/solutions/security/plugins/security_solution/public/onboarding/components/onboarding_telemetry.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface OnboardingTelemetry {
1616
reportCardOpen: (cardId: OnboardingCardId, options?: { auto?: boolean }) => void;
1717
reportCardComplete: (cardId: OnboardingCardId, options?: { auto?: boolean }) => void;
1818
reportCardLinkClicked: (cardId: OnboardingCardId, linkId: string) => void;
19+
reportCardSelectorClicked: (cardId: OnboardingCardId, selectorId: string) => void;
1920
}
2021

2122
export const useOnboardingTelemetry = (): OnboardingTelemetry => {
@@ -40,6 +41,12 @@ export const useOnboardingTelemetry = (): OnboardingTelemetry => {
4041
stepLinkId: linkId,
4142
});
4243
},
44+
reportCardSelectorClicked: (cardId, selectorId: string) => {
45+
telemetry.reportEvent(OnboardingHubEventTypes.OnboardingHubStepSelectorClicked, {
46+
originStepId: getStepId(cardId),
47+
selectorId,
48+
});
49+
},
4350
}),
4451
[telemetry]
4552
);

0 commit comments

Comments
 (0)