-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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 snooze settings on the rule details page #155407
Changes from all commits
543fbe5
ede1b07
d8da26b
5ed58d0
f09f722
caf95b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
* 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 { 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'; | ||
|
||
interface RuleSnoozeBadgeProps { | ||
/** | ||
* Rule's snooze settings, when set to `undefined` considered as a loading state | ||
*/ | ||
snoozeSettings: RuleSnoozeSettings | undefined; | ||
/** | ||
* It should represent a user readable error message happened during data snooze settings fetching | ||
*/ | ||
error?: string; | ||
showTooltipInline?: boolean; | ||
} | ||
|
||
export function RuleSnoozeBadge({ | ||
snoozeSettings, | ||
error, | ||
showTooltipInline = false, | ||
}: RuleSnoozeBadgeProps): JSX.Element { | ||
const RulesListNotifyBadge = useKibana().services.triggersActionsUi.getRulesListNotifyBadge; | ||
const [{ canUserCRUD }] = useUserData(); | ||
const hasCRUDPermissions = hasUserCRUDPermission(canUserCRUD); | ||
const invalidateFetchRuleSnoozeSettings = useInvalidateFetchRulesSnoozeSettingsQuery(); | ||
const isLoading = !snoozeSettings; | ||
const rule = useMemo(() => { | ||
return { | ||
id: snoozeSettings?.id ?? '', | ||
muteAll: snoozeSettings?.mute_all ?? false, | ||
activeSnoozes: snoozeSettings?.active_snoozes ?? [], | ||
isSnoozedUntil: snoozeSettings?.is_snoozed_until | ||
? new Date(snoozeSettings.is_snoozed_until) | ||
: undefined, | ||
snoozeSchedule: snoozeSettings?.snooze_schedule, | ||
isEditable: hasCRUDPermissions, | ||
}; | ||
}, [snoozeSettings, hasCRUDPermissions]); | ||
|
||
if (error) { | ||
return ( | ||
<EuiToolTip content={error}> | ||
<EuiButtonIcon size="s" iconType="bellSlash" disabled /> | ||
</EuiToolTip> | ||
); | ||
} | ||
|
||
return ( | ||
<RulesListNotifyBadge | ||
rule={rule} | ||
isLoading={isLoading} | ||
showTooltipInline={showTooltipInline} | ||
onRuleChanged={invalidateFetchRuleSnoozeSettings} | ||
/> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* 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 { useFetchRulesSnoozeSettings } from '../../../../../rule_management/api/hooks/use_fetch_rules_snooze_settings'; | ||
import { RuleSnoozeBadge } from '../../../../../components/rule_snooze_badge'; | ||
import * as i18n from './translations'; | ||
|
||
interface RuleDetailsSnoozeBadge { | ||
/** | ||
* Rule's SO id (not ruleId) | ||
*/ | ||
id: string; | ||
} | ||
|
||
export function RuleDetailsSnoozeSettings({ id }: RuleDetailsSnoozeBadge): JSX.Element { | ||
const { data: rulesSnoozeSettings, isFetching, isError } = useFetchRulesSnoozeSettings([id]); | ||
const snoozeSettings = rulesSnoozeSettings?.[0]; | ||
|
||
return ( | ||
<RuleSnoozeBadge | ||
snoozeSettings={snoozeSettings} | ||
error={ | ||
isError || (!snoozeSettings && !isFetching) | ||
? i18n.UNABLE_TO_FETCH_RULE_SNOOZE_SETTINGS | ||
: undefined | ||
} | ||
showTooltipInline={true} | ||
/> | ||
); | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,8 +40,18 @@ import { RuleSource } from './rules_table_saved_state'; | |
import { useRulesTableSavedState } from './use_rules_table_saved_state'; | ||
|
||
interface RulesSnoozeSettings { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not related to this PR directly. I was thinking about the snooze state inside the table context, and it seems a bit alien there. If I understood correctly, its purpose is to batch outgoing HTTP requests, i.e., send a single rule snooze request instead of 1 x N table rows. So I think it would be better to create an abstraction just for that. It could be a data hook that doesn't trigger a request immediately but waits some time and accumulates request params if other similar hooks are present on the page. Something similar to the GraphQL dataloader batching functionality but adapted for usage with react query. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, I also had some similar ideas already. My main concern is to make it simple, maintainable and reusable as creating such functionality only for one feature is time consuming. I think we can take the thoughts into account and analyze the possibilities when the snoozing epic is done. |
||
data: Record<string, RuleSnoozeSettings>; // The key is a rule SO's id (not ruleId) | ||
/** | ||
* A map object using rule SO's id (not ruleId) as keys and snooze settings as values | ||
*/ | ||
data: Record<string, RuleSnoozeSettings>; | ||
/** | ||
* Sets to true during the first data loading | ||
*/ | ||
isLoading: boolean; | ||
/** | ||
* Sets to true during data loading | ||
*/ | ||
isFetching: boolean; | ||
isError: boolean; | ||
} | ||
|
||
|
@@ -290,6 +300,7 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide | |
const { | ||
data: rulesSnoozeSettings, | ||
isLoading: isSnoozeSettingsLoading, | ||
isFetching: isSnoozeSettingsFetching, | ||
isError: isSnoozeSettingsFetchError, | ||
refetch: refetchSnoozeSettings, | ||
} = useFetchRulesSnoozeSettings( | ||
|
@@ -349,6 +360,7 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide | |
rulesSnoozeSettings: { | ||
data: rulesSnoozeSettingsMap, | ||
isLoading: isSnoozeSettingsLoading, | ||
isFetching: isSnoozeSettingsFetching, | ||
isError: isSnoozeSettingsFetchError, | ||
}, | ||
pagination: { | ||
|
@@ -382,6 +394,7 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide | |
rules, | ||
rulesSnoozeSettings, | ||
isSnoozeSettingsLoading, | ||
isSnoozeSettingsFetching, | ||
isSnoozeSettingsFetchError, | ||
page, | ||
perPage, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,7 +22,7 @@ import type { | |
} from '../../../../../common/detection_engine/rule_monitoring'; | ||
import { isMlRule } from '../../../../../common/machine_learning/helpers'; | ||
import { getEmptyTagValue } from '../../../../common/components/empty_value'; | ||
import { RuleSnoozeBadge } from '../../../rule_management/components/rule_snooze_badge'; | ||
import { RuleSnoozeBadge } from '../../../components/rule_snooze_badge'; | ||
import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; | ||
import { SecuritySolutionLinkAnchor } from '../../../../common/components/links'; | ||
import { getRuleDetailsTabUrl } from '../../../../common/components/link_to/redirect_to_detection_engine'; | ||
|
@@ -46,6 +46,7 @@ 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>; | ||
|
||
|
@@ -108,15 +109,33 @@ const useEnabledColumn = ({ hasCRUDPermissions, startMlJobs }: ColumnsProps): Ta | |
}; | ||
|
||
const useRuleSnoozeColumn = (): TableColumn => { | ||
const { | ||
state: { rulesSnoozeSettings }, | ||
} = useRulesTableContext(); | ||
|
||
return useMemo( | ||
() => ({ | ||
field: 'snooze', | ||
name: i18n.COLUMN_SNOOZE, | ||
render: (_, rule: Rule) => <RuleSnoozeBadge id={rule.id} />, | ||
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 | ||
} | ||
/> | ||
); | ||
}, | ||
Comment on lines
+112
to
+134
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to abstract the settings and error retrieval logic into a reusable hook. Something like: const { snoozingSettings, error } = useRuleSnoozingSettings(rule.id) Moreover, this logic is duplicated in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. I'm gonna address this in the next PR for adding support of rule snooze on the rule editing page. |
||
width: '100px', | ||
sortable: false, | ||
}), | ||
[] | ||
[rulesSnoozeSettings] | ||
); | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, it's the 14th mock added to this test file 😅.
Honestly, it might be better just to delete this messy test altogether.
Relying heavily on mocks in a single test isn't great for several reasons:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I totally agree with the comment though I'd like to abstain from any other changes in the test file in this PR. It more or less outside of scope here so I'd rather address test file refactoring in a subsequent PR if you don't mind.