Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38810,8 +38810,6 @@
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.badges.sharedBadge.visibilityDropdownLabel": "Sichtbarkeit",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.badges.sharedBadge.visibleToYourTeamLabel": "Sichtbar für Ihr Team",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.accordionButton.aiConnectorTooltipTitle": "KI-Connector",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.title.openScheduleDetailsLabel": "Zeitplandetails öffnen",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.title.scheduledAttackDiscoveryTooltipTitle": "Geplante Angriffserkennung",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.subtitle.createdByUserLabel": "Erstellt von: {user}",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.summaryActions.alertsLabel": "Alerts:",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.summaryActions.attackChainLabel": "Angriffskette:",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38727,8 +38727,6 @@
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.badges.sharedBadge.visibilityDropdownLabel": "Visibilité",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.badges.sharedBadge.visibleToYourTeamLabel": "Visible pour votre équipe",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.accordionButton.aiConnectorTooltipTitle": "Connecteur IA",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.title.openScheduleDetailsLabel": "Ouvrir les détails du planning",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.title.scheduledAttackDiscoveryTooltipTitle": "Détection planifiée d'attaques",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.subtitle.createdByUserLabel": "Créé par : {user}",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.summaryActions.alertsLabel": "Alertes :",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.summaryActions.attackChainLabel": "Chaîne d'attaque :",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38930,8 +38930,6 @@
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.badges.sharedBadge.visibilityDropdownLabel": "可視性",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.badges.sharedBadge.visibleToYourTeamLabel": "チームに表示",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.accordionButton.aiConnectorTooltipTitle": "AIコネクター",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.title.openScheduleDetailsLabel": "スケジュールの詳細を開く",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.title.scheduledAttackDiscoveryTooltipTitle": "スケジュールされた攻撃検出",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.subtitle.createdByUserLabel": "作成者: {user}",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.summaryActions.alertsLabel": "アラート:",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.summaryActions.attackChainLabel": "攻撃チェーン:",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38936,8 +38936,6 @@
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.badges.sharedBadge.visibilityDropdownLabel": "可见性",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.badges.sharedBadge.visibleToYourTeamLabel": "对您的团队可见",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.accordionButton.aiConnectorTooltipTitle": "AI 连接器",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.title.openScheduleDetailsLabel": "打开日程详情",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.title.scheduledAttackDiscoveryTooltipTitle": "计划的攻击发现",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.subtitle.createdByUserLabel": "创建者:{user}",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.summaryActions.alertsLabel": "告警:",
"xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.summaryActions.attackChainLabel": "攻击链:",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,13 @@ export class DetectionsAttackDiscoveryPage {
public attacksTableSection: Locator;
public scheduleButton: Locator;
public settingsFlyout: Locator;
public scheduleDetailsFlyout: Locator;
public schedulesTable: Locator;
public attackDetailsFlyoutBody: Locator;
public assigneesFilterButton: Locator;
public connectorFilterButton: Locator;
public tableExpandAttackDetailsButtons: Locator;
public tableScheduleButtons: Locator;
public settingsButton: Locator;
public generateButton: Locator;
public runButton: Locator;
Expand All @@ -91,13 +93,17 @@ export class DetectionsAttackDiscoveryPage {
this.attacksTableSection = this.page.testSubj.locator(ATTACKS_PAGE_TABLE_SECTION_TEST_ID);
this.scheduleButton = this.page.testSubj.locator(SCHEDULE_BUTTON_TEST_ID);
this.settingsFlyout = this.page.testSubj.locator(SETTINGS_FLYOUT_TEST_ID);
this.scheduleDetailsFlyout = this.page.testSubj.locator('scheduleDetailsFlyout');
this.schedulesTable = this.page.testSubj.locator(SCHEDULES_TABLE_TEST_ID);
this.attackDetailsFlyoutBody = this.page.testSubj.locator(ATTACK_DETAILS_FLYOUT_BODY_TEST_ID);
this.assigneesFilterButton = this.page.testSubj.locator(FILTER_BY_ASSIGNEES_BUTTON_TEST_ID);
this.connectorFilterButton = this.page.testSubj.locator(CONNECTOR_FILTER_BUTTON_TEST_ID);
this.tableExpandAttackDetailsButtons = this.attacksTableSection.locator(
`[data-test-subj="${EXPAND_ATTACK_BUTTON_TEST_ID}"]`
);
this.tableScheduleButtons = this.attacksTableSection.locator(
`[data-test-subj="scheduleButton"]`
);
this.settingsButton = this.page.testSubj.locator('settings');
this.generateButton = this.page.testSubj.locator('generate');
this.runButton = this.page.testSubj.locator('run');
Expand Down Expand Up @@ -174,7 +180,20 @@ export class DetectionsAttackDiscoveryPage {
throw new Error('No attack details expand button found');
}

await firstExpandAttackButton.scrollIntoViewIfNeeded();
await firstExpandAttackButton.click();
await this.attackDetailsFlyoutBody.waitFor({ state: 'visible' });
}

async openFirstScheduleDetailsFromTable() {
const [firstScheduleButton] = await this.tableScheduleButtons.all();

if (!firstScheduleButton) {
throw new Error('No schedule button found');
}

await firstScheduleButton.scrollIntoViewIfNeeded();
await firstScheduleButton.click();
await this.scheduleDetailsFlyout.waitFor({ state: 'visible' });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,30 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';

import { AttacksEventTypes } from '../../../../../../../common/lib/telemetry';
import { Title } from '.';
import { TestProviders } from '../../../../../../../common/mock';

const mockReportEvent = jest.fn();
jest.mock('../../../../../../../common/lib/kibana', () => {
const original = jest.requireActual('../../../../../../../common/lib/kibana');
return {
...original,
useKibana: () => {
const actual = original.useKibana();
return {
...actual,
services: {
...actual.services,
telemetry: {
reportEvent: mockReportEvent,
},
},
};
},
};
});

jest.mock('@kbn/elastic-assistant-common', () => ({
ATTACK_DISCOVERY_AD_HOC_RULE_ID: 'ad-hoc-rule-id',
API_VERSIONS: {
Expand Down Expand Up @@ -167,7 +188,7 @@ describe('Title', () => {
});

describe('schedule detection', () => {
it('renders DetailsFlyout when attack discovery has alertRuleUuid that is not ad-hoc', async () => {
it('renders DetailsFlyout and sends telemetry when attack discovery has alertRuleUuid that is not ad-hoc', async () => {
const discoveryWithSchedule = {
...mockRawResponse,
alertRuleUuid: 'scheduled-rule-id',
Expand All @@ -183,6 +204,9 @@ describe('Title', () => {
await userEvent.click(screen.getByTestId('scheduleButton'));

expect(screen.getByTestId('detailsFlyout')).toHaveTextContent('scheduled-rule-id');
expect(mockReportEvent).toHaveBeenCalledWith(AttacksEventTypes.ScheduleDetailsFlyoutOpened, {
source: 'attack_discovery_page',
});
});

it('does NOT render the schedule button when attack discovery has no alertRuleUuid', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@

import {
EuiAccordion,
EuiButtonIcon,
EuiCheckbox,
EuiFlexGroup,
EuiFlexItem,
EuiToolTip,
useEuiTheme,
useGeneratedHtmlId,
} from '@elastic/eui';
Expand All @@ -27,8 +25,10 @@ import React, { useCallback, useMemo, useState } from 'react';
import { AccordionButton } from '../accordion_button';
import { Badges } from '../badges';
import { DetailsFlyout } from '../../../../../settings_flyout/schedule/details_flyout';
import * as i18n from './translations';
import { ScheduleDetailsButton } from '../../../../../../../detections/components/attacks/schedule_details_button/schedule_details_button';
import { isAttackDiscoveryAlert } from '../../../../../utils/is_attack_discovery_alert';
import { useKibana } from '../../../../../../../common/lib/kibana';
import { AttacksEventTypes } from '../../../../../../../common/lib/telemetry';

interface Props {
attackDiscovery: AttackDiscovery | AttackDiscoveryAlert;
Expand All @@ -51,6 +51,10 @@ const TitleComponent: React.FC<Props> = ({
setIsSelected,
showAnonymized = false,
}) => {
const {
services: { telemetry },
} = useKibana();

const { euiTheme } = useEuiTheme();

const htmlId = useGeneratedHtmlId({
Expand Down Expand Up @@ -88,7 +92,10 @@ const TitleComponent: React.FC<Props> = ({

const openScheduleDetails = useCallback(() => {
setScheduleDetailsId(alertRuleUuid);
}, [alertRuleUuid]);
telemetry.reportEvent(AttacksEventTypes.ScheduleDetailsFlyoutOpened, {
source: 'attack_discovery_page',
});
}, [alertRuleUuid, telemetry]);

const onClose = useCallback(() => setScheduleDetailsId(undefined), []);

Expand Down Expand Up @@ -144,23 +151,7 @@ const TitleComponent: React.FC<Props> = ({
</EuiAccordion>
</EuiFlexItem>

{isScheduled && (
<EuiFlexItem grow={false}>
<EuiToolTip
content={i18n.SCHEDULED_ATTACK_DISCOVERY}
data-test-subj="scheduledTooltip"
position="top"
>
<EuiButtonIcon
aria-label={i18n.OPEN_SCHEDULE_DETAILS}
data-test-subj="scheduleButton"
iconType="calendar"
onClick={openScheduleDetails}
size="xs"
/>
</EuiToolTip>
</EuiFlexItem>
)}
{isScheduled && <ScheduleDetailsButton onClick={openScheduleDetails} />}

<EuiFlexItem grow={false}>
<Badges attackDiscovery={attackDiscovery} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ export const attacksScheduleFlyoutOpenedEvent: AttacksTelemetryEvent = {
},
};

export const attacksScheduleDetailsFlyoutOpenedEvent: AttacksTelemetryEvent = {
eventType: AttacksEventTypes.ScheduleDetailsFlyoutOpened,
schema: {
source: {
type: 'keyword',
_meta: { description: 'The source of the schedule details flyout open', optional: false },
},
},
};

export const attacksFeaturePromotionCalloutActionEvent: AttacksTelemetryEvent = {
eventType: AttacksEventTypes.FeaturePromotionCalloutAction,
schema: {
Expand Down Expand Up @@ -183,6 +193,7 @@ export const attacksTelemetryEvents = [
attacksDetailsFlyoutOpenedEvent,
attacksExpandedViewTabClickedEvent,
attacksScheduleFlyoutOpenedEvent,
attacksScheduleDetailsFlyoutOpenedEvent,
attacksFeaturePromotionCalloutActionEvent,
attacksWorkflowRunTriggeredEvent,
];
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export enum AttacksEventTypes {
DetailsFlyoutOpened = 'Attacks Details Flyout Opened',
ExpandedViewTabClicked = 'Attacks Expanded View Tab Clicked',
ScheduleFlyoutOpened = 'Attacks Schedule Flyout Opened',
ScheduleDetailsFlyoutOpened = 'Attacks Schedule Details Flyout Opened',
FeaturePromotionCalloutAction = 'Attacks Feature Promotion Callout Action',
WorkflowRunTriggered = 'Attacks Workflow Run Triggered',
}
Expand Down Expand Up @@ -54,6 +55,10 @@ interface AttacksScheduleFlyoutOpenedParams {
source: 'attacks_page_header' | 'attacks_page_empty_state';
}

interface AttacksScheduleDetailsFlyoutOpenedParams {
source: 'attacks_page_table' | 'attack_discovery_page';
}

interface AttacksActionStatusUpdatedParams extends AttacksActionBaseParams {
status: string;
scope?: AttacksUpdateScope;
Expand Down Expand Up @@ -97,6 +102,7 @@ export interface AttacksTelemetryEventsMap {
[AttacksEventTypes.DetailsFlyoutOpened]: AttacksDetailsFlyoutOpenedParams;
[AttacksEventTypes.ExpandedViewTabClicked]: AttacksExpandedViewTabClickedParams;
[AttacksEventTypes.ScheduleFlyoutOpened]: AttacksScheduleFlyoutOpenedParams;
[AttacksEventTypes.ScheduleDetailsFlyoutOpened]: AttacksScheduleDetailsFlyoutOpenedParams;
[AttacksEventTypes.FeaturePromotionCalloutAction]: AttacksFeaturePromotionCalloutActionParams;
[AttacksEventTypes.WorkflowRunTriggered]: AttacksActionBaseParams;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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, fireEvent } from '@testing-library/react';

import { ScheduleDetailsButton } from './schedule_details_button';

describe('ScheduleDetailsButton', () => {
it('renders the button and tooltip', () => {
const onClickMock = jest.fn();
const { getByTestId, getByLabelText } = render(<ScheduleDetailsButton onClick={onClickMock} />);

const button = getByTestId('scheduleButton');
expect(button).toBeInTheDocument();
expect(getByLabelText('Open schedule details')).toBeInTheDocument();
});

it('calls onClick when the button is clicked', () => {
const onClickMock = jest.fn();
const { getByTestId } = render(<ScheduleDetailsButton onClick={onClickMock} />);

const button = getByTestId('scheduleButton');
fireEvent.click(button);

expect(onClickMock).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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, { useCallback } from 'react';
import { EuiButtonIcon, EuiFlexItem, EuiToolTip } from '@elastic/eui';

import * as i18n from './translations';

export interface ScheduleDetailsButtonProps {
onClick: () => void;
}

export const ScheduleDetailsButton = React.memo<ScheduleDetailsButtonProps>(({ onClick }) => {
const handleClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
onClick();
},
[onClick]
);

return (
<EuiFlexItem grow={false}>
<EuiToolTip
content={i18n.SCHEDULED_ATTACK_DISCOVERY}
data-test-subj="scheduledTooltip"
position="top"
>
<EuiButtonIcon
aria-label={i18n.OPEN_SCHEDULE_DETAILS}
data-test-subj="scheduleButton"
iconType="calendar"
onClick={handleClick}
size="xs"
/>
</EuiToolTip>
</EuiFlexItem>
);
});
ScheduleDetailsButton.displayName = 'ScheduleDetailsButton';
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
import { i18n } from '@kbn/i18n';

export const OPEN_SCHEDULE_DETAILS = i18n.translate(
'xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.title.openScheduleDetailsLabel',
'xpack.securitySolution.detectionEngine.attacks.scheduleDetailsButton.openScheduleDetailsLabel',
{
defaultMessage: 'Open schedule details',
}
);

export const SCHEDULED_ATTACK_DISCOVERY = i18n.translate(
'xpack.securitySolution.attackDiscovery.results.attackDiscoveryPanel.panelHeader.primaryInteractions.title.scheduledAttackDiscoveryTooltipTitle',
'xpack.securitySolution.detectionEngine.attacks.scheduleDetailsButton.scheduledAttackDiscoveryTooltipTitle',
{
defaultMessage: 'Scheduled Attack discovery',
}
Expand Down
Loading
Loading