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] Add rule snoozing on the rule editing page #155612

Merged
banderror marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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 * from './rule_snooze_badge';
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,26 @@

import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import React, { useMemo } from 'react';
import { useUserData } from '../../detections/components/user_info';
import { hasUserCRUDPermission } from '../../common/utils/privileges';
import { useKibana } from '../../common/lib/kibana';
import type { RuleSnoozeSettings } from '../rule_management/logic';
import { useInvalidateFetchRulesSnoozeSettingsQuery } from '../rule_management/api/hooks/use_fetch_rules_snooze_settings';
import { useUserData } from '../../../detections/components/user_info';
import { hasUserCRUDPermission } from '../../../common/utils/privileges';
import { useKibana } from '../../../common/lib/kibana';
import { useInvalidateFetchRulesSnoozeSettingsQuery } from '../../rule_management/api/hooks/use_fetch_rules_snooze_settings';
import { useRuleSnoozeSettings } from './use_rule_snooze_settings';

interface RuleSnoozeBadgeProps {
/**
* Rule's snooze settings, when set to `undefined` considered as a loading state
* Rule's SO id (not ruleId)
*/
snoozeSettings: RuleSnoozeSettings | undefined;
/**
* It should represent a user readable error message happened during data snooze settings fetching
*/
error?: string;
id: string;
banderror marked this conversation as resolved.
Show resolved Hide resolved
showTooltipInline?: boolean;
}

