From 7e34bf2dbcbce4fee9e3508270f78dcd43e09fca Mon Sep 17 00:00:00 2001 From: ymao1 Date: Mon, 26 Oct 2020 13:30:27 -0400 Subject: [PATCH] [Alerting UI] Don't wait for health check before showing Create Alert flyout (#80996) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * Adding health context provider and option to block waiting for health check * Adding tests * Removing forced lag * Fixing action form not rendering pre selected action * PR fixes * Updating i18n ids Co-authored-by: Mike Côté * Applying i18n fix Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Mike Côté --- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../components/health_check.test.tsx | 59 ++++++-- .../application/components/health_check.tsx | 10 +- .../application/context/health_context.tsx | 45 ++++++ .../public/application/home.tsx | 17 ++- .../sections/alert_form/alert_add.tsx | 84 ++++------ .../sections/alert_form/alert_add_footer.tsx | 61 ++++++++ .../sections/alert_form/alert_edit.tsx | 143 ++++++++++-------- .../sections/alert_form/alert_form.tsx | 8 +- 10 files changed, 282 insertions(+), 149 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/context/health_context.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add_footer.tsx diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5cabdd62d7c87..e9e0f3aed6457 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -20272,7 +20272,6 @@ "xpack.triggersActionsUI.sections.addModalConnectorForm.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.addModalConnectorForm.updateSuccessNotificationText": "「{connectorName}」を作成しました", "xpack.triggersActionsUI.sections.alertAdd.betaBadgeTooltipContent": "{pluginName} はベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません。", - "xpack.triggersActionsUI.sections.alertAdd.cancelButtonLabel": "キャンセル", "xpack.triggersActionsUI.sections.alertAdd.conditionPrompt": "条件を定義してください", "xpack.triggersActionsUI.sections.alertAdd.errorLoadingAlertVisualizationTitle": "アラートビジュアライゼーションを読み込めません", "xpack.triggersActionsUI.sections.alertAdd.flyoutTitle": "アラートの作成", @@ -20280,7 +20279,6 @@ "xpack.triggersActionsUI.sections.alertAdd.loadingAlertVisualizationDescription": "アラートビジュアライゼーションを読み込み中...", "xpack.triggersActionsUI.sections.alertAdd.operationName": "作成", "xpack.triggersActionsUI.sections.alertAdd.previewAlertVisualizationDescription": "プレビューを生成するための式を完成します。", - "xpack.triggersActionsUI.sections.alertAdd.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.alertAdd.saveErrorNotificationText": "アラートを作成できません。", "xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText": "「{alertName}」 を保存しました", "xpack.triggersActionsUI.sections.alertAdd.selectIndex": "インデックスを選択してください", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 229938a3c1d08..48797d2b1e941 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -20292,7 +20292,6 @@ "xpack.triggersActionsUI.sections.addModalConnectorForm.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.addModalConnectorForm.updateSuccessNotificationText": "已创建“{connectorName}”", "xpack.triggersActionsUI.sections.alertAdd.betaBadgeTooltipContent": "{pluginName} 为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束。", - "xpack.triggersActionsUI.sections.alertAdd.cancelButtonLabel": "取消", "xpack.triggersActionsUI.sections.alertAdd.conditionPrompt": "定义条件", "xpack.triggersActionsUI.sections.alertAdd.errorLoadingAlertVisualizationTitle": "无法加载告警可视化", "xpack.triggersActionsUI.sections.alertAdd.flyoutTitle": "创建告警", @@ -20300,7 +20299,6 @@ "xpack.triggersActionsUI.sections.alertAdd.loadingAlertVisualizationDescription": "正在加载告警可视化……", "xpack.triggersActionsUI.sections.alertAdd.operationName": "创建", "xpack.triggersActionsUI.sections.alertAdd.previewAlertVisualizationDescription": "完成表达式以生成预览。", - "xpack.triggersActionsUI.sections.alertAdd.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.alertAdd.saveErrorNotificationText": "无法创建告警。", "xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText": "已保存“{alertName}”", "xpack.triggersActionsUI.sections.alertAdd.selectIndex": "选择索引", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx index 1d908920db8b0..a7de73c9aab29 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.test.tsx @@ -10,6 +10,7 @@ import { HealthCheck } from './health_check'; import { act } from 'react-dom/test-utils'; import { httpServiceMock } from '../../../../../../src/core/public/mocks'; +import { HealthContextProvider } from '../context/health_context'; const docLinks = { ELASTIC_WEBSITE_URL: 'elastic.co/', DOC_LINK_VERSION: 'current' }; @@ -20,9 +21,11 @@ describe('health check', () => { http.get.mockImplementationOnce(() => new Promise(() => {})); const { queryByText, container } = render( - -

