Skip to content

Commit

Permalink
[8.x] [Security Solution] Update rule link in cases activities (#198836
Browse files Browse the repository at this point in the history
…) (#199242)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution] Update rule link in cases activities
(#198836)](#198836)

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

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

<!--BACKPORT
[{"author":{"name":"christineweng","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-06T22:57:55Z","message":"[Security
Solution] Update rule link in cases activities (#198836)\n\n##
Summary\r\n\r\nAs a follow up to
#191764, this PR\r\nupdates rule
names in `Activity` tab in cases to open a rule flyout\r\ninstead of
going to rule details page.\r\n\r\n**Security solution changes**:
replace rule page navigation with a\r\n`openFlyout` call\r\n**Cases
plugin changes**: update the rules link component to accept\r\neither
`href` or
`onClick`.\r\n\r\n\r\n![image](https://github.com/user-attachments/assets/c5dca885-61b8-4481-adfa-f9e615a01265)\r\n\r\n\r\n###
Checklist\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":"09dd66d355fbb32fa0090e36945819a9509138c6","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","backport","v9.0.0","Team:Threat
Hunting","v8.17.0"],"title":"[Security Solution] Update rule link in
cases
activities","number":198836,"url":"https://github.com/elastic/kibana/pull/198836","mergeCommit":{"message":"[Security
Solution] Update rule link in cases activities (#198836)\n\n##
Summary\r\n\r\nAs a follow up to
#191764, this PR\r\nupdates rule
names in `Activity` tab in cases to open a rule flyout\r\ninstead of
going to rule details page.\r\n\r\n**Security solution changes**:
replace rule page navigation with a\r\n`openFlyout` call\r\n**Cases
plugin changes**: update the rules link component to accept\r\neither
`href` or
`onClick`.\r\n\r\n\r\n![image](https://github.com/user-attachments/assets/c5dca885-61b8-4481-adfa-f9e615a01265)\r\n\r\n\r\n###
Checklist\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":"09dd66d355fbb32fa0090e36945819a9509138c6"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/198836","number":198836,"mergeCommit":{"message":"[Security
Solution] Update rule link in cases activities (#198836)\n\n##
Summary\r\n\r\nAs a follow up to
#191764, this PR\r\nupdates rule
names in `Activity` tab in cases to open a rule flyout\r\ninstead of
going to rule details page.\r\n\r\n**Security solution changes**:
replace rule page navigation with a\r\n`openFlyout` call\r\n**Cases
plugin changes**: update the rules link component to accept\r\neither
`href` or
`onClick`.\r\n\r\n\r\n![image](https://github.com/user-attachments/assets/c5dca885-61b8-4481-adfa-f9e615a01265)\r\n\r\n\r\n###
Checklist\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":"09dd66d355fbb32fa0090e36945819a9509138c6"}},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: christineweng <[email protected]>
  • Loading branch information
kibanamachine and christineweng authored Nov 7, 2024
1 parent 2d180fc commit 6530bea
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 33 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/cases/public/components/links/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useCaseViewNavigation, useConfigureCasesNavigation } from '../../common
import * as i18n from './translations';

export interface CasesNavigation<T = React.MouseEvent | MouseEvent | null, K = null> {
href: K extends 'configurable' ? (arg: T) => string : string;
href?: K extends 'configurable' ? (arg: T) => string : string;
onClick: K extends 'configurable'
? (arg: T, arg2: React.MouseEvent | MouseEvent) => Promise<void> | void
: (arg: T) => Promise<void> | void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,38 @@ describe('Alert events', () => {
expect(wrapper.text()).toBe('added an alert from Awesome rule');
});

it('does NOT render the link when the rule id is null', async () => {
it('renders the link when onClick is provided but href is not valid', async () => {
const wrapper = mount(
<TestProviders>
<SingleAlertCommentEvent {...props} ruleId={null} />
<SingleAlertCommentEvent {...props} getRuleDetailsHref={undefined} />
</TestProviders>
);

expect(
wrapper.find(`[data-test-subj="alert-rule-link-action-id-1"]`).first().exists()
).toBeTruthy();
});

it('renders the link when href is valid but onClick is not available', async () => {
const wrapper = mount(
<TestProviders>
<SingleAlertCommentEvent {...props} onRuleDetailsClick={undefined} />
</TestProviders>
);

expect(
wrapper.find(`[data-test-subj="alert-rule-link-action-id-1"]`).first().exists()
).toBeTruthy();
});

it('does NOT render the link when the href and onclick are invalid but it shows the rule name', async () => {
const wrapper = mount(
<TestProviders>
<SingleAlertCommentEvent
{...props}
getRuleDetailsHref={undefined}
onRuleDetailsClick={undefined}
/>
</TestProviders>
);

Expand All @@ -61,10 +89,10 @@ describe('Alert events', () => {
expect(wrapper.text()).toBe('added an alert from Awesome rule');
});

it('does NOT render the link when the href is invalid but it shows the rule name', async () => {
it('does NOT render the link when the rule id is null', async () => {
const wrapper = mount(
<TestProviders>
<SingleAlertCommentEvent {...props} getRuleDetailsHref={undefined} />
<SingleAlertCommentEvent {...props} ruleId={null} />
</TestProviders>
);

Expand Down Expand Up @@ -131,9 +159,28 @@ describe('Alert events', () => {
expect(result.getByTestId('alert-rule-link-action-id-1')).toHaveTextContent('Awesome rule');
});

it('does NOT render the link when the rule id is null', async () => {
it('renders the link when onClick is provided but href is not valid', async () => {
const result = appMock.render(
<MultipleAlertsCommentEvent {...props} totalAlerts={2} ruleId={null} />
<MultipleAlertsCommentEvent {...props} totalAlerts={2} getRuleDetailsHref={undefined} />
);
expect(result.getByTestId('alert-rule-link-action-id-1')).toHaveTextContent('Awesome rule');
});

it('renders the link when href is valid but onClick is not available', async () => {
const result = appMock.render(
<MultipleAlertsCommentEvent {...props} totalAlerts={2} onRuleDetailsClick={undefined} />
);
expect(result.getByTestId('alert-rule-link-action-id-1')).toHaveTextContent('Awesome rule');
});

it('does NOT render the link when the href and onclick are invalid but it shows the rule name', async () => {
const result = appMock.render(
<MultipleAlertsCommentEvent
{...props}
totalAlerts={2}
getRuleDetailsHref={undefined}
onRuleDetailsClick={undefined}
/>
);

expect(result.getByTestId('multiple-alerts-user-action-action-id-1')).toHaveTextContent(
Expand All @@ -142,9 +189,9 @@ describe('Alert events', () => {
expect(result.queryByTestId('alert-rule-link-action-id-1')).toBeFalsy();
});

it('does NOT render the link when the href is invalid but it shows the rule name', async () => {
it('does NOT render the link when the rule id is null', async () => {
const result = appMock.render(
<MultipleAlertsCommentEvent {...props} totalAlerts={2} getRuleDetailsHref={undefined} />
<MultipleAlertsCommentEvent {...props} totalAlerts={2} ruleId={null} />
);

expect(result.getByTestId('multiple-alerts-user-action-action-id-1')).toHaveTextContent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { memo, useCallback } from 'react';
import React, { memo, useCallback, useMemo } from 'react';
import { isEmpty } from 'lodash';
import { EuiLoadingSpinner } from '@elastic/eui';

Expand Down Expand Up @@ -38,12 +38,18 @@ const RuleLink: React.FC<SingleAlertProps> = memo(

const ruleDetailsHref = getRuleDetailsHref?.(ruleId);
const finalRuleName = ruleName ?? i18n.UNKNOWN_RULE;
const isValidLink = useMemo(() => {
if (!onRuleDetailsClick && !ruleDetailsHref) {
return false;
}
return !isEmpty(ruleId);
}, [onRuleDetailsClick, ruleDetailsHref, ruleId]);

if (loadingAlertData) {
return <EuiLoadingSpinner size="m" data-test-subj={`alert-loading-spinner-${actionId}`} />;
}

if (!isEmpty(ruleId) && ruleDetailsHref != null) {
if (isValidLink) {
return (
<LinkAnchor
onClick={onLinkClick}
Expand Down
33 changes: 11 additions & 22 deletions x-pack/plugins/security_solution/public/cases/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ import { CaseMetricsFeature } from '@kbn/cases-plugin/common';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { CaseDetailsRefreshContext } from '../../common/components/endpoint';
import { DocumentDetailsRightPanelKey } from '../../flyout/document_details/shared/constants/panel_keys';
import { RulePanelKey } from '../../flyout/rule_details/right';
import { useTourContext } from '../../common/components/guided_onboarding_tour';
import {
AlertsCasesTourSteps,
SecurityStepId,
} from '../../common/components/guided_onboarding_tour/tour_config';
import { TimelineId } from '../../../common/types/timeline';

import { getRuleDetailsUrl, useFormatUrl } from '../../common/components/link_to';

import { useKibana, useNavigation } from '../../common/lib/kibana';
import { APP_ID, CASES_PATH, SecurityPageName } from '../../../common/constants';
import { timelineActions } from '../../timelines/store';
Expand All @@ -38,17 +36,8 @@ const CaseContainerComponent: React.FC = () => {
const { getAppUrl, navigateTo } = useNavigation();
const userCasesPermissions = cases.helpers.canUseCases([APP_ID]);
const dispatch = useDispatch();
const { formatUrl: detectionsFormatUrl, search: detectionsUrlSearch } = useFormatUrl(
SecurityPageName.rules
);
const { openFlyout } = useExpandableFlyoutApi();

const getDetectionsRuleDetailsHref = useCallback(
(ruleId: string | null | undefined) =>
detectionsFormatUrl(getRuleDetailsUrl(ruleId ?? '', detectionsUrlSearch)),
[detectionsFormatUrl, detectionsUrlSearch]
);

const interactionsUpsellingMessage = useUpsellingMessage('investigation_guide_interactions');

const showAlertDetails = useCallback(
Expand All @@ -71,6 +60,15 @@ const CaseContainerComponent: React.FC = () => {
[openFlyout, telemetry]
);

const onRuleDetailsClick = useCallback(
(ruleId: string | null | undefined) => {
if (ruleId) {
openFlyout({ right: { id: RulePanelKey, params: { ruleId } } });
}
},
[openFlyout]
);

const { onLoad: onAlertsTableLoaded } = useFetchNotes();

const endpointDetailsHref = (endpointId: string) =>
Expand Down Expand Up @@ -138,16 +136,7 @@ const CaseContainerComponent: React.FC = () => {
},
},
ruleDetailsNavigation: {
href: getDetectionsRuleDetailsHref,
onClick: async (ruleId: string | null | undefined, e) => {
if (e) {
e.preventDefault();
}
return navigateTo({
deepLinkId: SecurityPageName.rules,
path: getRuleDetailsUrl(ruleId ?? ''),
});
},
onClick: onRuleDetailsClick,
},
showAlertDetails,
timelineIntegration: {
Expand Down

0 comments on commit 6530bea

Please sign in to comment.