From c16fce9ee9ae776e92caf9558e170ed5896ba286 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Tue, 19 Apr 2022 16:35:04 -0500 Subject: [PATCH] [RAM] Make RuleStatusDropdown shareable (#130205) * Add shareable e dropdown and move snooze interval to localstorage * Add internal components sandbox page and status dropdown test * Remove components sandbox tab * Fix typecheck * Fix typechecks and tests * Attempt to deflake tests * Reenable previousSnoozeInterval from props and prefix storage key * Export missing apis * Fix tooltip for indefinite snooze * Attempt to deflake functional test * Modularize Sandbox * Up triggersActionsUi package limit --- packages/kbn-optimizer/limits.yml | 2 +- .../common/experimental_features.ts | 1 + .../public/application/app.tsx | 2 +- .../public/application/constants/index.ts | 3 +- .../public/application/home.tsx | 21 ++++- .../rule_status_dropdown_sandbox.tsx | 44 ++++++++++ .../shareable_components_sandbox.tsx | 20 +++++ .../public/application/sections/index.tsx | 4 + .../rule_details/components/rule_details.tsx | 3 +- .../components/rule_status_dropdown.test.tsx | 15 ++-- .../components/rule_status_dropdown.tsx | 87 ++++++++++++++----- .../rules_list/components/rules_list.tsx | 5 +- .../common/get_experimental_features.test.tsx | 5 ++ .../common/get_rule_status_dropdown.tsx | 14 +++ .../triggers_actions_ui/public/index.ts | 2 + .../triggers_actions_ui/public/mocks.ts | 4 + .../triggers_actions_ui/public/plugin.ts | 6 ++ .../triggers_actions_ui/public/types.ts | 2 + .../apps/triggers_actions_ui/index.ts | 1 + .../rule_status_dropdown.ts | 54 ++++++++++++ x-pack/test/functional_with_es_ssl/config.ts | 5 +- 21 files changed, 259 insertions(+), 41 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/shareable_components_sandbox.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/common/get_rule_status_dropdown.tsx create mode 100644 x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/rule_status_dropdown.ts diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index a8eaa148e74d4..f2f3fbc07aac8 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -56,7 +56,7 @@ pageLoadAssetSize: telemetry: 51957 telemetryManagementSection: 38586 transform: 41007 - triggersActionsUi: 102400 + triggersActionsUi: 103400 upgradeAssistant: 81241 uptime: 40825 urlForwarding: 32579 diff --git a/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts b/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts index 3a081a5a45486..21835a5977216 100644 --- a/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts +++ b/x-pack/plugins/triggers_actions_ui/common/experimental_features.ts @@ -14,6 +14,7 @@ export type ExperimentalFeatures = typeof allowedExperimentalValues; export const allowedExperimentalValues = Object.freeze({ rulesListDatagrid: true, internalAlertsTable: false, + internalShareableComponentsSandbox: false, rulesDetailLogs: true, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index 91c8b06d31a88..71bcb2ee7d760 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -68,7 +68,7 @@ export const renderApp = (deps: TriggersAndActionsUiServices) => { export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => { const { savedObjects, uiSettings, theme$ } = deps; - const sections: Section[] = ['rules', 'connectors', 'alerts']; + const sections: Section[] = ['rules', 'connectors', 'alerts', '__components_sandbox']; const isDarkMode = useObservable(uiSettings.get$('theme:darkMode')); const sectionsRegex = sections.join('|'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index b0c417e3ef9c9..99c115def07e6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -13,13 +13,14 @@ export { } from '@kbn/alerting-plugin/common'; export { BASE_ACTION_API_PATH, INTERNAL_BASE_ACTION_API_PATH } from '@kbn/actions-plugin/common'; -export type Section = 'connectors' | 'rules' | 'alerts'; +export type Section = 'connectors' | 'rules' | 'alerts' | '__components_sandbox'; export const routeToHome = `/`; export const routeToConnectors = `/connectors`; export const routeToRules = `/rules`; export const routeToRuleDetails = `/rule/:ruleId`; export const routeToInternalAlerts = `/alerts`; +export const routeToInternalShareableComponentsSandbox = '/__components_sandbox'; export const legacyRouteToRules = `/alerts`; export const legacyRouteToRuleDetails = `/alert/:alertId`; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx index 9110ebe1f51c8..802e3178b1554 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -11,7 +11,13 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiSpacer, EuiButtonEmpty, EuiPageHeader } from '@elastic/eui'; import { getIsExperimentalFeatureEnabled } from '../common/get_experimental_features'; -import { Section, routeToConnectors, routeToRules, routeToInternalAlerts } from './constants'; +import { + Section, + routeToConnectors, + routeToRules, + routeToInternalAlerts, + routeToInternalShareableComponentsSandbox, +} from './constants'; import { getAlertingSectionBreadcrumb } from './lib/breadcrumb'; import { getCurrentDocTitle } from './lib/doc_title'; import { hasShowActionsCapability } from './lib/capabilities'; @@ -26,6 +32,9 @@ const ActionsConnectorsList = lazy( ); const RulesList = lazy(() => import('./sections/rules_list/components/rules_list')); const AlertsPage = lazy(() => import('./sections/alerts_table/alerts_page')); +const InternalShareableComponentsSandbox = lazy( + () => import('./internal/shareable_components_sandbox/shareable_components_sandbox') +); export interface MatchParams { section: Section; @@ -45,6 +54,9 @@ export const TriggersActionsUIHome: React.FunctionComponent + {isInternalShareableComponentsSandboxEnabled && ( + + )} {isInternalAlertsTableEnabled ? ( = () => { + const [enabled, setEnabled] = useState(true); + const [snoozeEndTime, setSnoozeEndTime] = useState(null); + const [muteAll, setMuteAll] = useState(false); + + return getRuleStatusDropdownLazy({ + rule: { + enabled, + snoozeEndTime, + muteAll, + }, + enableRule: async () => { + setEnabled(true); + setMuteAll(false); + setSnoozeEndTime(null); + }, + disableRule: async () => setEnabled(false), + snoozeRule: async (time) => { + if (time === -1) { + setSnoozeEndTime(null); + setMuteAll(true); + } else { + setSnoozeEndTime(new Date(time)); + setMuteAll(false); + } + }, + unsnoozeRule: async () => { + setMuteAll(false); + setSnoozeEndTime(null); + }, + onRuleChanged: () => {}, + isEditable: true, + }); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/shareable_components_sandbox.tsx b/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/shareable_components_sandbox.tsx new file mode 100644 index 0000000000000..97366832bda0e --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/shareable_components_sandbox.tsx @@ -0,0 +1,20 @@ +/* + * 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 { RuleStatusDropdownSandbox } from './rule_status_dropdown_sandbox'; + +export const InternalShareableComponentsSandbox: React.FC<{}> = () => { + return ( + <> + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { InternalShareableComponentsSandbox as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx index 5b8a6ea569344..0aaa3195b7c52 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx @@ -28,3 +28,7 @@ export const ConnectorEditFlyout = suspendedComponentWithProps( export const ActionForm = suspendedComponentWithProps( lazy(() => import('./action_connector_form/action_form')) ); + +export const RuleStatusDropdown = suspendedComponentWithProps( + lazy(() => import('./rules_list/components/rule_status_dropdown')) +); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx index c940666b54077..b3363159851d0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx @@ -285,11 +285,10 @@ export const RuleDetails: React.FunctionComponent = ({ await snoozeRule(rule, snoozeEndTime) } unsnoozeRule={async () => await unsnoozeRule(rule)} - item={rule as RuleTableItem} + rule={rule as RuleTableItem} onRuleChanged={requestRefresh} direction="row" isEditable={hasEditButton} - previousSnoozeInterval={null} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx index ad642738dfbba..15086518124b4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx @@ -23,8 +23,7 @@ describe('RuleStatusDropdown', () => { snoozeRule, unsnoozeRule, isEditable: true, - previousSnoozeInterval: null, - item: { + rule: { id: '1', name: 'test rule', tags: ['tag1'], @@ -53,7 +52,7 @@ describe('RuleStatusDropdown', () => { index: 0, updatedAt: new Date('2020-08-20T19:23:38Z'), snoozeEndTime: null, - }, + } as ComponentOpts['rule'], onRuleChanged: jest.fn(), }; @@ -75,7 +74,7 @@ describe('RuleStatusDropdown', () => { test('renders status control as disabled when rule is disabled', () => { const wrapper = mountWithIntl( - + ); expect(wrapper.find('[data-test-subj="statusDropdown"]').first().props().title).toBe( 'Disabled' @@ -87,7 +86,7 @@ describe('RuleStatusDropdown', () => { const wrapper = mountWithIntl( ); expect(wrapper.find('[data-test-subj="statusDropdown"]').first().props().title).toBe('Snoozed'); @@ -98,7 +97,7 @@ describe('RuleStatusDropdown', () => { jest.spyOn(global.Date, 'now').mockImplementation(() => new Date(NOW_STRING).valueOf()); const wrapper = mountWithIntl( - + ); expect(wrapper.find('[data-test-subj="statusDropdown"]').first().props().title).toBe('Snoozed'); expect(wrapper.find('[data-test-subj="remainingSnoozeTime"]').first().text()).toBe( @@ -109,7 +108,7 @@ describe('RuleStatusDropdown', () => { test('renders status control as disabled when rule is snoozed but also disabled', () => { const wrapper = mountWithIntl( ); expect(wrapper.find('[data-test-subj="statusDropdown"]').first().props().title).toBe( @@ -122,7 +121,7 @@ describe('RuleStatusDropdown', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx index 098d7c08a78f5..40658ae282e16 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx @@ -31,20 +31,22 @@ import { } from '@elastic/eui'; import { parseInterval } from '../../../../../common'; -import { RuleTableItem } from '../../../../types'; +import { Rule } from '../../../../types'; type SnoozeUnit = 'm' | 'h' | 'd' | 'w' | 'M'; const SNOOZE_END_TIME_FORMAT = 'LL @ LT'; +type DropdownRuleRecord = Pick; + export interface ComponentOpts { - item: RuleTableItem; + rule: DropdownRuleRecord; onRuleChanged: () => void; enableRule: () => Promise; disableRule: () => Promise; snoozeRule: (snoozeEndTime: string | -1, interval: string | null) => Promise; unsnoozeRule: () => Promise; isEditable: boolean; - previousSnoozeInterval: string | null; + previousSnoozeInterval?: string | null; direction?: 'column' | 'row'; } @@ -55,34 +57,64 @@ const COMMON_SNOOZE_TIMES: Array<[number, SnoozeUnit]> = [ [1, 'd'], ]; +const PREV_SNOOZE_INTERVAL_KEY = 'triggersActionsUi_previousSnoozeInterval'; +const usePreviousSnoozeInterval: (p?: string | null) => [string | null, (n: string) => void] = ( + propsInterval +) => { + const intervalFromStorage = localStorage.getItem(PREV_SNOOZE_INTERVAL_KEY); + const usePropsInterval = typeof propsInterval !== 'undefined'; + const interval = usePropsInterval ? propsInterval : intervalFromStorage; + const [previousSnoozeInterval, setPreviousSnoozeInterval] = useState(interval); + const storeAndSetPreviousSnoozeInterval = (newInterval: string) => { + if (!usePropsInterval) { + localStorage.setItem(PREV_SNOOZE_INTERVAL_KEY, newInterval); + } + setPreviousSnoozeInterval(newInterval); + }; + return [previousSnoozeInterval, storeAndSetPreviousSnoozeInterval]; +}; + export const RuleStatusDropdown: React.FunctionComponent = ({ - item, + rule, onRuleChanged, disableRule, enableRule, snoozeRule, unsnoozeRule, isEditable, - previousSnoozeInterval, + previousSnoozeInterval: propsPreviousSnoozeInterval, direction = 'column', }: ComponentOpts) => { - const [isEnabled, setIsEnabled] = useState(item.enabled); - const [isSnoozed, setIsSnoozed] = useState(isItemSnoozed(item)); + const [isEnabled, setIsEnabled] = useState(rule.enabled); + const [isSnoozed, setIsSnoozed] = useState(isRuleSnoozed(rule)); + const [previousSnoozeInterval, setPreviousSnoozeInterval] = usePreviousSnoozeInterval( + propsPreviousSnoozeInterval + ); + useEffect(() => { - setIsEnabled(item.enabled); - }, [item.enabled]); + setIsEnabled(rule.enabled); + }, [rule.enabled]); useEffect(() => { - setIsSnoozed(isItemSnoozed(item)); - }, [item]); + setIsSnoozed(isRuleSnoozed(rule)); + }, [rule]); const [isUpdating, setIsUpdating] = useState(false); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const onClickBadge = useCallback(() => setIsPopoverOpen((isOpen) => !isOpen), [setIsPopoverOpen]); const onClosePopover = useCallback(() => setIsPopoverOpen(false), [setIsPopoverOpen]); + const snoozeRuleAndStoreInterval = useCallback( + (snoozeEndTime: string | -1, interval: string | null) => { + if (interval) { + setPreviousSnoozeInterval(interval); + } + return snoozeRule(snoozeEndTime, interval); + }, + [setPreviousSnoozeInterval, snoozeRule] + ); const onChangeEnabledStatus = useCallback( async (enable: boolean) => { - if (item.enabled === enable) { + if (rule.enabled === enable) { return; } setIsUpdating(true); @@ -98,17 +130,17 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ setIsUpdating(false); } }, - [item.enabled, isEnabled, onRuleChanged, enableRule, disableRule] + [rule.enabled, isEnabled, onRuleChanged, enableRule, disableRule] ); const onChangeSnooze = useCallback( async (value: number, unit?: SnoozeUnit) => { setIsUpdating(true); try { if (value === -1) { - await snoozeRule(-1, null); + await snoozeRuleAndStoreInterval(-1, null); } else if (value !== 0) { const snoozeEndTime = moment().add(value, unit).toISOString(); - await snoozeRule(snoozeEndTime, `${value}${unit}`); + await snoozeRuleAndStoreInterval(snoozeEndTime, `${value}${unit}`); } else await unsnoozeRule(); setIsSnoozed(value !== 0); onRuleChanged(); @@ -116,7 +148,7 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ setIsUpdating(false); } }, - [setIsUpdating, setIsSnoozed, onRuleChanged, snoozeRule, unsnoozeRule] + [setIsUpdating, setIsSnoozed, onRuleChanged, snoozeRuleAndStoreInterval, unsnoozeRule] ); const badgeColor = !isEnabled ? 'default' : isSnoozed ? 'warning' : 'primary'; @@ -124,9 +156,13 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ const remainingSnoozeTime = isEnabled && isSnoozed ? ( - + - {item.muteAll ? INDEFINITELY : moment(item.snoozeEndTime).fromNow(true)} + {rule.muteAll ? INDEFINITELY : moment(rule.snoozeEndTime).fromNow(true)} ) : null; @@ -179,7 +215,7 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ onChangeSnooze={onChangeSnooze} isEnabled={isEnabled} isSnoozed={isSnoozed} - snoozeEndTime={item.snoozeEndTime} + snoozeEndTime={rule.snoozeEndTime} previousSnoozeInterval={previousSnoozeInterval} /> @@ -267,6 +303,7 @@ const RuleStatusMenu: React.FunctionComponent = ({ icon: isEnabled && isSnoozed ? 'check' : 'empty', panel: 1, disabled: !isEnabled, + 'data-test-subj': 'statusDropdownSnoozeItem', }, ], }, @@ -330,6 +367,7 @@ const SnoozePanel: React.FunctionComponent = ({ applySnooze(parsedPrevSnooze.value, parsedPrevSnooze.unit as SnoozeUnit)} > {i18n.translate('xpack.triggersActionsUI.sections.rulesList.previousSnooze', { @@ -338,7 +376,7 @@ const SnoozePanel: React.FunctionComponent = ({ - + {durationToTextString(parsedPrevSnooze.value, parsedPrevSnooze.unit as SnoozeUnit)} @@ -358,6 +396,7 @@ const SnoozePanel: React.FunctionComponent = ({ 'xpack.triggersActionsUI.sections.rulesList.snoozePanelIntervalValueLabel', { defaultMessage: 'Snooze interval value' } )} + data-test-subj="ruleSnoozeIntervalValue" /> @@ -376,6 +415,7 @@ const SnoozePanel: React.FunctionComponent = ({ { value: 'w', text: WEEKS }, { value: 'M', text: MONTHS }, ]} + data-test-subj="ruleSnoozeIntervalUnit" /> @@ -434,8 +474,8 @@ const SnoozePanel: React.FunctionComponent = ({ ); }; -const isItemSnoozed = (item: { snoozeEndTime?: Date | null; muteAll: boolean }) => { - const { snoozeEndTime, muteAll } = item; +const isRuleSnoozed = (rule: DropdownRuleRecord) => { + const { snoozeEndTime, muteAll } = rule; if (muteAll) return true; if (!snoozeEndTime) { return false; @@ -548,3 +588,6 @@ const ONE: Record = { defaultMessage: '1 month', }), }; + +// eslint-disable-next-line import/no-default-export +export { RuleStatusDropdown as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index 73dc8e8e8c2cd..59515ca3c3622 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -160,7 +160,6 @@ export const RulesList: React.FunctionComponent = () => { const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); const [currentRuleToEdit, setCurrentRuleToEdit] = useState(null); const [tagPopoverOpenIndex, setTagPopoverOpenIndex] = useState(-1); - const [previousSnoozeInterval, setPreviousSnoozeInterval] = useState(null); const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>( {} ); @@ -355,13 +354,11 @@ export const RulesList: React.FunctionComponent = () => { enableRule={async () => await enableRule({ http, id: item.id })} snoozeRule={async (snoozeEndTime: string | -1, interval: string | null) => { await snoozeRule({ http, id: item.id, snoozeEndTime }); - setPreviousSnoozeInterval(interval); }} unsnoozeRule={async () => await unsnoozeRule({ http, id: item.id })} - item={item} + rule={item} onRuleChanged={() => loadRulesData()} isEditable={item.isEditable && isRuleTypeEditableInContext(item.ruleTypeId)} - previousSnoozeInterval={previousSnoozeInterval} /> ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx index aa7de97a6c889..237eccb0b4434 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_experimental_features.test.tsx @@ -18,6 +18,7 @@ describe('getIsExperimentalFeatureEnabled', () => { rulesListDatagrid: true, internalAlertsTable: true, rulesDetailLogs: true, + internalShareableComponentsSandbox: true, }, }); @@ -33,6 +34,10 @@ describe('getIsExperimentalFeatureEnabled', () => { expect(result).toEqual(true); + result = getIsExperimentalFeatureEnabled('internalShareableComponentsSandbox'); + + expect(result).toEqual(true); + expect(() => getIsExperimentalFeatureEnabled('doesNotExist' as any)).toThrowError( `Invalid enable value doesNotExist. Allowed values are: ${allowedExperimentalValueKeys.join( ', ' diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_rule_status_dropdown.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_rule_status_dropdown.tsx new file mode 100644 index 0000000000000..06edadef7fab5 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_rule_status_dropdown.tsx @@ -0,0 +1,14 @@ +/* + * 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 { RuleStatusDropdown } from '../application/sections'; +import type { ComponentOpts } from '../application/sections/rules_list/components/rule_status_dropdown'; + +export const getRuleStatusDropdownLazy = (props: ComponentOpts) => { + return ; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index f72ed095efdcc..94fe718363e2b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -57,6 +57,8 @@ export { enableRule } from './application/lib/rule_api/enable'; export { disableRule } from './application/lib/rule_api/disable'; export { muteRule } from './application/lib/rule_api/mute'; export { unmuteRule } from './application/lib/rule_api/unmute'; +export { snoozeRule } from './application/lib/rule_api/snooze'; +export { unsnoozeRule } from './application/lib/rule_api/unsnooze'; export { loadRuleAggregations } from './application/lib/rule_api/aggregate'; export { useLoadRuleTypes } from './application/hooks/use_load_rule_types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/mocks.ts b/x-pack/plugins/triggers_actions_ui/public/mocks.ts index 87b270070c10b..79edc1f08ac97 100644 --- a/x-pack/plugins/triggers_actions_ui/public/mocks.ts +++ b/x-pack/plugins/triggers_actions_ui/public/mocks.ts @@ -24,6 +24,7 @@ import { AlertsTableConfigurationRegistry, } from './types'; import { getAlertsTableLazy } from './common/get_alerts_table'; +import { getRuleStatusDropdownLazy } from './common/get_rule_status_dropdown'; function createStartMock(): TriggersAndActionsUIPublicPluginStart { const actionTypeRegistry = new TypeRegistry(); @@ -59,6 +60,9 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart { getAlertsTable: (props: AlertsTableProps) => { return getAlertsTableLazy(props); }, + getRuleStatusDropdown: (props) => { + return getRuleStatusDropdownLazy(props); + }, }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index e07fb2a6fef99..ba2e869c82e0f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -29,6 +29,7 @@ import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout'; import { getAddAlertFlyoutLazy } from './common/get_add_alert_flyout'; import { getEditAlertFlyoutLazy } from './common/get_edit_alert_flyout'; import { getAlertsTableLazy } from './common/get_alerts_table'; +import { getRuleStatusDropdownLazy } from './common/get_rule_status_dropdown'; import { ExperimentalFeaturesService } from './common/experimental_features_service'; import { ExperimentalFeatures, @@ -43,6 +44,7 @@ import type { ConnectorAddFlyoutProps, ConnectorEditFlyoutProps, AlertsTableProps, + RuleStatusDropdownProps, AlertsTableConfigurationRegistry, } from './types'; import { TriggersActionsUiConfigType } from '../common/types'; @@ -72,6 +74,7 @@ export interface TriggersAndActionsUIPublicPluginStart { props: Omit ) => ReactElement; getAlertsTable: (props: AlertsTableProps) => ReactElement; + getRuleStatusDropdown: (props: RuleStatusDropdownProps) => ReactElement; } interface PluginsSetup { @@ -238,6 +241,9 @@ export class Plugin getAlertsTable: (props: AlertsTableProps) => { return getAlertsTableLazy(props); }, + getRuleStatusDropdown: (props: RuleStatusDropdownProps) => { + return getRuleStatusDropdownLazy(props); + }, }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index d411182e15237..2d4268252a353 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -47,6 +47,7 @@ import { } from '@kbn/alerting-plugin/common'; import { RuleRegistrySearchRequestPagination } from '@kbn/rule-registry-plugin/common'; import { TypeRegistry } from './application/type_registry'; +import type { ComponentOpts as RuleStatusDropdownProps } from './application/sections/rules_list/components/rule_status_dropdown'; // In Triggers and Actions we treat all `Alert`s as `SanitizedRule` // so the `Params` is a black-box of Record @@ -78,6 +79,7 @@ export type { RuleTypeParams, ResolvedRule, SanitizedRule, + RuleStatusDropdownProps, }; export type { ActionType, AsApiContract }; export { diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/index.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/index.ts index 76a952c354ab4..179cd0c7ab8e9 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/index.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/index.ts @@ -16,5 +16,6 @@ export default ({ loadTestFile, getService }: FtrProviderContext) => { loadTestFile(require.resolve('./details')); loadTestFile(require.resolve('./connectors')); loadTestFile(require.resolve('./alerts_table')); + loadTestFile(require.resolve('./rule_status_dropdown')); }); }; diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/rule_status_dropdown.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/rule_status_dropdown.ts new file mode 100644 index 0000000000000..b594d88a25689 --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/rule_status_dropdown.ts @@ -0,0 +1,54 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); + // const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + // const find = getService('find'); + + describe('Rule status dropdown', function () { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); + await PageObjects.common.navigateToUrlWithBrowserHistory( + 'triggersActions', + '/__components_sandbox' + ); + }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); + }); + + it('should load from the shareable lazy loader', async () => { + await testSubjects.find('statusDropdown'); + const exists = await testSubjects.exists('statusDropdown'); + expect(exists).to.be(true); + }); + + it('should store the previous snooze interval', async () => { + await testSubjects.find('statusDropdown'); + await testSubjects.click('statusDropdown'); + await testSubjects.click('statusDropdownSnoozeItem'); + await testSubjects.setValue('ruleSnoozeIntervalValue', '10'); + await testSubjects.setValue('ruleSnoozeIntervalUnit', 'h'); + await testSubjects.click('ruleSnoozeApply'); + + // Wait for the dropdown to finish re-rendering before opening again + await new Promise((res) => setTimeout(res, 500)); + + await testSubjects.click('statusDropdown'); + await testSubjects.click('statusDropdownSnoozeItem'); + await testSubjects.setValue('ruleSnoozeIntervalValue', '3'); + expect(await testSubjects.exists('ruleSnoozePreviousText')).to.be(true); + expect(await testSubjects.getVisibleText('ruleSnoozePreviousText')).to.be('10 hours'); + }); + }); +}; diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts index 1c10548b90699..5243b97898578 100644 --- a/x-pack/test/functional_with_es_ssl/config.ts +++ b/x-pack/test/functional_with_es_ssl/config.ts @@ -71,7 +71,10 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--elasticsearch.hosts=https://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, `--plugin-path=${join(__dirname, 'fixtures', 'plugins', 'alerts')}`, - `--xpack.trigger_actions_ui.enableExperimental=${JSON.stringify(['internalAlertsTable'])}`, + `--xpack.trigger_actions_ui.enableExperimental=${JSON.stringify([ + 'internalAlertsTable', + 'internalShareableComponentsSandbox', + ])}`, `--xpack.alerting.rules.minimumScheduleInterval.value="2s"`, `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, `--xpack.actions.preconfiguredAlertHistoryEsIndex=false`,