{'shouldnt render'}

-
+ + +

{'shouldnt render'}

+
+
); await act(async () => { // wait for useEffect to run @@ -32,13 +35,33 @@ describe('health check', () => { expect(queryByText('shouldnt render')).not.toBeInTheDocument(); }); + it('renders children immediately if waitForCheck is false', async () => { + http.get.mockImplementationOnce(() => new Promise(() => {})); + + const { queryByText, container } = render( + + +

{'should render'}

+
+
+ ); + await act(async () => { + // wait for useEffect to run + }); + + expect(container.getElementsByClassName('euiLoadingSpinner').length).toBe(0); + expect(queryByText('should render')).toBeInTheDocument(); + }); + it('renders children if keys are enabled', async () => { http.get.mockResolvedValue({ isSufficientlySecure: true, hasPermanentEncryptionKey: true }); const { queryByText } = render( - -

{'should render'}

-
+ + +

{'should render'}

+
+
); await act(async () => { // wait for useEffect to run @@ -53,9 +76,11 @@ describe('health check', () => { })); const { queryAllByText } = render( - -

{'should render'}

-
+ + +

{'should render'}

+
+
); await act(async () => { // wait for useEffect to run @@ -81,9 +106,11 @@ describe('health check', () => { })); const { queryByText, queryByRole } = render( - -

{'should render'}

-
+ + +

{'should render'}

+
+
); await act(async () => { // wait for useEffect to run @@ -108,9 +135,11 @@ describe('health check', () => { })); const { queryByText } = render( - -

{'should render'}

-
+ + +

{'should render'}

+
+
); await act(async () => { // wait for useEffect to run diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx index 009f582424765..c4d0b4976266e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx @@ -18,33 +18,39 @@ import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; import { AlertingFrameworkHealth } from '../../types'; import { health } from '../lib/alert_api'; import './health_check.scss'; +import { useHealthContext } from '../context/health_context'; interface Props { docLinks: Pick; http: HttpSetup; inFlyout?: boolean; + waitForCheck: boolean; } export const HealthCheck: React.FunctionComponent = ({ docLinks, http, children, + waitForCheck, inFlyout = false, }) => { + const { setLoadingHealthCheck } = useHealthContext(); const [alertingHealth, setAlertingHealth] = React.useState>(none); React.useEffect(() => { (async function () { + setLoadingHealthCheck(true); setAlertingHealth(some(await health({ http }))); + setLoadingHealthCheck(false); })(); - }, [http]); + }, [http, setLoadingHealthCheck]); const className = inFlyout ? 'alertingFlyoutHealthCheck' : 'alertingHealthCheck'; return pipe( alertingHealth, fold( - () => , + () => (waitForCheck ? : {children}), (healthCheck) => { return healthCheck?.isSufficientlySecure && healthCheck?.hasPermanentEncryptionKey ? ( {children} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/context/health_context.tsx b/x-pack/plugins/triggers_actions_ui/public/application/context/health_context.tsx new file mode 100644 index 0000000000000..de27f6db761e8 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/context/health_context.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; + +export interface HealthContextValue { + loadingHealthCheck: boolean; + setLoadingHealthCheck: (loading: boolean) => void; +} + +const defaultHealthContext: HealthContextValue = { + loadingHealthCheck: false, + setLoadingHealthCheck: (loading: boolean) => { + throw new Error( + 'setLoadingHealthCheck was not initialized, set it when you invoke the context' + ); + }, +}; + +const HealthContext = createContext(defaultHealthContext); + +export const HealthContextProvider = ({ children }: { children: React.ReactNode }) => { + const [loading, setLoading] = useState(false); + + const setLoadingHealthCheck = useCallback((isLoading: boolean) => { + setLoading(isLoading); + }, []); + + const value = useMemo(() => { + return { loadingHealthCheck: loading, setLoadingHealthCheck }; + }, [loading, setLoadingHealthCheck]); + + return {children}; +}; + +export const useHealthContext = () => { + const ctx = useContext(HealthContext); + if (!ctx) { + throw new Error('HealthContext has not been set.'); + } + return ctx; +}; 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 eb6b1ada3ba93..f009a04d40978 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx @@ -31,6 +31,7 @@ import { ActionsConnectorsList } from './sections/actions_connectors_list/compon import { AlertsList } from './sections/alerts_list/components/alerts_list'; import { PLUGIN } from './constants/plugin'; import { HealthCheck } from './components/health_check'; +import { HealthContextProvider } from './context/health_context'; interface MatchParams { section: Section; @@ -139,9 +140,11 @@ export const TriggersActionsUIHome: React.FunctionComponent ( - - - + + + + + )} /> )} @@ -149,9 +152,11 @@ export const TriggersActionsUIHome: React.FunctionComponent ( - - - + + + + + )} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx index 7be7e60c2e19c..763462ba6ebf4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx @@ -10,11 +10,6 @@ import { EuiTitle, EuiFlyoutHeader, EuiFlyout, - EuiFlyoutFooter, - EuiFlexGroup, - EuiFlexItem, - EuiButtonEmpty, - EuiButton, EuiFlyoutBody, EuiPortal, EuiBetaBadge, @@ -29,6 +24,8 @@ import { HealthCheck } from '../../components/health_check'; import { PLUGIN } from '../../constants/plugin'; import { ConfirmAlertSave } from './confirm_alert_save'; import { hasShowActionsCapability } from '../../lib/capabilities'; +import AlertAddFooter from './alert_add_footer'; +import { HealthContextProvider } from '../../context/health_context'; interface AlertAddProps { consumer: string; @@ -183,54 +180,37 @@ export const AlertAdd = ({ - - - + + + + + { + setIsSaving(true); + if (shouldConfirmSave) { + setIsConfirmAlertSaveModalOpen(true); + } else { + await saveAlertAndCloseFlyout(); + } + }} + onCancel={closeFlyout} /> - - - - - - {i18n.translate('xpack.triggersActionsUI.sections.alertAdd.cancelButtonLabel', { - defaultMessage: 'Cancel', - })} - - - - { - setIsSaving(true); - if (shouldConfirmSave) { - setIsConfirmAlertSaveModalOpen(true); - } else { - await saveAlertAndCloseFlyout(); - } - }} - > - - - - - - + + {isConfirmAlertSaveModalOpen && ( { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add_footer.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add_footer.tsx new file mode 100644 index 0000000000000..92e1198de8440 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add_footer.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useHealthContext } from '../../context/health_context'; + +interface AlertAddFooterProps { + isSaving: boolean; + hasErrors: boolean; + onSave: () => void; + onCancel: () => void; +} + +export const AlertAddFooter = ({ isSaving, hasErrors, onSave, onCancel }: AlertAddFooterProps) => { + const { loadingHealthCheck } = useHealthContext(); + + return ( + + + + + {i18n.translate('xpack.triggersActionsUI.sections.alertAddFooter.cancelButtonLabel', { + defaultMessage: 'Cancel', + })} + + + + + + + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { AlertAddFooter as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index b60aa04ee9f27..0435a4cc33cb8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -28,6 +28,7 @@ import { alertReducer } from './alert_reducer'; import { updateAlert } from '../../lib/alert_api'; import { HealthCheck } from '../../components/health_check'; import { PLUGIN } from '../../constants/plugin'; +import { HealthContextProvider } from '../../context/health_context'; interface AlertEditProps { initialAlert: Alert; @@ -135,74 +136,82 @@ export const AlertEdit = ({ initialAlert, onClose }: AlertEditProps) => { - - - {hasActionsDisabled && ( - - - - - )} - - - - - - - {i18n.translate('xpack.triggersActionsUI.sections.alertEdit.cancelButtonLabel', { - defaultMessage: 'Cancel', - })} - - - - { - setIsSaving(true); - const savedAlert = await onSaveAlert(); - setIsSaving(false); - if (savedAlert) { - closeFlyout(); - if (reloadAlerts) { - reloadAlerts(); - } - } - }} - > - + + + {hasActionsDisabled && ( + + - - - - - + + + )} + + + + + + + {i18n.translate( + 'xpack.triggersActionsUI.sections.alertEdit.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + )} + + + + { + setIsSaving(true); + const savedAlert = await onSaveAlert(); + setIsSaving(false); + if (savedAlert) { + closeFlyout(); + if (reloadAlerts) { + reloadAlerts(); + } + } + }} + > + + + + + + + ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 8800f149c033b..d2ca0abe566ad 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -105,9 +105,7 @@ export const AlertForm = ({ } = alertsContext; const canShowActions = hasShowActionsCapability(capabilities); - const [alertTypeModel, setAlertTypeModel] = useState( - alert.alertTypeId ? alertTypeRegistry.get(alert.alertTypeId) : null - ); + const [alertTypeModel, setAlertTypeModel] = useState(null); const [alertTypesIndex, setAlertTypesIndex] = useState(undefined); const [alertInterval, setAlertInterval] = useState( @@ -149,6 +147,10 @@ export const AlertForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + setAlertTypeModel(alert.alertTypeId ? alertTypeRegistry.get(alert.alertTypeId) : null); + }, [alert, alertTypeRegistry]); + const setAlertProperty = (key: string, value: any) => { dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); };