Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Update rule link in cases activities #198836

Merged
merged 4 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
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,16 @@ const RuleLink: React.FC<SingleAlertProps> = memo(

const ruleDetailsHref = getRuleDetailsHref?.(ruleId);
const finalRuleName = ruleName ?? i18n.UNKNOWN_RULE;
const isValidLink = useMemo(() => {
if (!onRuleDetailsClick && !ruleDetailsHref) return false;
christineweng marked this conversation as resolved.
Show resolved Hide resolved
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
40 changes: 18 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,22 @@ const CaseContainerComponent: React.FC = () => {
[openFlyout, telemetry]
);

const onRuleDetailsClick = useCallback(
(ruleId: string | null | undefined) => {
if (ruleId) {
openFlyout({
right: {
id: RulePanelKey,
params: {
ruleId,
},
},
});
christineweng marked this conversation as resolved.
Show resolved Hide resolved
}
},
[openFlyout]
);

const { onLoad: onAlertsTableLoaded } = useFetchNotes();

const endpointDetailsHref = (endpointId: string) =>
Expand Down Expand Up @@ -138,16 +143,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