Skip to content

Commit

Permalink
[Alerting UI] Fixed a bad UX for xpack.actions.enabled is set as fa…
Browse files Browse the repository at this point in the history
…lse. UI should show the proper message instead of the endless spinner. (#89043)

* [Alerts][Actions] Changed isESOUsingEphemeralEncryptionKey determination. Set ESO plugin as an optional dependancy for actions and alerts plugins.

* fixed faling typechecks

* fixed faling typechecks

* fixed health framework status message

* fixed due to comments

* fixed faling test

* changed approach

* fixed due to comments

* fixed due to comments

* fixed tests

* fixed tests

* fixed tests

* fixed wrong commit

* fixed lang issue

* Fixed to remove eso check

* Fixed tests

* Fixed due to comments.
  • Loading branch information
YulNaumenko authored Jan 29, 2021
1 parent 46c9e64 commit 9ba3ee3
Show file tree
Hide file tree
Showing 15 changed files with 280 additions and 88 deletions.
3 changes: 0 additions & 3 deletions x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -21446,9 +21446,6 @@
"xpack.triggersActionsUI.components.healthCheck.encryptionErrorAfterKey": " kibana.ymlファイルで",
"xpack.triggersActionsUI.components.healthCheck.encryptionErrorBeforeKey": "アラートを作成するには、値を設定します ",
"xpack.triggersActionsUI.components.healthCheck.encryptionErrorTitle": "暗号化鍵を設定する必要があります",
"xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionError": "KibanaとElasticsearchの間でトランスポートレイヤーセキュリティを有効にし、kibana.ymlファイルで暗号化鍵を構成する必要があります。",
"xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorAction": "方法を学習",
"xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorTitle": "追加の設定が必要です",
"xpack.triggersActionsUI.components.healthCheck.tlsError": "アラートはAPIキーに依存し、キーを使用するにはElasticsearchとKibanaの間にTLSが必要です。",
"xpack.triggersActionsUI.components.healthCheck.tlsErrorAction": "TLSを有効にする方法をご覧ください。",
"xpack.triggersActionsUI.components.healthCheck.tlsErrorTitle": "トランスポートレイヤーセキュリティを有効にする必要があります",
Expand Down
3 changes: 0 additions & 3 deletions x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -21496,9 +21496,6 @@
"xpack.triggersActionsUI.components.healthCheck.encryptionErrorAfterKey": " 。",
"xpack.triggersActionsUI.components.healthCheck.encryptionErrorBeforeKey": "要创建告警,请在 kibana.yml 文件中设置以下项的值: ",
"xpack.triggersActionsUI.components.healthCheck.encryptionErrorTitle": "必须设置加密密钥",
"xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionError": "必须在 Kibana 和 Elasticsearch 之间启用传输层安全并在 kibana.yml 文件中配置加密密钥。",
"xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorAction": "了解操作方法",
"xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorTitle": "需要其他设置",
"xpack.triggersActionsUI.components.healthCheck.tlsError": "Alerting 功能依赖于 API 密钥,这需要在 Elasticsearch 与 Kibana 之间启用 TLS。",
"xpack.triggersActionsUI.components.healthCheck.tlsErrorAction": "了解如何启用 TLS。",
"xpack.triggersActionsUI.components.healthCheck.tlsErrorTitle": "必须启用传输层安全",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ describe('health check', () => {
});

it('renders children if keys are enabled', async () => {
useKibanaMock().services.http.get = jest
.fn()
.mockResolvedValue({ isSufficientlySecure: true, hasPermanentEncryptionKey: true });
useKibanaMock().services.http.get = jest.fn().mockResolvedValue({
isSufficientlySecure: true,
hasPermanentEncryptionKey: true,
isAlertsAvailable: true,
});
const { queryByText } = render(
<HealthContextProvider>
<HealthCheck waitForCheck={true}>
Expand All @@ -72,10 +74,11 @@ describe('health check', () => {
expect(queryByText('should render')).toBeInTheDocument();
});

test('renders warning if keys are disabled', async () => {
useKibanaMock().services.http.get = jest.fn().mockImplementationOnce(async () => ({
test('renders warning if TLS is required', async () => {
useKibanaMock().services.http.get = jest.fn().mockImplementation(async () => ({
isSufficientlySecure: false,
hasPermanentEncryptionKey: true,
isAlertsAvailable: true,
}));
const { queryAllByText } = render(
<HealthContextProvider>
Expand Down Expand Up @@ -104,9 +107,10 @@ describe('health check', () => {
});

test('renders warning if encryption key is ephemeral', async () => {
useKibanaMock().services.http.get = jest.fn().mockImplementationOnce(async () => ({
useKibanaMock().services.http.get = jest.fn().mockImplementation(async () => ({
isSufficientlySecure: true,
hasPermanentEncryptionKey: false,
isAlertsAvailable: true,
}));
const { queryByText, queryByRole } = render(
<HealthContextProvider>
Expand All @@ -121,7 +125,7 @@ describe('health check', () => {

const description = queryByRole(/banner/i);
expect(description!.textContent).toMatchInlineSnapshot(
`"To create an alert, set a value for xpack.encryptedSavedObjects.encryptionKey in your kibana.yml file. Learn how.(opens in a new tab or window)"`
`"To create an alert, set a value for xpack.encryptedSavedObjects.encryptionKey in your kibana.yml file and ensure the Encrypted Saved Objects plugin is enabled. Learn how.(opens in a new tab or window)"`
);

const action = queryByText(/Learn/i);
Expand All @@ -132,9 +136,10 @@ describe('health check', () => {
});

test('renders warning if encryption key is ephemeral and keys are disabled', async () => {
useKibanaMock().services.http.get = jest.fn().mockImplementationOnce(async () => ({
useKibanaMock().services.http.get = jest.fn().mockImplementation(async () => ({
isSufficientlySecure: false,
hasPermanentEncryptionKey: false,
isAlertsAvailable: true,
}));

const { queryByText } = render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,49 @@ import { i18n } from '@kbn/i18n';

import { EuiEmptyPrompt, EuiCode } from '@elastic/eui';
import { DocLinksStart } from 'kibana/public';
import { AlertingFrameworkHealth } from '../../types';
import { health } from '../lib/alert_api';
import { alertingFrameworkHealth } from '../lib/alert_api';
import './health_check.scss';
import { useHealthContext } from '../context/health_context';
import { useKibana } from '../../common/lib/kibana';
import { CenterJustifiedSpinner } from './center_justified_spinner';
import { triggersActionsUiHealth } from '../../common/lib/health_api';

interface Props {
inFlyout?: boolean;
waitForCheck: boolean;
}

interface HealthStatus {
isAlertsAvailable: boolean;
isSufficientlySecure: boolean;
hasPermanentEncryptionKey: boolean;
}

export const HealthCheck: React.FunctionComponent<Props> = ({
children,
waitForCheck,
inFlyout = false,
}) => {
const { http, docLinks } = useKibana().services;
const { setLoadingHealthCheck } = useHealthContext();
const [alertingHealth, setAlertingHealth] = React.useState<Option<AlertingFrameworkHealth>>(none);
const [alertingHealth, setAlertingHealth] = React.useState<Option<HealthStatus>>(none);

React.useEffect(() => {
(async function () {
setLoadingHealthCheck(true);
setAlertingHealth(some(await health({ http })));
const triggersActionsUiHealthStatus = await triggersActionsUiHealth({ http });
const healthStatus: HealthStatus = {
...triggersActionsUiHealthStatus,
isSufficientlySecure: false,
hasPermanentEncryptionKey: false,
};
if (healthStatus.isAlertsAvailable) {
const alertingHealthResult = await alertingFrameworkHealth({ http });
healthStatus.isSufficientlySecure = alertingHealthResult.isSufficientlySecure;
healthStatus.hasPermanentEncryptionKey = alertingHealthResult.hasPermanentEncryptionKey;
}

setAlertingHealth(some(healthStatus));
setLoadingHealthCheck(false);
})();
}, [http, setLoadingHealthCheck]);
Expand All @@ -60,6 +78,8 @@ export const HealthCheck: React.FunctionComponent<Props> = ({
(healthCheck) => {
return healthCheck?.isSufficientlySecure && healthCheck?.hasPermanentEncryptionKey ? (
<Fragment>{children}</Fragment>
) : !healthCheck.isAlertsAvailable ? (
<AlertsError docLinks={docLinks} className={className} />
) : !healthCheck.isSufficientlySecure && !healthCheck.hasPermanentEncryptionKey ? (
<TlsAndEncryptionError docLinks={docLinks} className={className} />
) : !healthCheck.hasPermanentEncryptionKey ? (
Expand All @@ -77,7 +97,7 @@ interface PromptErrorProps {
className?: string;
}

const TlsAndEncryptionError = ({
const EncryptionError = ({
// eslint-disable-next-line @typescript-eslint/naming-convention
docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION },
className,
Expand All @@ -90,27 +110,37 @@ const TlsAndEncryptionError = ({
title={
<h2>
<FormattedMessage
id="xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorTitle"
defaultMessage="Additional setup required"
id="xpack.triggersActionsUI.components.healthCheck.encryptionErrorTitle"
defaultMessage="Encrypted saved objects are not available"
/>
</h2>
}
body={
<div className={`${className}__body`}>
<p role="banner">
{i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionError', {
defaultMessage:
'You must enable Transport Layer Security between Kibana and Elasticsearch and configure an encryption key in your kibana.yml file. ',
})}
{i18n.translate(
'xpack.triggersActionsUI.components.healthCheck.encryptionErrorBeforeKey',
{
defaultMessage: 'To create an alert, set a value for ',
}
)}
<EuiCode>{'xpack.encryptedSavedObjects.encryptionKey'}</EuiCode>
{i18n.translate(
'xpack.triggersActionsUI.components.healthCheck.encryptionErrorAfterKey',
{
defaultMessage:
' in your kibana.yml file and ensure the Encrypted Saved Objects plugin is enabled. ',
}
)}
<EuiLink
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/alerting-getting-started.html#alerting-setup-prerequisites`}
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/alert-action-settings-kb.html#general-alert-action-settings`}
external
target="_blank"
>
{i18n.translate(
'xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorAction',
'xpack.triggersActionsUI.components.healthCheck.encryptionErrorAction',
{
defaultMessage: 'Learn how',
defaultMessage: 'Learn how.',
}
)}
</EuiLink>
Expand All @@ -120,7 +150,7 @@ const TlsAndEncryptionError = ({
/>
);

const EncryptionError = ({
const TlsError = ({
// eslint-disable-next-line @typescript-eslint/naming-convention
docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION },
className,
Expand All @@ -133,46 +163,73 @@ const EncryptionError = ({
title={
<h2>
<FormattedMessage
id="xpack.triggersActionsUI.components.healthCheck.encryptionErrorTitle"
defaultMessage="You must set an encryption key"
id="xpack.triggersActionsUI.components.healthCheck.tlsErrorTitle"
defaultMessage="You must enable Transport Layer Security"
/>
</h2>
}
body={
<div className={`${className}__body`}>
<p role="banner">
{i18n.translate(
'xpack.triggersActionsUI.components.healthCheck.encryptionErrorBeforeKey',
{
defaultMessage: 'To create an alert, set a value for ',
}
)}
<EuiCode>{'xpack.encryptedSavedObjects.encryptionKey'}</EuiCode>
{i18n.translate(
'xpack.triggersActionsUI.components.healthCheck.encryptionErrorAfterKey',
{
defaultMessage: ' in your kibana.yml file. ',
}
)}
{i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsError', {
defaultMessage:
'Alerting relies on API keys, which require TLS between Elasticsearch and Kibana. ',
})}
<EuiLink
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/alert-action-settings-kb.html#general-alert-action-settings`}
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/configuring-tls.html`}
external
target="_blank"
>
{i18n.translate(
'xpack.triggersActionsUI.components.healthCheck.encryptionErrorAction',
{
defaultMessage: 'Learn how.',
}
)}
{i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsErrorAction', {
defaultMessage: 'Learn how to enable TLS.',
})}
</EuiLink>
</p>
</div>
}
/>
);

const TlsError = ({
const AlertsError = ({
// eslint-disable-next-line @typescript-eslint/naming-convention
docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION },
className,
}: PromptErrorProps) => (
<EuiEmptyPrompt
iconType="watchesApp"
data-test-subj="alertsNeededEmptyPrompt"
className={className}
titleSize="xs"
title={
<h2>
<FormattedMessage
id="xpack.triggersActionsUI.components.healthCheck.alertsErrorTitle"
defaultMessage="You must enable Alerts and Actions"
/>
</h2>
}
body={
<div className={`${className}__body`}>
<p role="banner">
{i18n.translate('xpack.triggersActionsUI.components.healthCheck.alertsError', {
defaultMessage: 'To create an alert, set alerts and actions plugins enabled. ',
})}
<EuiLink
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/alert-action-settings-kb.html`}
external
target="_blank"
>
{i18n.translate('xpack.triggersActionsUI.components.healthCheck.alertsErrorAction', {
defaultMessage: 'Learn how to enable Alerts and Actions.',
})}
</EuiLink>
</p>
</div>
}
/>
);

const TlsAndEncryptionError = ({
// eslint-disable-next-line @typescript-eslint/naming-convention
docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION },
className,
Expand All @@ -185,26 +242,29 @@ const TlsError = ({
title={
<h2>
<FormattedMessage
id="xpack.triggersActionsUI.components.healthCheck.tlsErrorTitle"
defaultMessage="You must enable Transport Layer Security"
id="xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorTitle"
defaultMessage="Additional setup required"
/>
</h2>
}
body={
<div className={`${className}__body`}>
<p role="banner">
{i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsError', {
{i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionError', {
defaultMessage:
'Alerting relies on API keys, which require TLS between Elasticsearch and Kibana. ',
'You must enable Transport Layer Security between Kibana and Elasticsearch and configure an encryption key in your kibana.yml file. ',
})}
<EuiLink
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/configuring-tls.html`}
href={`${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/alerting-getting-started.html#alerting-setup-prerequisites`}
external
target="_blank"
>
{i18n.translate('xpack.triggersActionsUI.components.healthCheck.tlsErrorAction', {
defaultMessage: 'Learn how to enable TLS.',
})}
{i18n.translate(
'xpack.triggersActionsUI.components.healthCheck.tlsAndEncryptionErrorAction',
{
defaultMessage: 'Learn how',
}
)}
</EuiLink>
</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
updateAlert,
muteAlertInstance,
unmuteAlertInstance,
health,
alertingFrameworkHealth,
mapFiltersToKql,
} from './alert_api';
import uuid from 'uuid';
Expand Down Expand Up @@ -801,9 +801,9 @@ describe('unmuteAlerts', () => {
});
});

describe('health', () => {
test('should call health API', async () => {
const result = await health({ http });
describe('alertingFrameworkHealth', () => {
test('should call alertingFrameworkHealth API', async () => {
const result = await alertingFrameworkHealth({ http });
expect(result).toEqual(undefined);
expect(http.get.mock.calls).toMatchInlineSnapshot(`
Array [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ export async function unmuteAlerts({
await Promise.all(ids.map((id) => unmuteAlert({ id, http })));
}

export async function health({ http }: { http: HttpSetup }): Promise<AlertingFrameworkHealth> {
export async function alertingFrameworkHealth({
http,
}: {
http: HttpSetup;
}): Promise<AlertingFrameworkHealth> {
return await http.get(`${BASE_ALERT_API_PATH}/_health`);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ jest.mock('../../../common/lib/kibana');

jest.mock('../../lib/alert_api', () => ({
loadAlertTypes: jest.fn(),
health: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true })),
alertingFrameworkHealth: jest.fn(() => ({
isSufficientlySecure: true,
hasPermanentEncryptionKey: true,
})),
}));

jest.mock('../../../common/lib/health_api', () => ({
triggersActionsUiHealth: jest.fn(() => ({ isAlertsAvailable: true })),
}));

const actionTypeRegistry = actionTypeRegistryMock.create();
Expand Down
Loading

0 comments on commit 9ba3ee3

Please sign in to comment.