Skip to content

Commit

Permalink
[Alerting UI] Don't wait for health check before showing Create Alert…
Browse files Browse the repository at this point in the history
… flyout (#80996)

* 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é <[email protected]>

* Applying i18n fix

Co-authored-by: Kibana Machine <[email protected]>
Co-authored-by: Mike Côté <[email protected]>
  • Loading branch information
3 people authored Oct 26, 2020
1 parent ac640f1 commit 7e34bf2
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 149 deletions.
2 changes: 0 additions & 2 deletions x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -20272,15 +20272,13 @@
"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": "アラートの作成",
"xpack.triggersActionsUI.sections.alertAdd.geoThreshold.closePopoverLabel": "閉じる",
"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": "インデックスを選択してください",
Expand Down
2 changes: 0 additions & 2 deletions x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -20292,15 +20292,13 @@
"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": "创建告警",
"xpack.triggersActionsUI.sections.alertAdd.geoThreshold.closePopoverLabel": "关闭",
"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": "选择索引",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' };

Expand All @@ -20,9 +21,11 @@ describe('health check', () => {
http.get.mockImplementationOnce(() => new Promise(() => {}));

const { queryByText, container } = render(
<HealthCheck http={http} docLinks={docLinks}>
<p>{'shouldnt render'}</p>
</HealthCheck>
<HealthContextProvider>
<HealthCheck http={http} docLinks={docLinks} waitForCheck={true}>
<p>{'shouldnt render'}</p>
</HealthCheck>
</HealthContextProvider>
);
await act(async () => {
// wait for useEffect to run
Expand All @@ -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(
<HealthContextProvider>
<HealthCheck http={http} docLinks={docLinks} waitForCheck={false}>
<p>{'should render'}</p>
</HealthCheck>
</HealthContextProvider>
);
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(
<HealthCheck http={http} docLinks={docLinks}>
<p>{'should render'}</p>
</HealthCheck>
<HealthContextProvider>
<HealthCheck http={http} docLinks={docLinks} waitForCheck={true}>
<p>{'should render'}</p>
</HealthCheck>
</HealthContextProvider>
);
await act(async () => {
// wait for useEffect to run
Expand All @@ -53,9 +76,11 @@ describe('health check', () => {
}));

const { queryAllByText } = render(
<HealthCheck http={http} docLinks={docLinks}>
<p>{'should render'}</p>
</HealthCheck>
<HealthContextProvider>
<HealthCheck http={http} docLinks={docLinks} waitForCheck={true}>
<p>{'should render'}</p>
</HealthCheck>
</HealthContextProvider>
);
await act(async () => {
// wait for useEffect to run
Expand All @@ -81,9 +106,11 @@ describe('health check', () => {
}));

const { queryByText, queryByRole } = render(
<HealthCheck http={http} docLinks={docLinks}>
<p>{'should render'}</p>
</HealthCheck>
<HealthContextProvider>
<HealthCheck http={http} docLinks={docLinks} waitForCheck={true}>
<p>{'should render'}</p>
</HealthCheck>
</HealthContextProvider>
);
await act(async () => {
// wait for useEffect to run
Expand All @@ -108,9 +135,11 @@ describe('health check', () => {
}));

const { queryByText } = render(
<HealthCheck http={http} docLinks={docLinks}>
<p>{'should render'}</p>
</HealthCheck>
<HealthContextProvider>
<HealthCheck http={http} docLinks={docLinks} waitForCheck={true}>
<p>{'should render'}</p>
</HealthCheck>
</HealthContextProvider>
);
await act(async () => {
// wait for useEffect to run
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<DocLinksStart, 'ELASTIC_WEBSITE_URL' | 'DOC_LINK_VERSION'>;
http: HttpSetup;
inFlyout?: boolean;
waitForCheck: boolean;
}

export const HealthCheck: React.FunctionComponent<Props> = ({
docLinks,
http,
children,
waitForCheck,
inFlyout = false,
}) => {
const { setLoadingHealthCheck } = useHealthContext();
const [alertingHealth, setAlertingHealth] = React.useState<Option<AlertingFrameworkHealth>>(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(
() => <EuiLoadingSpinner size="m" />,
() => (waitForCheck ? <EuiLoadingSpinner size="m" /> : <Fragment>{children}</Fragment>),
(healthCheck) => {
return healthCheck?.isSufficientlySecure && healthCheck?.hasPermanentEncryptionKey ? (
<Fragment>{children}</Fragment>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<HealthContextValue>(defaultHealthContext);

export const HealthContextProvider = ({ children }: { children: React.ReactNode }) => {
const [loading, setLoading] = useState<boolean>(false);

const setLoadingHealthCheck = useCallback((isLoading: boolean) => {
setLoading(isLoading);
}, []);

const value = useMemo(() => {
return { loadingHealthCheck: loading, setLoadingHealthCheck };
}, [loading, setLoadingHealthCheck]);

return <HealthContext.Provider value={value}>{children}</HealthContext.Provider>;
};

export const useHealthContext = () => {
const ctx = useContext(HealthContext);
if (!ctx) {
throw new Error('HealthContext has not been set.');
}
return ctx;
};
17 changes: 11 additions & 6 deletions x-pack/plugins/triggers_actions_ui/public/application/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -139,19 +140,23 @@ export const TriggersActionsUIHome: React.FunctionComponent<RouteComponentProps<
exact
path={routeToConnectors}
component={() => (
<HealthCheck docLinks={docLinks} http={http}>
<ActionsConnectorsList />
</HealthCheck>
<HealthContextProvider>
<HealthCheck docLinks={docLinks} http={http} waitForCheck={true}>
<ActionsConnectorsList />
</HealthCheck>
</HealthContextProvider>
)}
/>
)}
<Route
exact
path={routeToAlerts}
component={() => (
<HealthCheck docLinks={docLinks} http={http}>
<AlertsList />
</HealthCheck>
<HealthContextProvider>
<HealthCheck docLinks={docLinks} http={http} inFlyout={true} waitForCheck={true}>
<AlertsList />
</HealthCheck>
</HealthContextProvider>
)}
/>
</Switch>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ import {
EuiTitle,
EuiFlyoutHeader,
EuiFlyout,
EuiFlyoutFooter,
EuiFlexGroup,
EuiFlexItem,
EuiButtonEmpty,
EuiButton,
EuiFlyoutBody,
EuiPortal,
EuiBetaBadge,
Expand All @@ -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;
Expand Down Expand Up @@ -183,54 +180,37 @@ export const AlertAdd = ({
</h3>
</EuiTitle>
</EuiFlyoutHeader>
<HealthCheck docLinks={docLinks} http={http} inFlyout={true}>
<EuiFlyoutBody>
<AlertForm
alert={alert}
dispatch={dispatch}
errors={errors}
canChangeTrigger={canChangeTrigger}
operation={i18n.translate('xpack.triggersActionsUI.sections.alertAdd.operationName', {
defaultMessage: 'create',
})}
<HealthContextProvider>
<HealthCheck docLinks={docLinks} http={http} inFlyout={true} waitForCheck={false}>
<EuiFlyoutBody>
<AlertForm
alert={alert}
dispatch={dispatch}
errors={errors}
canChangeTrigger={canChangeTrigger}
operation={i18n.translate(
'xpack.triggersActionsUI.sections.alertAdd.operationName',
{
defaultMessage: 'create',
}
)}
/>
</EuiFlyoutBody>
<AlertAddFooter
isSaving={isSaving}
hasErrors={hasErrors || hasActionErrors}
onSave={async () => {
setIsSaving(true);
if (shouldConfirmSave) {
setIsConfirmAlertSaveModalOpen(true);
} else {
await saveAlertAndCloseFlyout();
}
}}
onCancel={closeFlyout}
/>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty data-test-subj="cancelSaveAlertButton" onClick={closeFlyout}>
{i18n.translate('xpack.triggersActionsUI.sections.alertAdd.cancelButtonLabel', {
defaultMessage: 'Cancel',
})}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
fill
color="secondary"
data-test-subj="saveAlertButton"
type="submit"
iconType="check"
isDisabled={hasErrors || hasActionErrors}
isLoading={isSaving}
onClick={async () => {
setIsSaving(true);
if (shouldConfirmSave) {
setIsConfirmAlertSaveModalOpen(true);
} else {
await saveAlertAndCloseFlyout();
}
}}
>
<FormattedMessage
id="xpack.triggersActionsUI.sections.alertAdd.saveButtonLabel"
defaultMessage="Save"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</HealthCheck>
</HealthCheck>
</HealthContextProvider>
{isConfirmAlertSaveModalOpen && (
<ConfirmAlertSave
onConfirm={async () => {
Expand Down
Loading

0 comments on commit 7e34bf2

Please sign in to comment.