diff --git a/x-pack/plugins/security_solution/public/detection_engine/components/rule_snooze_badge.tsx b/x-pack/plugins/security_solution/public/detection_engine/components/rule_snooze_badge.tsx
new file mode 100644
index 0000000000000..7fa16826eec60
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/components/rule_snooze_badge.tsx
@@ -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 (
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/index.tsx
new file mode 100644
index 0000000000000..e610715d676ce
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/index.tsx
@@ -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 (
+
+ );
+}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/translations.ts
similarity index 81%
rename from x-pack/plugins/security_solution/public/detection_engine/rule_management/components/translations.ts
rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/translations.ts
index 1b98a9c6212eb..37b3b6c75ba6e 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/translations.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/components/rule_details_snooze_settings/translations.ts
@@ -8,7 +8,7 @@
import { i18n } from '@kbn/i18n';
export const UNABLE_TO_FETCH_RULE_SNOOZE_SETTINGS = i18n.translate(
- 'xpack.securitySolution.detectionEngine.ruleManagement.ruleSnoozeBadge.error.unableToFetch',
+ 'xpack.securitySolution.detectionEngine.ruleDetails.rulesSnoozeSettings.error.unableToFetch',
{
defaultMessage: 'Unable to fetch snooze settings',
}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.test.tsx
index 28ed5a658558d..07cbd4294cb22 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.test.tsx
@@ -86,6 +86,11 @@ jest.mock('react-router-dom', () => {
};
});
+// RuleDetailsSnoozeSettings is an isolated component and not essential for existing tests
+jest.mock('./components/rule_details_snooze_settings', () => ({
+ RuleDetailsSnoozeSettings: () => <>>,
+}));
+
const mockRedirectLegacyUrl = jest.fn();
const mockGetLegacyUrlConflict = jest.fn();
jest.mock('../../../../common/lib/kibana', () => {
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx
index e53b23d16a46d..6e1b4fddbd167 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx
@@ -140,6 +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';
/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
@@ -539,23 +540,30 @@ const RuleDetailsPageComponent: React.FC = ({
const lastExecutionMessage = lastExecution?.message ?? '';
const ruleStatusInfo = useMemo(() => {
- return ruleLoading ? (
-
-
-
- ) : (
-
-
-
+ return (
+ <>
+ {ruleLoading ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+ >
);
- }, [lastExecutionStatus, lastExecutionDate, ruleLoading, isExistingRule, refreshRule]);
+ }, [ruleId, lastExecutionStatus, lastExecutionDate, ruleLoading, isExistingRule, refreshRule]);
const ruleError = useMemo(() => {
return ruleLoading ? (
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge.tsx
deleted file mode 100644
index f2a06cd5475e0..0000000000000
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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 { useInvalidateFetchRulesSnoozeSettingsQuery } from '../api/hooks/use_fetch_rules_snooze_settings';
-import { useRulesTableContext } from '../../rule_management_ui/components/rules_table/rules_table/rules_table_context';
-import * as i18n from './translations';
-
-interface RuleSnoozeBadgeProps {
- id: string; // Rule SO's id (not ruleId)
-}
-
-export function RuleSnoozeBadge({ id }: RuleSnoozeBadgeProps): JSX.Element {
- const RulesListNotifyBadge = useKibana().services.triggersActionsUi.getRulesListNotifyBadge;
- const [{ canUserCRUD }] = useUserData();
- const hasCRUDPermissions = hasUserCRUDPermission(canUserCRUD);
- const {
- state: { rulesSnoozeSettings },
- } = useRulesTableContext();
- const invalidateFetchRuleSnoozeSettings = useInvalidateFetchRulesSnoozeSettingsQuery();
- const rule = useMemo(() => {
- const ruleSnoozeSettings = rulesSnoozeSettings.data[id];
-
- return {
- id: ruleSnoozeSettings?.id ?? '',
- muteAll: ruleSnoozeSettings?.mute_all ?? false,
- activeSnoozes: ruleSnoozeSettings?.active_snoozes ?? [],
- isSnoozedUntil: ruleSnoozeSettings?.is_snoozed_until
- ? new Date(ruleSnoozeSettings.is_snoozed_until)
- : undefined,
- snoozeSchedule: ruleSnoozeSettings?.snooze_schedule,
- isEditable: hasCRUDPermissions,
- };
- }, [id, rulesSnoozeSettings, hasCRUDPermissions]);
-
- if (rulesSnoozeSettings.isError) {
- return (
-
-
-
- );
- }
-
- return (
-
- );
-}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/__mocks__/rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/__mocks__/rules_table_context.tsx
index 8ab84b2e60a60..9ee763899eab7 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/__mocks__/rules_table_context.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/__mocks__/rules_table_context.tsx
@@ -15,6 +15,7 @@ export const useRulesTableContextMock = {
rulesSnoozeSettings: {
data: {},
isLoading: false,
+ isFetching: false,
isError: false,
},
pagination: {
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx
index fa2c64f01d2d4..938174d0c567d 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/rules_table/rules_table_context.tsx
@@ -40,8 +40,18 @@ import { RuleSource } from './rules_table_saved_state';
import { useRulesTableSavedState } from './use_rules_table_saved_state';
interface RulesSnoozeSettings {
- data: Record; // 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;
+ /**
+ * 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,
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/translations.ts
index 52b4a5d4ba622..ad3cd89604030 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/translations.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/translations.ts
@@ -21,3 +21,10 @@ 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',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx
index e20a2f2c70e4f..0ffb0ac7574a6 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/use_columns.tsx
@@ -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 | EuiTableActionsColumnType;
@@ -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) => ,
+ render: (_, rule: Rule) => {
+ const snoozeSettings = rulesSnoozeSettings.data[rule.id];
+ const { isFetching, isError } = rulesSnoozeSettings;
+
+ return (
+
+ );
+ },
width: '100px',
sortable: false,
}),
- []
+ [rulesSnoozeSettings]
);
};