export function RuleSnoozeBadge({
snoozeSettings,
error,
id,
showTooltipInline = false,
}: RuleSnoozeBadgeProps): JSX.Element {
const RulesListNotifyBadge = useKibana().services.triggersActionsUi.getRulesListNotifyBadge;
const { snoozeSettings, error } = useRuleSnoozeSettings(id);
const [{ canUserCRUD }] = useUserData();
const hasCRUDPermissions = hasUserCRUDPermission(canUserCRUD);
const invalidateFetchRuleSnoozeSettings = useInvalidateFetchRulesSnoozeSettingsQuery();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

import { i18n } from '@kbn/i18n';

export const UNABLE_TO_FETCH_RULE_SNOOZE_SETTINGS = i18n.translate(
'xpack.securitySolution.detectionEngine.ruleDetails.rulesSnoozeSettings.error.unableToFetch',
export const UNABLE_TO_FETCH_RULES_SNOOZE_SETTINGS = i18n.translate(
'xpack.securitySolution.detectionEngine.rulesSnoozeBadge.error.unableToFetch',
{
defaultMessage: 'Unable to fetch snooze settings',
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 type { RuleSnoozeSettings } from '../../rule_management/logic';
import { useFetchRulesSnoozeSettings } from '../../rule_management/api/hooks/use_fetch_rules_snooze_settings';
import { useRulesTableContextOptional } from '../../rule_management_ui/components/rules_table/rules_table/rules_table_context';
import * as i18n from './translations';

interface UseRuleSnoozeSettingsResult {
snoozeSettings?: RuleSnoozeSettings;
error?: string;
}

export function useRuleSnoozeSettings(id: string): UseRuleSnoozeSettingsResult {
const {
state: { rulesSnoozeSettings: rulesTableSnoozeSettings },
} = useRulesTableContextOptional() ?? { state: {} };
const {
data: rulesSnoozeSettings,
isFetching: isSingleSnoozeSettingsFetching,
isError: isSingleSnoozeSettingsError,
} = useFetchRulesSnoozeSettings([id], { enabled: !rulesTableSnoozeSettings?.data[id] });
const snoozeSettings = rulesTableSnoozeSettings?.data[id] ?? rulesSnoozeSettings?.[0];
const isFetching = rulesTableSnoozeSettings?.isFetching || isSingleSnoozeSettingsFetching;
const isError = rulesTableSnoozeSettings?.isError || isSingleSnoozeSettingsError;

return {
snoozeSettings,
error:
isError || (!snoozeSettings && !isFetching)
? i18n.UNABLE_TO_FETCH_RULES_SNOOZE_SETTINGS
: undefined,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ const EditRulePageComponent: FC = () => {
<StepPanel loading={loading}>
{actionsStep.data != null && (
<StepRuleActions
id={rule?.id}
isReadOnlyView={false}
isLoading={isLoading}
isUpdateView
Expand All @@ -319,6 +320,7 @@ const EditRulePageComponent: FC = () => {
},
],
[
rule?.id,
rule?.immutable,
rule?.type,
loading,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ jest.mock('react-router-dom', () => {
});

// RuleDetailsSnoozeSettings is an isolated component and not essential for existing tests
jest.mock('./components/rule_details_snooze_settings', () => ({
RuleDetailsSnoozeSettings: () => <></>,
jest.mock('../../../components/rule_snooze_badge', () => ({
RuleSnoozeBadge: () => <></>,
}));

const mockRedirectLegacyUrl = jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ import { EditRuleSettingButtonLink } from '../../../../detections/pages/detectio
import { useStartMlJobs } from '../../../rule_management/logic/use_start_ml_jobs';
import { useBulkDuplicateExceptionsConfirmation } from '../../../rule_management_ui/components/rules_table/bulk_actions/use_bulk_duplicate_confirmation';
import { BulkActionDuplicateExceptionsConfirmation } from '../../../rule_management_ui/components/rules_table/bulk_actions/bulk_duplicate_exceptions_confirmation';
import { RuleDetailsSnoozeSettings } from './components/rule_details_snooze_settings';
import { RuleSnoozeBadge } from '../../../components/rule_snooze_badge';

/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
Expand Down Expand Up @@ -559,7 +559,7 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
</RuleStatus>
)}
<EuiFlexItem grow={false}>
<RuleDetailsSnoozeSettings id={ruleId} />
<RuleSnoozeBadge id={ruleId} showTooltipInline />
</EuiFlexItem>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,3 @@ export const ML_RULE_JOBS_WARNING_BUTTON_LABEL = i18n.translate(
defaultMessage: 'Visit rule details page to investigate',
}
);

export const UNABLE_TO_FETCH_RULES_SNOOZE_SETTINGS = i18n.translate(
'xpack.securitySolution.detectionEngine.ruleManagement.rulesSnoozeSettings.error.unableToFetch',
{
defaultMessage: 'Unable to fetch snooze settings',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import { useHasActionsPrivileges } from './use_has_actions_privileges';
import { useHasMlPermissions } from './use_has_ml_permissions';
import { useRulesTableActions } from './use_rules_table_actions';
import { MlRuleWarningPopover } from './ml_rule_warning_popover';
import * as rulesTableI18n from './translations';

export type TableColumn = EuiBasicTableColumn<Rule> | EuiTableActionsColumnType<Rule>;

Expand Down Expand Up @@ -109,33 +108,15 @@ const useEnabledColumn = ({ hasCRUDPermissions, startMlJobs }: ColumnsProps): Ta
};

const useRuleSnoozeColumn = (): TableColumn => {
const {
state: { rulesSnoozeSettings },
} = useRulesTableContext();

return useMemo(
() => ({
field: 'snooze',
name: i18n.COLUMN_SNOOZE,
render: (_, rule: Rule) => {
const snoozeSettings = rulesSnoozeSettings.data[rule.id];
const { isFetching, isError } = rulesSnoozeSettings;

return (
<RuleSnoozeBadge
snoozeSettings={snoozeSettings}
error={
isError || (!snoozeSettings && !isFetching)
? rulesTableI18n.UNABLE_TO_FETCH_RULES_SNOOZE_SETTINGS
: undefined
}
/>
);
},
render: (_, rule: Rule) => <RuleSnoozeBadge id={rule.id} />,
width: '100px',
sortable: false,
}),
[rulesSnoozeSettings]
[]
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ import { useKibana } from '../../../../common/lib/kibana';
import { getSchema } from './get_schema';
import * as I18n from './translations';
import { APP_UI_ID } from '../../../../../common/constants';
import { RuleSnoozeSection } from './rule_snooze_section';

interface StepRuleActionsProps extends RuleStepProps {
id?: string; // Rule SO's id (not ruleId)
banderror marked this conversation as resolved.
Show resolved Hide resolved
defaultValues?: ActionsStepRule | null;
actionMessageParams: ActionVariables;
ruleType?: Type;
Expand Down Expand Up @@ -68,6 +70,7 @@ const DisplayActionsHeader = () => {
};

const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
id,
addPadding = false,
defaultValues,
isReadOnlyView,
Expand Down Expand Up @@ -166,9 +169,9 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
return application.capabilities.actions.show ? (
<>
<DisplayActionsHeader />
{id && <RuleSnoozeSection id={id} />}
{displayActionsOptions}
{responseActionsEnabled && displayResponseActionsOptions}

<UseField path="kibanaSiemAppUrl" component={GhostFormField} />
<UseField path="enabled" component={GhostFormField} />
</>
Expand All @@ -178,6 +181,7 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
</>
);
}, [
id,
application.capabilities.actions.show,
displayActionsOptions,
displayResponseActionsOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 { css } from '@emotion/react';
import { EuiFlexGroup, EuiFlexItem, EuiText, useEuiTheme } from '@elastic/eui';
import { RuleSnoozeBadge } from '../../../../detection_engine/components/rule_snooze_badge';
import * as i18n from './translations';

interface RuleSnoozeSectionProps {
id: string; // Rule SO's id (not ruleId)
banderror marked this conversation as resolved.
Show resolved Hide resolved
}

export function RuleSnoozeSection({ id }: RuleSnoozeSectionProps): JSX.Element {
const { euiTheme } = useEuiTheme();

return (
<section>
<EuiText size="s">{i18n.RULE_SNOOZE_DESCRIPTION}</EuiText>
<EuiFlexGroup
alignItems="center"
css={css`
margin-top: ${euiTheme.size.s};
`}
>
<EuiFlexItem grow={false}>
<RuleSnoozeBadge id={id} showTooltipInline />
</EuiFlexItem>
<EuiFlexItem>
<EuiText size="s">
<strong>{i18n.SNOOZED_ACTIONS_WARNING}</strong>
</EuiText>
</EuiFlexItem>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we show the Actions will not be preformed until it is unsnoozed message unconditionally, i.e. regardless of whether the rule is snoozed or not. Since it's in bold it feels like a warning in the case where it doesn't really matter:

Screenshot 2023-04-24 at 18 36 31

Can we hide it when the rule is not snoozed? If it's not trivial, can we make it look less dangerous by making the font regular and playing with the copy a little bit? E.g. If snoozed actions will not be triggered.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@banderror your comment is addressed by this PR.

</EuiFlexGroup>
</section>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,18 @@ export const NO_ACTIONS_READ_PERMISSIONS = i18n.translate(
'Cannot create rule actions. You do not have "Read" permissions for the "Actions" plugin.',
}
);

export const RULE_SNOOZE_DESCRIPTION = i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepRuleActions.snoozeDescription',
{
defaultMessage:
'Select when automated actions should be performed if a rule evaluates as true.',
}
);

export const SNOOZED_ACTIONS_WARNING = i18n.translate(
'xpack.securitySolution.detectionEngine.createRule.stepRuleActions.snoozedActionsWarning',
{
defaultMessage: 'Actions will not be preformed until it is unsnoozed.',
}
);