mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[RAM][SecuritySolution] Simplify rules list notify badge (#155500)
**Relates to:** https://github.com/elastic/security-team/issues/5308 ## Summary This PR make improvements and simplifications to `RulesListNotifyBadge` component exported by `triggers_actions_ui` plugin spotted during [adoption](https://github.com/elastic/security-team/issues/5308) of this component in Security Solution. ## Details The list of the changes - Mixed controlled and uncontrolled state has been resolved in favour of controlled state. It means an external code is responsible to fetch and provide rule snooze settings to the component. - Loading state management has been moved inside the component. The only way to to set the component in loading state it to provide `undefined` for `snoozeSettings` input prop. - Popover's open/close state management has been moved inside the component. I haven't noticed any problems in tables (Stack Management Rules and Security Solution Rules) if there are multiple rules are shown with `RulesListNotifyBadge` rendered. In attempt to open another snooze popover a previous one closes automatically. - `rule` input prop has been renamed to `snoozeSettings`. - `isEditable` field has been moved to a separate `disabled` prop. `disabled` can accept a string which is considered as a disabled reason and displayed in a tooltip. It's quite helpful to display for example fetching errors. - `onRuleChanged` handler can return a promise so internal loading state persists until this promise resolved. ### Checklist - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
This commit is contained in:
parent
6591da49df
commit
a61c63dc07
33 changed files with 353 additions and 424 deletions
|
@ -6,46 +6,15 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
TriggersAndActionsUIPublicPluginStart,
|
||||
RuleTableItem,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import type { RuleSnoozeSettings } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
interface SandboxProps {
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
}
|
||||
|
||||
const mockRule: RuleTableItem = {
|
||||
id: '1',
|
||||
enabled: true,
|
||||
name: 'test rule',
|
||||
tags: ['tag1'],
|
||||
ruleTypeId: 'test_rule_type',
|
||||
consumer: 'rules',
|
||||
schedule: { interval: '5d' },
|
||||
actions: [
|
||||
{ id: 'test', actionTypeId: 'the_connector', group: 'rule', params: { message: 'test' } },
|
||||
],
|
||||
params: { name: 'test rule type name' },
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
notifyWhen: 'onActiveAlert',
|
||||
const mockSnoozeSettings: RuleSnoozeSettings = {
|
||||
muteAll: true,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'active',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
},
|
||||
actionsCount: 1,
|
||||
index: 0,
|
||||
ruleType: 'Test Rule Type',
|
||||
isEditable: true,
|
||||
enabledInLicense: true,
|
||||
revision: 0,
|
||||
};
|
||||
|
||||
export const RulesListNotifyBadgeSandbox = ({ triggersActionsUi }: SandboxProps) => {
|
||||
|
@ -53,8 +22,8 @@ export const RulesListNotifyBadgeSandbox = ({ triggersActionsUi }: SandboxProps)
|
|||
return (
|
||||
<div style={{ flex: 1 }}>
|
||||
<RulesListNotifyBadge
|
||||
rule={mockRule}
|
||||
isLoading={false}
|
||||
ruleId="1"
|
||||
snoozeSettings={mockSnoozeSettings}
|
||||
onRuleChanged={() => Promise.resolve()}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -848,7 +848,7 @@ describe('Detections Rules API', () => {
|
|||
mute_all: false,
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
id: '2',
|
||||
mute_all: false,
|
||||
active_snoozes: [],
|
||||
is_snoozed_until: '2023-04-24T19:31:46.765Z',
|
||||
|
@ -856,21 +856,19 @@ describe('Detections Rules API', () => {
|
|||
],
|
||||
});
|
||||
|
||||
const result = await fetchRulesSnoozeSettings({ ids: ['id1'] });
|
||||
const result = await fetchRulesSnoozeSettings({ ids: ['1', '2'] });
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: '1',
|
||||
expect(result).toEqual({
|
||||
'1': {
|
||||
muteAll: false,
|
||||
activeSnoozes: [],
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
'2': {
|
||||
muteAll: false,
|
||||
activeSnoozes: [],
|
||||
isSnoozedUntil: new Date('2023-04-24T19:31:46.765Z'),
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -57,8 +57,8 @@ import type {
|
|||
PrePackagedRulesStatusResponse,
|
||||
PreviewRulesProps,
|
||||
Rule,
|
||||
RuleSnoozeSettings,
|
||||
RulesSnoozeSettingsBatchResponse,
|
||||
RulesSnoozeSettingsMap,
|
||||
UpdateRulesProps,
|
||||
} from '../logic/types';
|
||||
import { convertRulesFilterToKQL } from '../logic/utils';
|
||||
|
@ -198,7 +198,7 @@ export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise<Rul
|
|||
export const fetchRulesSnoozeSettings = async ({
|
||||
ids,
|
||||
signal,
|
||||
}: FetchRuleSnoozingProps): Promise<RuleSnoozeSettings[]> => {
|
||||
}: FetchRuleSnoozingProps): Promise<RulesSnoozeSettingsMap> => {
|
||||
const response = await KibanaServices.get().http.fetch<RulesSnoozeSettingsBatchResponse>(
|
||||
INTERNAL_ALERTING_API_FIND_RULES_PATH,
|
||||
{
|
||||
|
@ -212,15 +212,18 @@ export const fetchRulesSnoozeSettings = async ({
|
|||
}
|
||||
);
|
||||
|
||||
return response.data?.map((snoozeSettings) => ({
|
||||
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,
|
||||
}));
|
||||
return response.data?.reduce((result, { id, ...snoozeSettings }) => {
|
||||
result[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,
|
||||
};
|
||||
|
||||
return result;
|
||||
}, {} as RulesSnoozeSettingsMap);
|
||||
};
|
||||
|
||||
export interface BulkActionSummary {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { INTERNAL_ALERTING_API_FIND_RULES_PATH } from '@kbn/alerting-plugin/comm
|
|||
import type { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback } from 'react';
|
||||
import type { RuleSnoozeSettings } from '../../logic';
|
||||
import type { RulesSnoozeSettingsMap } from '../../logic';
|
||||
import { fetchRulesSnoozeSettings } from '../api';
|
||||
import { DEFAULT_QUERY_OPTIONS } from './constants';
|
||||
|
||||
|
@ -25,7 +25,7 @@ const FETCH_RULE_SNOOZE_SETTINGS_QUERY_KEY = ['GET', INTERNAL_ALERTING_API_FIND_
|
|||
*/
|
||||
export const useFetchRulesSnoozeSettings = (
|
||||
ids: string[],
|
||||
queryOptions?: UseQueryOptions<RuleSnoozeSettings[], Error, RuleSnoozeSettings[], string[]>
|
||||
queryOptions?: UseQueryOptions<RulesSnoozeSettingsMap, Error, RulesSnoozeSettingsMap, string[]>
|
||||
) => {
|
||||
return useQuery(
|
||||
[...FETCH_RULE_SNOOZE_SETTINGS_QUERY_KEY, ...ids],
|
||||
|
@ -51,7 +51,7 @@ export const useInvalidateFetchRulesSnoozeSettingsQuery = () => {
|
|||
* Invalidate all queries that start with FIND_RULES_QUERY_KEY. This
|
||||
* includes the in-memory query cache and paged query cache.
|
||||
*/
|
||||
queryClient.invalidateQueries(FETCH_RULE_SNOOZE_SETTINGS_QUERY_KEY, {
|
||||
return queryClient.invalidateQueries(FETCH_RULE_SNOOZE_SETTINGS_QUERY_KEY, {
|
||||
refetchType: 'active',
|
||||
});
|
||||
}, [queryClient]);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import type { RuleObjectId } from '../../../../../common/detection_engine/rule_schema';
|
||||
import { useUserData } from '../../../../detections/components/user_info';
|
||||
import { hasUserCRUDPermission } from '../../../../common/utils/privileges';
|
||||
|
@ -31,20 +31,6 @@ export function RuleSnoozeBadge({
|
|||
const [{ canUserCRUD }] = useUserData();
|
||||
const hasCRUDPermissions = hasUserCRUDPermission(canUserCRUD);
|
||||
const invalidateFetchRuleSnoozeSettings = useInvalidateFetchRulesSnoozeSettingsQuery();
|
||||
const isLoading = !snoozeSettings;
|
||||
const rule = useMemo(
|
||||
() => ({
|
||||
id: snoozeSettings?.id ?? '',
|
||||
muteAll: snoozeSettings?.muteAll ?? false,
|
||||
activeSnoozes: snoozeSettings?.activeSnoozes ?? [],
|
||||
isSnoozedUntil: snoozeSettings?.isSnoozedUntil
|
||||
? new Date(snoozeSettings.isSnoozedUntil)
|
||||
: undefined,
|
||||
snoozeSchedule: snoozeSettings?.snoozeSchedule,
|
||||
isEditable: hasCRUDPermissions,
|
||||
}),
|
||||
[snoozeSettings, hasCRUDPermissions]
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
@ -56,8 +42,10 @@ export function RuleSnoozeBadge({
|
|||
|
||||
return (
|
||||
<RulesListNotifyBadge
|
||||
rule={rule}
|
||||
isLoading={isLoading}
|
||||
ruleId={ruleId}
|
||||
snoozeSettings={snoozeSettings}
|
||||
loading={!snoozeSettings}
|
||||
disabled={!hasCRUDPermissions}
|
||||
showTooltipInline={showTooltipInline}
|
||||
onRuleChanged={invalidateFetchRuleSnoozeSettings}
|
||||
/>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RuleSnoozeSettings } from '../../logic';
|
||||
import type { RuleSnoozeSettings } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import { useFetchRulesSnoozeSettings } from '../../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';
|
||||
|
@ -26,7 +26,7 @@ export function useRuleSnoozeSettings(id: string): UseRuleSnoozeSettingsResult {
|
|||
} = useFetchRulesSnoozeSettings([id], {
|
||||
enabled: !rulesTableSnoozeSettings?.data[id] && !rulesTableSnoozeSettings?.isFetching,
|
||||
});
|
||||
const snoozeSettings = rulesTableSnoozeSettings?.data[id] ?? rulesSnoozeSettings?.[0];
|
||||
const snoozeSettings = rulesTableSnoozeSettings?.data[id] ?? rulesSnoozeSettings?.[id];
|
||||
const isFetching = rulesTableSnoozeSettings?.isFetching || isSingleSnoozeSettingsFetching;
|
||||
const isError = rulesTableSnoozeSettings?.isError || isSingleSnoozeSettingsError;
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
type,
|
||||
} from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import type { NamespaceType } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import type { RuleSnoozeSettings } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
|
||||
import { PositiveInteger } from '@kbn/securitysolution-io-ts-types';
|
||||
import type { WarningSchema } from '../../../../common/detection_engine/schemas/response';
|
||||
|
@ -218,15 +219,13 @@ export interface FetchRulesProps {
|
|||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export interface RuleSnoozeSettings {
|
||||
id: string;
|
||||
muteAll: boolean;
|
||||
snoozeSchedule?: RuleSnooze;
|
||||
activeSnoozes?: string[];
|
||||
isSnoozedUntil?: Date;
|
||||
}
|
||||
// Rule snooze settings map keyed by rule SO's id (not ruleId) and valued by rule snooze settings
|
||||
export type RulesSnoozeSettingsMap = Record<string, RuleSnoozeSettings>;
|
||||
|
||||
interface RuleSnoozeSettingsResponse {
|
||||
/**
|
||||
* Rule's SO id
|
||||
*/
|
||||
id: string;
|
||||
mute_all: boolean;
|
||||
snooze_schedule?: RuleSnooze;
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useUiSetting$ } from '../../../../../common/lib/kibana';
|
||||
import type { Rule, RuleSnoozeSettings } from '../../../../rule_management/logic/types';
|
||||
import type { Rule, RulesSnoozeSettingsMap } from '../../../../rule_management/logic';
|
||||
import { useFindRules } from '../../../../rule_management/logic/use_find_rules';
|
||||
import { useFetchRulesSnoozeSettings } from '../../../../rule_management/api/hooks/use_fetch_rules_snooze_settings';
|
||||
import type { RulesTableState } from './rules_table_context';
|
||||
|
@ -34,7 +34,7 @@ function renderUseRulesTableContext({
|
|||
savedState,
|
||||
}: {
|
||||
rules?: Rule[] | Error;
|
||||
rulesSnoozeSettings?: RuleSnoozeSettings[] | Error;
|
||||
rulesSnoozeSettings?: RulesSnoozeSettingsMap | Error;
|
||||
savedState?: ReturnType<typeof useRulesTableSavedState>;
|
||||
}): RulesTableState {
|
||||
(useFindRules as jest.Mock).mockReturnValue({
|
||||
|
@ -189,10 +189,10 @@ describe('RulesTableContextProvider', () => {
|
|||
{ id: '1', name: 'rule 1' },
|
||||
{ id: '2', name: 'rule 2' },
|
||||
] as Rule[],
|
||||
rulesSnoozeSettings: [
|
||||
{ id: '1', muteAll: true, snoozeSchedule: [] },
|
||||
{ id: '2', muteAll: false, snoozeSchedule: [] },
|
||||
],
|
||||
rulesSnoozeSettings: {
|
||||
'1': { muteAll: true, snoozeSchedule: [] },
|
||||
'2': { muteAll: false, snoozeSchedule: [] },
|
||||
},
|
||||
});
|
||||
|
||||
expect(state.rules).toEqual([
|
||||
|
@ -215,20 +215,18 @@ describe('RulesTableContextProvider', () => {
|
|||
{ id: '1', name: 'rule 1' },
|
||||
{ id: '2', name: 'rule 2' },
|
||||
] as Rule[],
|
||||
rulesSnoozeSettings: [
|
||||
{ id: '1', muteAll: true, snoozeSchedule: [] },
|
||||
{ id: '2', muteAll: false, snoozeSchedule: [] },
|
||||
],
|
||||
rulesSnoozeSettings: {
|
||||
'1': { muteAll: true, snoozeSchedule: [] },
|
||||
'2': { muteAll: false, snoozeSchedule: [] },
|
||||
},
|
||||
});
|
||||
|
||||
expect(state.rulesSnoozeSettings.data).toEqual({
|
||||
'1': {
|
||||
id: '1',
|
||||
muteAll: true,
|
||||
snoozeSchedule: [],
|
||||
},
|
||||
'2': {
|
||||
id: '2',
|
||||
muteAll: false,
|
||||
snoozeSchedule: [],
|
||||
},
|
||||
|
|
|
@ -25,7 +25,7 @@ import type {
|
|||
FilterOptions,
|
||||
PaginationOptions,
|
||||
Rule,
|
||||
RuleSnoozeSettings,
|
||||
RulesSnoozeSettingsMap,
|
||||
SortingOptions,
|
||||
} from '../../../../rule_management/logic/types';
|
||||
import { useFindRules } from '../../../../rule_management/logic/use_find_rules';
|
||||
|
@ -39,11 +39,11 @@ import {
|
|||
import { RuleSource } from './rules_table_saved_state';
|
||||
import { useRulesTableSavedState } from './use_rules_table_saved_state';
|
||||
|
||||
interface RulesSnoozeSettings {
|
||||
interface RulesSnoozeSettingsState {
|
||||
/**
|
||||
* A map object using rule SO's id (not ruleId) as keys and snooze settings as values
|
||||
*/
|
||||
data: Record<string, RuleSnoozeSettings>;
|
||||
data: RulesSnoozeSettingsMap;
|
||||
/**
|
||||
* Sets to true during the first data loading
|
||||
*/
|
||||
|
@ -127,7 +127,7 @@ export interface RulesTableState {
|
|||
/**
|
||||
* Rules snooze settings for the current rules
|
||||
*/
|
||||
rulesSnoozeSettings: RulesSnoozeSettings;
|
||||
rulesSnoozeSettings: RulesSnoozeSettingsState;
|
||||
}
|
||||
|
||||
export type LoadingRuleAction =
|
||||
|
@ -298,7 +298,7 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide
|
|||
|
||||
// Fetch rules snooze settings
|
||||
const {
|
||||
data: rulesSnoozeSettings,
|
||||
data: rulesSnoozeSettingsMap,
|
||||
isLoading: isSnoozeSettingsLoading,
|
||||
isFetching: isSnoozeSettingsFetching,
|
||||
isError: isSnoozeSettingsFetchError,
|
||||
|
@ -346,19 +346,12 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide
|
|||
]
|
||||
);
|
||||
|
||||
const providerValue = useMemo(() => {
|
||||
const rulesSnoozeSettingsMap =
|
||||
rulesSnoozeSettings?.reduce((map, snoozeSettings) => {
|
||||
map[snoozeSettings.id] = snoozeSettings;
|
||||
|
||||
return map;
|
||||
}, {} as Record<string, RuleSnoozeSettings>) ?? {};
|
||||
|
||||
return {
|
||||
const providerValue = useMemo(
|
||||
() => ({
|
||||
state: {
|
||||
rules,
|
||||
rulesSnoozeSettings: {
|
||||
data: rulesSnoozeSettingsMap,
|
||||
data: rulesSnoozeSettingsMap ?? {},
|
||||
isLoading: isSnoozeSettingsLoading,
|
||||
isFetching: isSnoozeSettingsFetching,
|
||||
isError: isSnoozeSettingsFetchError,
|
||||
|
@ -389,32 +382,33 @@ export const RulesTableContextProvider = ({ children }: RulesTableContextProvide
|
|||
}),
|
||||
},
|
||||
actions,
|
||||
};
|
||||
}, [
|
||||
rules,
|
||||
rulesSnoozeSettings,
|
||||
isSnoozeSettingsLoading,
|
||||
isSnoozeSettingsFetching,
|
||||
isSnoozeSettingsFetchError,
|
||||
page,
|
||||
perPage,
|
||||
total,
|
||||
filterOptions,
|
||||
isPreflightInProgress,
|
||||
isActionInProgress,
|
||||
isAllSelected,
|
||||
isFetched,
|
||||
isFetching,
|
||||
isLoading,
|
||||
isRefetching,
|
||||
isRefreshOn,
|
||||
dataUpdatedAt,
|
||||
loadingRules.ids,
|
||||
loadingRules.action,
|
||||
selectedRuleIds,
|
||||
sortingOptions,
|
||||
actions,
|
||||
]);
|
||||
}),
|
||||
[
|
||||
rules,
|
||||
rulesSnoozeSettingsMap,
|
||||
isSnoozeSettingsLoading,
|
||||
isSnoozeSettingsFetching,
|
||||
isSnoozeSettingsFetchError,
|
||||
page,
|
||||
perPage,
|
||||
total,
|
||||
filterOptions,
|
||||
isPreflightInProgress,
|
||||
isActionInProgress,
|
||||
isAllSelected,
|
||||
isFetched,
|
||||
isFetching,
|
||||
isLoading,
|
||||
isRefetching,
|
||||
isRefreshOn,
|
||||
dataUpdatedAt,
|
||||
loadingRules.ids,
|
||||
loadingRules.action,
|
||||
selectedRuleIds,
|
||||
sortingOptions,
|
||||
actions,
|
||||
]
|
||||
);
|
||||
|
||||
return <RulesTableContext.Provider value={providerValue}>{children}</RulesTableContext.Provider>;
|
||||
};
|
||||
|
|
|
@ -24,7 +24,14 @@ export const LogsList = ({
|
|||
'xl'
|
||||
)({
|
||||
ruleId: '*',
|
||||
refreshToken: 0,
|
||||
refreshToken: {
|
||||
resolve: () => {
|
||||
/* noop */
|
||||
},
|
||||
reject: () => {
|
||||
/* noop */
|
||||
},
|
||||
},
|
||||
initialPageSize: 50,
|
||||
hasRuleNames: true,
|
||||
hasAllSpaceSwitch: true,
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '../../common/components/with_bulk_rule_api_operations';
|
||||
import './rule.scss';
|
||||
import type { RuleEventLogListProps } from './rule_event_log_list';
|
||||
import { AlertListItem } from './types';
|
||||
import { AlertListItem, RefreshToken } from './types';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
import { suspendedComponentWithProps } from '../../../lib/suspended_component_with_props';
|
||||
import {
|
||||
|
@ -41,7 +41,7 @@ type RuleProps = {
|
|||
readOnly: boolean;
|
||||
ruleSummary: RuleSummary;
|
||||
requestRefresh: () => Promise<void>;
|
||||
refreshToken?: number;
|
||||
refreshToken?: RefreshToken;
|
||||
numberOfExecutions: number;
|
||||
onChangeDuration: (length: number) => void;
|
||||
durationEpoch?: number;
|
||||
|
|
|
@ -23,10 +23,11 @@ import {
|
|||
import { IExecutionLog } from '@kbn/alerting-plugin/common';
|
||||
import { RuleErrorLogWithApi } from './rule_error_log';
|
||||
import { RuleActionErrorBadge } from './rule_action_error_badge';
|
||||
import { RefreshToken } from './types';
|
||||
|
||||
export interface RuleActionErrorLogFlyoutProps {
|
||||
runLog: IExecutionLog;
|
||||
refreshToken?: number;
|
||||
refreshToken?: RefreshToken;
|
||||
onClose: () => void;
|
||||
activeSpaceId?: string;
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ const mockRuleApis = {
|
|||
muteRule: jest.fn(),
|
||||
unmuteRule: jest.fn(),
|
||||
requestRefresh: jest.fn(),
|
||||
refreshToken: Date.now(),
|
||||
refreshToken: { resolve: jest.fn(), reject: jest.fn() },
|
||||
snoozeRule: jest.fn(),
|
||||
unsnoozeRule: jest.fn(),
|
||||
bulkEnableRules: jest.fn(),
|
||||
|
|
|
@ -68,13 +68,14 @@ import {
|
|||
MULTIPLE_RULE_TITLE,
|
||||
} from '../../rules_list/translations';
|
||||
import { useBulkOperationToast } from '../../../hooks/use_bulk_operation_toast';
|
||||
import { RefreshToken } from './types';
|
||||
|
||||
export type RuleDetailsProps = {
|
||||
rule: Rule;
|
||||
ruleType: RuleType;
|
||||
actionTypes: ActionType[];
|
||||
requestRefresh: () => Promise<void>;
|
||||
refreshToken?: number;
|
||||
refreshToken?: RefreshToken;
|
||||
} & Pick<
|
||||
BulkOperationsComponentOpts,
|
||||
'bulkDisableRules' | 'bulkEnableRules' | 'bulkDeleteRules' | 'snoozeRule' | 'unsnoozeRule'
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { ToastsApi } from '@kbn/core/public';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
|
@ -49,18 +49,38 @@ export const RuleDetailsRoute: React.FunctionComponent<RuleDetailsRouteProps> =
|
|||
const [rule, setRule] = useState<ResolvedRule | null>(null);
|
||||
const [ruleType, setRuleType] = useState<RuleType | null>(null);
|
||||
const [actionTypes, setActionTypes] = useState<ActionType[] | null>(null);
|
||||
const [refreshToken, requestRefresh] = React.useState<number>();
|
||||
const [refreshToken, setRefreshToken] = useState<{
|
||||
resolve: () => void;
|
||||
reject: () => void;
|
||||
}>();
|
||||
const requestRefresh = useCallback(
|
||||
() =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
setRefreshToken({
|
||||
resolve,
|
||||
reject,
|
||||
});
|
||||
}),
|
||||
[setRefreshToken]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getRuleData(
|
||||
ruleId,
|
||||
loadRuleTypes,
|
||||
resolveRule,
|
||||
loadActionTypes,
|
||||
setRule,
|
||||
setRuleType,
|
||||
setActionTypes,
|
||||
toasts
|
||||
);
|
||||
const loadData = async () => {
|
||||
await getRuleData(
|
||||
ruleId,
|
||||
loadRuleTypes,
|
||||
resolveRule,
|
||||
loadActionTypes,
|
||||
setRule,
|
||||
setRuleType,
|
||||
setActionTypes,
|
||||
toasts
|
||||
);
|
||||
|
||||
refreshToken?.resolve();
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, [ruleId, http, loadActionTypes, loadRuleTypes, resolveRule, toasts, refreshToken]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -117,7 +137,7 @@ export const RuleDetailsRoute: React.FunctionComponent<RuleDetailsRouteProps> =
|
|||
rule={rule}
|
||||
ruleType={ruleType}
|
||||
actionTypes={actionTypes}
|
||||
requestRefresh={async () => requestRefresh(Date.now())}
|
||||
requestRefresh={requestRefresh}
|
||||
refreshToken={refreshToken}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
withBulkRuleOperations,
|
||||
} from '../../common/components/with_bulk_rule_api_operations';
|
||||
import { EventLogListCellRenderer } from '../../common/components/event_log';
|
||||
import { RefreshToken } from './types';
|
||||
|
||||
const getParsedDate = (date: string) => {
|
||||
if (date.includes('now')) {
|
||||
|
@ -62,7 +63,7 @@ const MAX_RESULTS = 1000;
|
|||
export type RuleErrorLogProps = {
|
||||
ruleId: string;
|
||||
runId?: string;
|
||||
refreshToken?: number;
|
||||
refreshToken?: RefreshToken;
|
||||
spaceId?: string;
|
||||
logFromDifferentSpace?: boolean;
|
||||
requestRefresh?: () => Promise<void>;
|
||||
|
|
|
@ -12,6 +12,7 @@ import { RuleExecutionSummaryAndChartWithApi } from './rule_execution_summary_an
|
|||
import { RuleSummary, RuleType } from '../../../../types';
|
||||
import { ComponentOpts as RuleApis } from '../../common/components/with_bulk_rule_api_operations';
|
||||
import { RuleEventLogListTableWithApi } from './rule_event_log_list_table';
|
||||
import { RefreshToken } from './types';
|
||||
|
||||
const RULE_EVENT_LOG_LIST_STORAGE_KEY = 'xpack.triggersActionsUI.ruleEventLogList.initialColumns';
|
||||
|
||||
|
@ -23,7 +24,7 @@ export interface RuleEventLogListCommonProps {
|
|||
ruleId: string;
|
||||
ruleType: RuleType;
|
||||
localStorageKey?: string;
|
||||
refreshToken?: number;
|
||||
refreshToken?: RefreshToken;
|
||||
requestRefresh?: () => Promise<void>;
|
||||
loadExecutionLogAggregations?: RuleApis['loadExecutionLogAggregations'];
|
||||
fetchRuleSummary?: boolean;
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { EventLogListStatus, EventLogStat } from '../../common/components/event_log';
|
||||
import { RefreshToken } from './types';
|
||||
|
||||
const getParsedDate = (date: string) => {
|
||||
if (date.includes('now')) {
|
||||
|
@ -59,7 +60,7 @@ export type RuleEventLogListKPIProps = {
|
|||
dateEnd: string;
|
||||
outcomeFilter?: string[];
|
||||
message?: string;
|
||||
refreshToken?: number;
|
||||
refreshToken?: RefreshToken;
|
||||
namespaces?: Array<string | undefined>;
|
||||
} & Pick<RuleApis, 'loadExecutionKPIAggregations' | 'loadGlobalExecutionKPIAggregations'>;
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ import {
|
|||
} from '../../common/components/with_bulk_rule_api_operations';
|
||||
import { useMultipleSpaces } from '../../../hooks/use_multiple_spaces';
|
||||
import { RulesSettingsLink } from '../../../components/rules_setting/rules_settings_link';
|
||||
import { RefreshToken } from './types';
|
||||
|
||||
const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => <>{children}</>;
|
||||
|
||||
|
@ -102,7 +103,7 @@ export type RuleEventLogListOptions = 'stackManagement' | 'default';
|
|||
export type RuleEventLogListCommonProps = {
|
||||
ruleId: string;
|
||||
localStorageKey?: string;
|
||||
refreshToken?: number;
|
||||
refreshToken?: RefreshToken;
|
||||
initialPageSize?: number;
|
||||
// Duplicating these properties is extremely silly but it's the only way to get Jest to cooperate with the way this component is structured
|
||||
overrideLoadExecutionLogAggregations?: RuleApis['loadExecutionLogAggregations'];
|
||||
|
@ -142,7 +143,7 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
const [search, setSearch] = useState<string>('');
|
||||
const [isFlyoutOpen, setIsFlyoutOpen] = useState<boolean>(false);
|
||||
const [selectedRunLog, setSelectedRunLog] = useState<IExecutionLog | undefined>();
|
||||
const [internalRefreshToken, setInternalRefreshToken] = useState<number | undefined>(
|
||||
const [internalRefreshToken, setInternalRefreshToken] = useState<RefreshToken | undefined>(
|
||||
refreshToken
|
||||
);
|
||||
const [showFromAllSpaces, setShowFromAllSpaces] = useState(false);
|
||||
|
@ -298,7 +299,14 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
);
|
||||
|
||||
const onRefresh = () => {
|
||||
setInternalRefreshToken(Date.now());
|
||||
setInternalRefreshToken({
|
||||
resolve: () => {
|
||||
/* noop */
|
||||
},
|
||||
reject: () => {
|
||||
/* noop */
|
||||
},
|
||||
});
|
||||
loadEventLogs();
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
ComponentOpts as RuleApis,
|
||||
withBulkRuleOperations,
|
||||
} from '../../common/components/with_bulk_rule_api_operations';
|
||||
import { RefreshToken } from './types';
|
||||
|
||||
export const DEFAULT_NUMBER_OF_EXECUTIONS = 60;
|
||||
|
||||
|
@ -29,7 +30,7 @@ type RuleExecutionSummaryAndChartProps = {
|
|||
ruleSummary?: RuleSummary;
|
||||
numberOfExecutions?: number;
|
||||
isLoadingRuleSummary?: boolean;
|
||||
refreshToken?: number;
|
||||
refreshToken?: RefreshToken;
|
||||
onChangeDuration?: (duration: number) => void;
|
||||
requestRefresh?: () => Promise<void>;
|
||||
fetchRuleSummary?: boolean;
|
||||
|
|
|
@ -16,13 +16,14 @@ import {
|
|||
import { RuleWithApi as Rules } from './rule';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner';
|
||||
import { RefreshToken } from './types';
|
||||
|
||||
type WithRuleSummaryProps = {
|
||||
rule: Rule;
|
||||
ruleType: RuleType;
|
||||
readOnly: boolean;
|
||||
requestRefresh: () => Promise<void>;
|
||||
refreshToken?: number;
|
||||
refreshToken?: RefreshToken;
|
||||
} & Pick<RuleApis, 'loadRuleSummary'>;
|
||||
|
||||
export const RuleRoute: React.FunctionComponent<WithRuleSummaryProps> = ({
|
||||
|
|
|
@ -58,12 +58,8 @@ export const RuleStatusPanel: React.FC<ComponentOpts> = ({
|
|||
statusMessage,
|
||||
loadExecutionLogAggregations,
|
||||
}) => {
|
||||
const [isSnoozeLoading, setIsSnoozeLoading] = useState(false);
|
||||
const [isSnoozeOpen, setIsSnoozeOpen] = useState(false);
|
||||
const [lastNumberOfExecutions, setLastNumberOfExecutions] = useState<number | null>(null);
|
||||
|
||||
const openSnooze = useCallback(() => setIsSnoozeOpen(true), [setIsSnoozeOpen]);
|
||||
const closeSnooze = useCallback(() => setIsSnoozeOpen(false), [setIsSnoozeOpen]);
|
||||
const onSnoozeRule = useCallback(
|
||||
(snoozeSchedule) => snoozeRule(rule, snoozeSchedule),
|
||||
[rule, snoozeRule]
|
||||
|
@ -187,12 +183,9 @@ export const RuleStatusPanel: React.FC<ComponentOpts> = ({
|
|||
<EuiHorizontalRule margin="none" />
|
||||
<EuiPanel hasShadow={false}>
|
||||
<RulesListNotifyBadge
|
||||
rule={{ ...rule, isEditable }}
|
||||
isOpen={isSnoozeOpen}
|
||||
isLoading={isSnoozeLoading}
|
||||
onLoading={setIsSnoozeLoading}
|
||||
onClick={openSnooze}
|
||||
onClose={closeSnooze}
|
||||
snoozeSettings={rule}
|
||||
loading={!rule}
|
||||
disabled={!isEditable}
|
||||
onRuleChanged={requestRefresh}
|
||||
snoozeRule={onSnoozeRule}
|
||||
unsnoozeRule={onUnsnoozeRule}
|
||||
|
|
|
@ -16,3 +16,8 @@ export interface AlertListItem {
|
|||
flapping: boolean;
|
||||
maintenanceWindowIds?: string[];
|
||||
}
|
||||
|
||||
export interface RefreshToken {
|
||||
resolve: () => void;
|
||||
reject: () => void;
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ export const CollapsedItemActions: React.FunctionComponent<ComponentOpts> = ({
|
|||
try {
|
||||
onLoading(true);
|
||||
await snoozeRule(item, snoozeSchedule);
|
||||
onRuleChanged();
|
||||
await onRuleChanged();
|
||||
toasts.addSuccess(SNOOZE_SUCCESS_MESSAGE);
|
||||
} catch (e) {
|
||||
toasts.addDanger(SNOOZE_FAILED_MESSAGE);
|
||||
|
@ -101,7 +101,7 @@ export const CollapsedItemActions: React.FunctionComponent<ComponentOpts> = ({
|
|||
try {
|
||||
onLoading(true);
|
||||
await unsnoozeRule(item, scheduleIds);
|
||||
onRuleChanged();
|
||||
await onRuleChanged();
|
||||
toasts.addSuccess(UNSNOOZE_SUCCESS_MESSAGE);
|
||||
} catch (e) {
|
||||
toasts.addDanger(SNOOZE_FAILED_MESSAGE);
|
||||
|
|
|
@ -9,73 +9,27 @@ import { EuiButtonIcon, EuiButton } from '@elastic/eui';
|
|||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import moment from 'moment';
|
||||
|
||||
import { RuleTableItem } from '../../../../../types';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { RulesListNotifyBadge } from './notify_badge';
|
||||
|
||||
jest.mock('../../../../../common/lib/kibana');
|
||||
|
||||
const onClick = jest.fn();
|
||||
const onClose = jest.fn();
|
||||
const onLoading = jest.fn();
|
||||
const onRuleChanged = jest.fn();
|
||||
const snoozeRule = jest.fn();
|
||||
const unsnoozeRule = jest.fn();
|
||||
|
||||
const getRule = (overrides = {}): RuleTableItem => ({
|
||||
id: '1',
|
||||
enabled: true,
|
||||
name: 'test rule',
|
||||
tags: ['tag1'],
|
||||
ruleTypeId: 'test_rule_type',
|
||||
consumer: 'rules',
|
||||
schedule: { interval: '5d' },
|
||||
actions: [
|
||||
{ id: 'test', actionTypeId: 'the_connector', group: 'rule', params: { message: 'test' } },
|
||||
],
|
||||
params: { name: 'test rule type name' },
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
notifyWhen: 'onActiveAlert',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'active',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
},
|
||||
actionsCount: 1,
|
||||
index: 0,
|
||||
ruleType: 'Test Rule Type',
|
||||
isEditable: true,
|
||||
enabledInLicense: true,
|
||||
revision: 0,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('RulesListNotifyBadge', () => {
|
||||
const onRuleChanged = jest.fn();
|
||||
const snoozeRule = jest.fn();
|
||||
const unsnoozeRule = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders the notify badge correctly', async () => {
|
||||
jest.useFakeTimers().setSystemTime(moment('1990-01-01').toDate());
|
||||
|
||||
it('renders an unsnoozed badge', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<RulesListNotifyBadge
|
||||
rule={getRule({
|
||||
snoozeSettings={{
|
||||
isSnoozedUntil: null,
|
||||
muteAll: false,
|
||||
})}
|
||||
isLoading={false}
|
||||
isOpen={false}
|
||||
onLoading={onLoading}
|
||||
onClick={onClick}
|
||||
onClose={onClose}
|
||||
}}
|
||||
onRuleChanged={onRuleChanged}
|
||||
snoozeRule={snoozeRule}
|
||||
unsnoozeRule={unsnoozeRule}
|
||||
|
@ -85,26 +39,46 @@ describe('RulesListNotifyBadge', () => {
|
|||
// Rule without snooze
|
||||
const badge = wrapper.find(EuiButtonIcon);
|
||||
expect(badge.first().props().iconType).toEqual('bell');
|
||||
});
|
||||
|
||||
it('renders a snoozed badge', () => {
|
||||
jest.useFakeTimers().setSystemTime(moment('1990-01-01').toDate());
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<RulesListNotifyBadge
|
||||
snoozeSettings={{
|
||||
muteAll: false,
|
||||
isSnoozedUntil: moment('1990-02-01').toDate(),
|
||||
}}
|
||||
onRuleChanged={onRuleChanged}
|
||||
snoozeRule={snoozeRule}
|
||||
unsnoozeRule={unsnoozeRule}
|
||||
/>
|
||||
);
|
||||
|
||||
// Rule with snooze
|
||||
wrapper.setProps({
|
||||
rule: getRule({
|
||||
isSnoozedUntil: moment('1990-02-01').format(),
|
||||
}),
|
||||
});
|
||||
const snoozeBadge = wrapper.find(EuiButton);
|
||||
|
||||
expect(snoozeBadge.first().props().iconType).toEqual('bellSlash');
|
||||
expect(snoozeBadge.text()).toEqual('Feb 1');
|
||||
});
|
||||
|
||||
// Rule with indefinite snooze
|
||||
wrapper.setProps({
|
||||
rule: getRule({
|
||||
isSnoozedUntil: moment('1990-02-01').format(),
|
||||
muteAll: true,
|
||||
}),
|
||||
});
|
||||
it('renders an indefinitely snoozed badge', () => {
|
||||
jest.useFakeTimers().setSystemTime(moment('1990-01-01').toDate());
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
<RulesListNotifyBadge
|
||||
snoozeSettings={{
|
||||
muteAll: true,
|
||||
isSnoozedUntil: moment('1990-02-01').toDate(),
|
||||
}}
|
||||
onRuleChanged={onRuleChanged}
|
||||
snoozeRule={snoozeRule}
|
||||
unsnoozeRule={unsnoozeRule}
|
||||
/>
|
||||
);
|
||||
|
||||
const indefiniteSnoozeBadge = wrapper.find(EuiButtonIcon);
|
||||
|
||||
expect(indefiniteSnoozeBadge.first().props().iconType).toEqual('bellSlash');
|
||||
expect(indefiniteSnoozeBadge.text()).toEqual('');
|
||||
});
|
||||
|
@ -113,24 +87,21 @@ describe('RulesListNotifyBadge', () => {
|
|||
jest.useFakeTimers().setSystemTime(moment('1990-01-01').toDate());
|
||||
const wrapper = mountWithIntl(
|
||||
<RulesListNotifyBadge
|
||||
rule={getRule({
|
||||
isSnoozedUntil: null,
|
||||
snoozeSettings={{
|
||||
muteAll: false,
|
||||
})}
|
||||
isLoading={false}
|
||||
isOpen={true}
|
||||
onLoading={onLoading}
|
||||
onClick={onClick}
|
||||
onClose={onClose}
|
||||
isSnoozedUntil: null,
|
||||
}}
|
||||
onRuleChanged={onRuleChanged}
|
||||
snoozeRule={snoozeRule}
|
||||
unsnoozeRule={unsnoozeRule}
|
||||
/>
|
||||
);
|
||||
|
||||
// Open the popover
|
||||
wrapper.find(EuiButtonIcon).first().simulate('click');
|
||||
|
||||
// Snooze for 1 hour
|
||||
wrapper.find('button[data-test-subj="linkSnooze1h"]').first().simulate('click');
|
||||
expect(onLoading).toHaveBeenCalledWith(true);
|
||||
expect(snoozeRule).toHaveBeenCalledWith({
|
||||
duration: 3600000,
|
||||
id: null,
|
||||
|
@ -146,38 +117,31 @@ describe('RulesListNotifyBadge', () => {
|
|||
});
|
||||
|
||||
expect(onRuleChanged).toHaveBeenCalled();
|
||||
expect(onLoading).toHaveBeenCalledWith(false);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should allow the user to unsnooze rules', async () => {
|
||||
jest.useFakeTimers().setSystemTime(moment('1990-01-01').toDate());
|
||||
const wrapper = mountWithIntl(
|
||||
<RulesListNotifyBadge
|
||||
rule={getRule({
|
||||
snoozeSettings={{
|
||||
muteAll: true,
|
||||
})}
|
||||
isLoading={false}
|
||||
isOpen={true}
|
||||
onLoading={onLoading}
|
||||
onClick={onClick}
|
||||
onClose={onClose}
|
||||
}}
|
||||
onRuleChanged={onRuleChanged}
|
||||
snoozeRule={snoozeRule}
|
||||
unsnoozeRule={unsnoozeRule}
|
||||
/>
|
||||
);
|
||||
|
||||
// Open the popover
|
||||
wrapper.find(EuiButtonIcon).first().simulate('click');
|
||||
|
||||
// Unsnooze
|
||||
wrapper.find('[data-test-subj="ruleSnoozeCancel"] button').simulate('click');
|
||||
expect(onLoading).toHaveBeenCalledWith(true);
|
||||
|
||||
await act(async () => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
||||
expect(unsnoozeRule).toHaveBeenCalled();
|
||||
expect(onLoading).toHaveBeenCalledWith(false);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
EuiButton,
|
||||
|
@ -30,35 +30,38 @@ import {
|
|||
} from './translations';
|
||||
import { RulesListNotifyBadgeProps } from './types';
|
||||
|
||||
export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeProps> = (props) => {
|
||||
const {
|
||||
isLoading = false,
|
||||
rule,
|
||||
isOpen,
|
||||
onClick,
|
||||
onClose,
|
||||
onLoading,
|
||||
onRuleChanged,
|
||||
snoozeRule,
|
||||
unsnoozeRule,
|
||||
showOnHover = false,
|
||||
showTooltipInline = false,
|
||||
} = props;
|
||||
|
||||
const { isSnoozedUntil, muteAll, isEditable } = rule;
|
||||
|
||||
export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeProps> = ({
|
||||
snoozeSettings,
|
||||
loading = false,
|
||||
disabled = false,
|
||||
onRuleChanged,
|
||||
snoozeRule,
|
||||
unsnoozeRule,
|
||||
showOnHover = false,
|
||||
showTooltipInline = false,
|
||||
}) => {
|
||||
const [requestInFlight, setRequestInFlightLoading] = useState(false);
|
||||
const isLoading = loading || requestInFlight;
|
||||
const isDisabled = Boolean(disabled) || !snoozeSettings;
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const openPopover = useCallback(() => setIsPopoverOpen(true), [setIsPopoverOpen]);
|
||||
const closePopover = useCallback(() => setIsPopoverOpen(false), [setIsPopoverOpen]);
|
||||
const isSnoozedUntil = snoozeSettings?.isSnoozedUntil;
|
||||
const muteAll = snoozeSettings?.muteAll ?? false;
|
||||
const isSnoozedIndefinitely = muteAll;
|
||||
const isSnoozed = useMemo(
|
||||
() => (snoozeSettings ? isRuleSnoozed(snoozeSettings) : false),
|
||||
[snoozeSettings]
|
||||
);
|
||||
const nextScheduledSnooze = useMemo(
|
||||
() => (snoozeSettings ? getNextRuleSnoozeSchedule(snoozeSettings) : null),
|
||||
[snoozeSettings]
|
||||
);
|
||||
|
||||
const {
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
||||
const isSnoozed = useMemo(() => {
|
||||
return isRuleSnoozed(rule);
|
||||
}, [rule]);
|
||||
|
||||
const nextScheduledSnooze = useMemo(() => getNextRuleSnoozeSchedule(rule), [rule]);
|
||||
|
||||
const isScheduled = useMemo(() => {
|
||||
return !isSnoozed && Boolean(nextScheduledSnooze);
|
||||
}, [nextScheduledSnooze, isSnoozed]);
|
||||
|
@ -124,18 +127,18 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
|
|||
<EuiButton
|
||||
size="s"
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading || !isEditable}
|
||||
disabled={isLoading || isDisabled}
|
||||
data-test-subj="rulesListNotifyBadge-snoozed"
|
||||
aria-label={OPEN_SNOOZE_PANEL_ARIA_LABEL}
|
||||
minWidth={85}
|
||||
iconType="bellSlash"
|
||||
color="accent"
|
||||
onClick={onClick}
|
||||
onClick={openPopover}
|
||||
>
|
||||
<EuiText size="xs">{formattedSnoozeText}</EuiText>
|
||||
</EuiButton>
|
||||
);
|
||||
}, [formattedSnoozeText, isLoading, isEditable, onClick]);
|
||||
}, [formattedSnoozeText, isLoading, isDisabled, openPopover]);
|
||||
|
||||
const scheduledSnoozeButton = useMemo(() => {
|
||||
// TODO: Implement scheduled snooze button
|
||||
|
@ -143,18 +146,18 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
|
|||
<EuiButton
|
||||
size="s"
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading || !isEditable}
|
||||
disabled={isLoading || isDisabled}
|
||||
data-test-subj="rulesListNotifyBadge-scheduled"
|
||||
minWidth={85}
|
||||
iconType="calendar"
|
||||
color="text"
|
||||
aria-label={OPEN_SNOOZE_PANEL_ARIA_LABEL}
|
||||
onClick={onClick}
|
||||
onClick={openPopover}
|
||||
>
|
||||
<EuiText size="xs">{formattedSnoozeText}</EuiText>
|
||||
</EuiButton>
|
||||
);
|
||||
}, [formattedSnoozeText, isLoading, isEditable, onClick]);
|
||||
}, [formattedSnoozeText, isLoading, isDisabled, openPopover]);
|
||||
|
||||
const unsnoozedButton = useMemo(() => {
|
||||
// This show on hover is needed because we need style sheets to achieve the
|
||||
|
@ -165,32 +168,32 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
|
|||
<EuiButtonIcon
|
||||
size="s"
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading || !isEditable}
|
||||
disabled={isLoading || isDisabled}
|
||||
display={isLoading ? 'base' : 'empty'}
|
||||
data-test-subj="rulesListNotifyBadge-unsnoozed"
|
||||
aria-label={OPEN_SNOOZE_PANEL_ARIA_LABEL}
|
||||
className={isOpen || isLoading ? '' : showOnHoverClass}
|
||||
className={isPopoverOpen || isLoading ? '' : showOnHoverClass}
|
||||
iconType="bell"
|
||||
onClick={onClick}
|
||||
onClick={openPopover}
|
||||
/>
|
||||
);
|
||||
}, [isOpen, isLoading, isEditable, showOnHover, onClick]);
|
||||
}, [isPopoverOpen, isLoading, isDisabled, showOnHover, openPopover]);
|
||||
|
||||
const indefiniteSnoozeButton = useMemo(() => {
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
size="s"
|
||||
isLoading={isLoading}
|
||||
disabled={isLoading || !isEditable}
|
||||
disabled={isLoading || isDisabled}
|
||||
display="base"
|
||||
data-test-subj="rulesListNotifyBadge-snoozedIndefinitely"
|
||||
aria-label={OPEN_SNOOZE_PANEL_ARIA_LABEL}
|
||||
iconType="bellSlash"
|
||||
color="accent"
|
||||
onClick={onClick}
|
||||
onClick={openPopover}
|
||||
/>
|
||||
);
|
||||
}, [isLoading, isEditable, onClick]);
|
||||
}, [isLoading, isDisabled, openPopover]);
|
||||
|
||||
const button = useMemo(() => {
|
||||
if (isScheduled) {
|
||||
|
@ -214,57 +217,55 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
|
|||
]);
|
||||
|
||||
const buttonWithToolTip = useMemo(() => {
|
||||
if (isOpen || showTooltipInline) {
|
||||
return button;
|
||||
}
|
||||
return <EuiToolTip content={snoozeTooltipText}>{button}</EuiToolTip>;
|
||||
}, [isOpen, button, snoozeTooltipText, showTooltipInline]);
|
||||
const tooltipContent =
|
||||
typeof disabled === 'string'
|
||||
? disabled
|
||||
: isPopoverOpen || showTooltipInline
|
||||
? undefined
|
||||
: snoozeTooltipText;
|
||||
|
||||
const onClosePopover = useCallback(() => {
|
||||
onClose();
|
||||
// Set a timeout on closing the scheduler to avoid flicker
|
||||
// setTimeout(onCloseScheduler, 1000);
|
||||
}, [onClose]);
|
||||
return <EuiToolTip content={tooltipContent}>{button}</EuiToolTip>;
|
||||
}, [disabled, isPopoverOpen, button, snoozeTooltipText, showTooltipInline]);
|
||||
|
||||
const onApplySnooze = useCallback(
|
||||
async (schedule: SnoozeSchedule) => {
|
||||
try {
|
||||
onLoading(true);
|
||||
onClosePopover();
|
||||
setRequestInFlightLoading(true);
|
||||
closePopover();
|
||||
await snoozeRule(schedule);
|
||||
onRuleChanged();
|
||||
await onRuleChanged();
|
||||
toasts.addSuccess(SNOOZE_SUCCESS_MESSAGE);
|
||||
} catch (e) {
|
||||
toasts.addDanger(SNOOZE_FAILED_MESSAGE);
|
||||
} finally {
|
||||
onLoading(false);
|
||||
setRequestInFlightLoading(false);
|
||||
}
|
||||
},
|
||||
[onLoading, snoozeRule, onRuleChanged, toasts, onClosePopover]
|
||||
[setRequestInFlightLoading, snoozeRule, onRuleChanged, toasts, closePopover]
|
||||
);
|
||||
|
||||
const onApplyUnsnooze = useCallback(
|
||||
async (scheduleIds?: string[]) => {
|
||||
try {
|
||||
onLoading(true);
|
||||
onClosePopover();
|
||||
setRequestInFlightLoading(true);
|
||||
closePopover();
|
||||
await unsnoozeRule(scheduleIds);
|
||||
onRuleChanged();
|
||||
await onRuleChanged();
|
||||
toasts.addSuccess(UNSNOOZE_SUCCESS_MESSAGE);
|
||||
} catch (e) {
|
||||
toasts.addDanger(SNOOZE_FAILED_MESSAGE);
|
||||
} finally {
|
||||
onLoading(false);
|
||||
setRequestInFlightLoading(false);
|
||||
}
|
||||
},
|
||||
[onLoading, unsnoozeRule, onRuleChanged, toasts, onClosePopover]
|
||||
[setRequestInFlightLoading, unsnoozeRule, onRuleChanged, toasts, closePopover]
|
||||
);
|
||||
|
||||
const popover = (
|
||||
<EuiPopover
|
||||
data-test-subj="rulesListNotifyBadge"
|
||||
isOpen={isOpen}
|
||||
closePopover={onClosePopover}
|
||||
isOpen={isPopoverOpen && !isDisabled}
|
||||
closePopover={closePopover}
|
||||
button={buttonWithToolTip}
|
||||
anchorPosition="rightCenter"
|
||||
panelStyle={{ maxHeight: '100vh', overflowY: 'auto' }}
|
||||
|
@ -274,8 +275,8 @@ export const RulesListNotifyBadge: React.FunctionComponent<RulesListNotifyBadgeP
|
|||
unsnoozeRule={onApplyUnsnooze}
|
||||
interval={futureTimeToInterval(isSnoozedUntil)}
|
||||
showCancel={isSnoozed}
|
||||
scheduledSnoozes={rule.snoozeSchedule ?? []}
|
||||
activeSnoozes={rule.activeSnoozes ?? []}
|
||||
scheduledSnoozes={snoozeSettings?.snoozeSchedule ?? []}
|
||||
activeSnoozes={snoozeSettings?.activeSnoozes ?? []}
|
||||
inPopover
|
||||
/>
|
||||
</EuiPopover>
|
||||
|
|
|
@ -27,7 +27,7 @@ export default {
|
|||
title: 'app/RulesListNotifyBadgeWithApi',
|
||||
component: RulesListNotifyBadgeWithApi,
|
||||
argTypes: {
|
||||
rule: {
|
||||
snoozeSettings: {
|
||||
defaultValue: rule,
|
||||
control: {
|
||||
type: 'object',
|
||||
|
@ -54,7 +54,7 @@ export default {
|
|||
onRuleChanged: {},
|
||||
},
|
||||
args: {
|
||||
rule,
|
||||
snoozeSettings: rule,
|
||||
onRuleChanged: (...args: any) => action('onRuleChanged')(args),
|
||||
},
|
||||
} as Meta<RulesListNotifyBadgePropsWithApi>;
|
||||
|
@ -69,7 +69,7 @@ const IndefinitelyDate = new Date();
|
|||
IndefinitelyDate.setDate(IndefinitelyDate.getDate() + 1);
|
||||
export const IndefinitelyRuleNotifyBadgeWithApi = Template.bind({});
|
||||
IndefinitelyRuleNotifyBadgeWithApi.args = {
|
||||
rule: {
|
||||
snoozeSettings: {
|
||||
...rule,
|
||||
muteAll: true,
|
||||
isSnoozedUntil: IndefinitelyDate,
|
||||
|
@ -80,7 +80,7 @@ export const ActiveSnoozesRuleNotifyBadgeWithApi = Template.bind({});
|
|||
const ActiveSnoozeDate = new Date();
|
||||
ActiveSnoozeDate.setDate(ActiveSnoozeDate.getDate() + 2);
|
||||
ActiveSnoozesRuleNotifyBadgeWithApi.args = {
|
||||
rule: {
|
||||
snoozeSettings: {
|
||||
...rule,
|
||||
activeSnoozes: ['24da3b26-bfa5-4317-b72f-4063dbea618e'],
|
||||
isSnoozedUntil: ActiveSnoozeDate,
|
||||
|
@ -111,7 +111,7 @@ export const ScheduleSnoozesRuleNotifyBadgeWithApi: Story<RulesListNotifyBadgePr
|
|||
};
|
||||
|
||||
ScheduleSnoozesRuleNotifyBadgeWithApi.args = {
|
||||
rule: {
|
||||
snoozeSettings: {
|
||||
...rule,
|
||||
snoozeSchedule: [
|
||||
{
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useKibana } from '../../../../../common/lib/kibana';
|
||||
import { SnoozeSchedule } from '../../../../../types';
|
||||
import { loadRule } from '../../../../lib/rule_api/get_rule';
|
||||
import { unsnoozeRule as unsnoozeRuleApi } from '../../../../lib/rule_api/unsnooze';
|
||||
import { snoozeRule as snoozeRuleApi } from '../../../../lib/rule_api/snooze';
|
||||
import { RulesListNotifyBadge } from './notify_badge';
|
||||
|
@ -16,74 +15,35 @@ import { RulesListNotifyBadgePropsWithApi } from './types';
|
|||
|
||||
export const RulesListNotifyBadgeWithApi: React.FunctionComponent<
|
||||
RulesListNotifyBadgePropsWithApi
|
||||
> = (props) => {
|
||||
const { onRuleChanged, rule, isLoading, showTooltipInline, showOnHover } = props;
|
||||
> = ({
|
||||
ruleId,
|
||||
snoozeSettings,
|
||||
loading,
|
||||
disabled,
|
||||
showTooltipInline,
|
||||
showOnHover,
|
||||
onRuleChanged,
|
||||
}) => {
|
||||
const { http } = useKibana().services;
|
||||
const [currentlyOpenNotify, setCurrentlyOpenNotify] = useState<string>();
|
||||
const [loadingSnoozeAction, setLoadingSnoozeAction] = useState<boolean>(false);
|
||||
const [ruleSnoozeInfo, setRuleSnoozeInfo] =
|
||||
useState<RulesListNotifyBadgePropsWithApi['rule']>(rule);
|
||||
|
||||
// This helps to fix problems related to rule prop updates. As component handles the loading state via isLoading prop
|
||||
// rule prop is obviously not ready atm so when it's ready ruleSnoozeInfo won't be updated without useEffect so
|
||||
// incorrect state will be shown.
|
||||
useEffect(() => {
|
||||
setRuleSnoozeInfo(rule);
|
||||
}, [rule]);
|
||||
|
||||
const onSnoozeRule = useCallback(
|
||||
(snoozeSchedule: SnoozeSchedule) => {
|
||||
return snoozeRuleApi({ http, id: ruleSnoozeInfo.id, snoozeSchedule });
|
||||
},
|
||||
[http, ruleSnoozeInfo.id]
|
||||
(snoozeSchedule: SnoozeSchedule) =>
|
||||
ruleId ? snoozeRuleApi({ http, id: ruleId, snoozeSchedule }) : Promise.resolve(),
|
||||
[http, ruleId]
|
||||
);
|
||||
|
||||
const onUnsnoozeRule = useCallback(
|
||||
(scheduleIds?: string[]) => {
|
||||
return unsnoozeRuleApi({ http, id: ruleSnoozeInfo.id, scheduleIds });
|
||||
},
|
||||
[http, ruleSnoozeInfo.id]
|
||||
(scheduleIds?: string[]) =>
|
||||
ruleId ? unsnoozeRuleApi({ http, id: ruleId, scheduleIds }) : Promise.resolve(),
|
||||
[http, ruleId]
|
||||
);
|
||||
|
||||
const onRuleChangedCallback = useCallback(async () => {
|
||||
const updatedRule = await loadRule({
|
||||
http,
|
||||
ruleId: ruleSnoozeInfo.id,
|
||||
});
|
||||
setLoadingSnoozeAction(false);
|
||||
setRuleSnoozeInfo((prevRule) => ({
|
||||
...prevRule,
|
||||
activeSnoozes: updatedRule.activeSnoozes,
|
||||
isSnoozedUntil: updatedRule.isSnoozedUntil,
|
||||
muteAll: updatedRule.muteAll,
|
||||
snoozeSchedule: updatedRule.snoozeSchedule,
|
||||
}));
|
||||
onRuleChanged();
|
||||
}, [http, ruleSnoozeInfo.id, onRuleChanged]);
|
||||
|
||||
const openSnooze = useCallback(() => {
|
||||
setCurrentlyOpenNotify(props.rule.id);
|
||||
}, [props.rule.id]);
|
||||
|
||||
const closeSnooze = useCallback(() => {
|
||||
setCurrentlyOpenNotify('');
|
||||
}, []);
|
||||
|
||||
const onLoading = useCallback((value: boolean) => {
|
||||
if (value) {
|
||||
setLoadingSnoozeAction(value);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<RulesListNotifyBadge
|
||||
rule={ruleSnoozeInfo}
|
||||
isOpen={currentlyOpenNotify === ruleSnoozeInfo.id}
|
||||
isLoading={isLoading || loadingSnoozeAction}
|
||||
onClick={openSnooze}
|
||||
onClose={closeSnooze}
|
||||
onLoading={onLoading}
|
||||
onRuleChanged={onRuleChangedCallback}
|
||||
snoozeSettings={snoozeSettings}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
onRuleChanged={onRuleChanged}
|
||||
snoozeRule={onSnoozeRule}
|
||||
unsnoozeRule={onUnsnoozeRule}
|
||||
showTooltipInline={showTooltipInline}
|
||||
|
|
|
@ -5,20 +5,23 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RuleTableItem, SnoozeSchedule } from '../../../../../types';
|
||||
import { RuleSnoozeSettings, SnoozeSchedule } from '../../../../../types';
|
||||
|
||||
export interface RulesListNotifyBadgeProps {
|
||||
rule: Pick<
|
||||
RuleTableItem,
|
||||
'id' | 'activeSnoozes' | 'isSnoozedUntil' | 'muteAll' | 'isEditable' | 'snoozeSchedule'
|
||||
>;
|
||||
isOpen: boolean;
|
||||
isLoading: boolean;
|
||||
previousSnoozeInterval?: string | null;
|
||||
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onClose: () => void;
|
||||
onLoading: (isLoading: boolean) => void;
|
||||
onRuleChanged: () => void;
|
||||
/**
|
||||
* Rule's snooze settings
|
||||
*/
|
||||
snoozeSettings: RuleSnoozeSettings | undefined;
|
||||
/**
|
||||
* Displays the component in the loading state. If isLoading = false and snoozeSettings aren't set
|
||||
* and the component is shown in disabled state.
|
||||
*/
|
||||
loading?: boolean;
|
||||
/**
|
||||
* Whether the component is disabled or not, string give a disabled reason displayed as a tooltip
|
||||
*/
|
||||
disabled?: boolean | string;
|
||||
onRuleChanged: () => void | Promise<void>;
|
||||
snoozeRule: (schedule: SnoozeSchedule, muteAll?: boolean) => Promise<void>;
|
||||
unsnoozeRule: (scheduleIds?: string[]) => Promise<void>;
|
||||
showTooltipInline?: boolean;
|
||||
|
@ -27,5 +30,10 @@ export interface RulesListNotifyBadgeProps {
|
|||
|
||||
export type RulesListNotifyBadgePropsWithApi = Pick<
|
||||
RulesListNotifyBadgeProps,
|
||||
'rule' | 'isLoading' | 'onRuleChanged' | 'showOnHover' | 'showTooltipInline'
|
||||
>;
|
||||
'snoozeSettings' | 'loading' | 'disabled' | 'onRuleChanged' | 'showOnHover' | 'showTooltipInline'
|
||||
> & {
|
||||
/**
|
||||
* Rule's SO id
|
||||
*/
|
||||
ruleId: string;
|
||||
};
|
||||
|
|
|
@ -46,10 +46,12 @@ export const SnoozePanel: React.FC<SnoozePanelProps> = ({
|
|||
try {
|
||||
await snoozeRule(schedule);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
if (!inPopover) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[setIsLoading, snoozeRule]
|
||||
[inPopover, setIsLoading, snoozeRule]
|
||||
);
|
||||
|
||||
const onUnsnoozeRule = useCallback(
|
||||
|
@ -58,10 +60,12 @@ export const SnoozePanel: React.FC<SnoozePanelProps> = ({
|
|||
try {
|
||||
await unsnoozeRule(scheduleIds);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
if (!inPopover) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[setIsLoading, unsnoozeRule]
|
||||
[inPopover, setIsLoading, unsnoozeRule]
|
||||
);
|
||||
|
||||
const saveSnoozeSchedule = useCallback(
|
||||
|
@ -70,10 +74,12 @@ export const SnoozePanel: React.FC<SnoozePanelProps> = ({
|
|||
try {
|
||||
await snoozeRule(schedule);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
if (!inPopover) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[snoozeRule, setIsLoading]
|
||||
[inPopover, snoozeRule, setIsLoading]
|
||||
);
|
||||
|
||||
const cancelSnoozeSchedules = useCallback(
|
||||
|
@ -82,10 +88,12 @@ export const SnoozePanel: React.FC<SnoozePanelProps> = ({
|
|||
try {
|
||||
await unsnoozeRule(scheduleIds);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
if (!inPopover) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[unsnoozeRule, setIsLoading]
|
||||
[inPopover, unsnoozeRule, setIsLoading]
|
||||
);
|
||||
|
||||
const onOpenScheduler = useCallback(
|
||||
|
|
|
@ -847,7 +847,7 @@ export const RulesList = ({
|
|||
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
|
||||
onSort={setSort}
|
||||
onPage={setPage}
|
||||
onRuleChanged={() => refreshRules()}
|
||||
onRuleChanged={refreshRules}
|
||||
onRuleClick={(rule) => {
|
||||
const detailsRoute = ruleDetailsRoute ? ruleDetailsRoute : commonRuleDetailsRoute;
|
||||
history.push(detailsRoute.replace(`:ruleId`, rule.id));
|
||||
|
@ -883,7 +883,7 @@ export const RulesList = ({
|
|||
key={rule.id}
|
||||
item={rule}
|
||||
onLoading={onLoading}
|
||||
onRuleChanged={() => refreshRules()}
|
||||
onRuleChanged={refreshRules}
|
||||
onDeleteRule={() =>
|
||||
updateRulesToBulkEdit({
|
||||
action: 'delete',
|
||||
|
|
|
@ -208,7 +208,6 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
} = props;
|
||||
|
||||
const [tagPopoverOpenIndex, setTagPopoverOpenIndex] = useState<number>(-1);
|
||||
const [currentlyOpenNotify, setCurrentlyOpenNotify] = useState<string>();
|
||||
const [isLoadingMap, setIsLoadingMap] = useState<Record<string, boolean>>({});
|
||||
|
||||
const isRuleUsingExecutionStatus = getIsExperimentalFeatureEnabled('ruleUseExecutionStatus');
|
||||
|
@ -485,12 +484,9 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
return (
|
||||
<RulesListNotifyBadge
|
||||
showOnHover
|
||||
rule={rule}
|
||||
isLoading={!!isLoadingMap[rule.id]}
|
||||
onLoading={(newIsLoading) => onLoading(rule.id, newIsLoading)}
|
||||
isOpen={currentlyOpenNotify === rule.id}
|
||||
onClick={() => setCurrentlyOpenNotify(rule.id)}
|
||||
onClose={() => setCurrentlyOpenNotify('')}
|
||||
snoozeSettings={rule}
|
||||
loading={!!isLoadingMap[rule.id]}
|
||||
disabled={!rule.isEditable}
|
||||
onRuleChanged={onRuleChanged}
|
||||
snoozeRule={async (snoozeSchedule) => {
|
||||
await onSnoozeRule(rule, snoozeSchedule);
|
||||
|
@ -769,7 +765,6 @@ export const RulesListTable = (props: RulesListTableProps) => {
|
|||
];
|
||||
}, [
|
||||
config.minimumScheduleInterval,
|
||||
currentlyOpenNotify,
|
||||
isLoadingMap,
|
||||
isRuleTypeEditableInContext,
|
||||
onRuleChanged,
|
||||
|
|
|
@ -343,6 +343,11 @@ export type SanitizedRuleType = Omit<RuleType, 'apiKey'>;
|
|||
|
||||
export type RuleUpdates = Omit<Rule, 'id' | 'executionStatus' | 'lastRun' | 'nextRun'>;
|
||||
|
||||
export type RuleSnoozeSettings = Pick<
|
||||
Rule,
|
||||
'activeSnoozes' | 'isSnoozedUntil' | 'muteAll' | 'snoozeSchedule'
|
||||
>;
|
||||
|
||||
export interface RuleTableItem extends Rule {
|
||||
ruleType: RuleType['name'];
|
||||
index: number;
|
||||
|
@ -350,7 +355,6 @@ export interface RuleTableItem extends Rule {
|
|||
isEditable: boolean;
|
||||
enabledInLicense: boolean;
|
||||
showIntervalWarning?: boolean;
|
||||
activeSnoozes?: string[];
|
||||
}
|
||||
|
||||
export interface RuleTypeParamsExpressionProps<
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue