[Alerting] License Errors on Alert List View (#89920)
* Adding tooltips to alert list and modal for license upgrade * Fixing typings * Custom License Error status. Moving modal to alerts list page * Adding unit test * Cleanup * Unit tests * Removing tooltip from alert name * License * PR fixes * Updating modal wording * Updating license state error message * i18n fix * Fixing functional test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
ce441bdc32
commit
3e91bc728d
|
@ -248,7 +248,7 @@ describe('ensureLicenseForAlertType()', () => {
|
|||
expect(() =>
|
||||
licenseState.ensureLicenseForAlertType(alertType)
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Alert test is disabled because it requires a Gold license. Contact your administrator to upgrade your license."`
|
||||
`"Alert test is disabled because it requires a Gold license. Go to License Management to view upgrade options."`
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import Boom from '@hapi/boom';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import type { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import { assertNever } from '@kbn/std';
|
||||
import { capitalize } from 'lodash';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { LicensingPluginStart } from '../../../licensing/server';
|
||||
import { ILicense, LicenseType } from '../../../licensing/common/types';
|
||||
|
@ -190,8 +191,11 @@ export class LicenseState {
|
|||
throw new AlertTypeDisabledError(
|
||||
i18n.translate('xpack.alerts.serverSideErrors.invalidLicenseErrorMessage', {
|
||||
defaultMessage:
|
||||
'Alert {alertTypeId} is disabled because it requires a Gold license. Contact your administrator to upgrade your license.',
|
||||
values: { alertTypeId: alertType.id },
|
||||
'Alert {alertTypeId} is disabled because it requires a {licenseType} license. Go to License Management to view upgrade options.',
|
||||
values: {
|
||||
alertTypeId: alertType.id,
|
||||
licenseType: capitalize(alertType.minimumLicenseRequired),
|
||||
},
|
||||
}),
|
||||
'license_invalid'
|
||||
);
|
||||
|
|
|
@ -4830,7 +4830,6 @@
|
|||
"xpack.alerts.server.healthStatus.degraded": "アラートフレームワークは劣化しました",
|
||||
"xpack.alerts.server.healthStatus.unavailable": "アラートフレームワークを使用できません",
|
||||
"xpack.alerts.serverSideErrors.expirerdLicenseErrorMessage": "{licenseType} ライセンスの期限が切れたのでアラートタイプ {alertTypeId} は無効です。",
|
||||
"xpack.alerts.serverSideErrors.invalidLicenseErrorMessage": "アラート {alertTypeId} は無効です。Gold ライセンスが必要です。ライセンスをアップグレードするには、管理者に問い合わせてください。",
|
||||
"xpack.alerts.serverSideErrors.unavailableLicenseErrorMessage": "現時点でライセンス情報を入手できないため、アラートタイプ {alertTypeId} は無効です。",
|
||||
"xpack.alerts.serverSideErrors.unavailableLicenseInformationErrorMessage": "アラートを利用できません。現在ライセンス情報が利用できません。",
|
||||
"xpack.apm.a.thresholdMet": "しきい値一致",
|
||||
|
|
|
@ -4836,7 +4836,6 @@
|
|||
"xpack.alerts.server.healthStatus.degraded": "告警框架已降级",
|
||||
"xpack.alerts.server.healthStatus.unavailable": "告警框架不可用",
|
||||
"xpack.alerts.serverSideErrors.expirerdLicenseErrorMessage": "告警类型 {alertTypeId} 已禁用,因为您的{licenseType}许可证已过期。",
|
||||
"xpack.alerts.serverSideErrors.invalidLicenseErrorMessage": "告警 {alertTypeId} 已禁用,因为它需要黄金级许可证。请联系管理员升级您的许可证。",
|
||||
"xpack.alerts.serverSideErrors.unavailableLicenseErrorMessage": "告警类型 {alertTypeId} 已禁用,因为许可证信息当前不可用。",
|
||||
"xpack.alerts.serverSideErrors.unavailableLicenseInformationErrorMessage": "告警不可用 - 许可信息当前不可用。",
|
||||
"xpack.apm.a.thresholdMet": "已达到阈值",
|
||||
|
|
|
@ -127,11 +127,16 @@ describe('alerts_list component empty', () => {
|
|||
|
||||
wrapper.find('button[data-test-subj="createFirstAlertButton"]').simulate('click');
|
||||
|
||||
// When the AlertAdd component is rendered, it waits for the healthcheck to resolve
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000);
|
||||
await act(async () => {
|
||||
// When the AlertAdd component is rendered, it waits for the healthcheck to resolve
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000);
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('AlertAdd').exists()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
@ -139,104 +144,131 @@ describe('alerts_list component empty', () => {
|
|||
describe('alerts_list component with items', () => {
|
||||
let wrapper: ReactWrapper<any>;
|
||||
|
||||
const mockedAlertsData = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'test alert',
|
||||
tags: ['tag1'],
|
||||
enabled: true,
|
||||
alertTypeId: 'test_alert_type',
|
||||
schedule: { interval: '5d' },
|
||||
actions: [],
|
||||
params: { name: 'test alert type name' },
|
||||
scheduledTaskId: null,
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'active',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'test alert ok',
|
||||
tags: ['tag1'],
|
||||
enabled: true,
|
||||
alertTypeId: 'test_alert_type',
|
||||
schedule: { interval: '5d' },
|
||||
actions: [],
|
||||
params: { name: 'test alert type name' },
|
||||
scheduledTaskId: null,
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'ok',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'test alert pending',
|
||||
tags: ['tag1'],
|
||||
enabled: true,
|
||||
alertTypeId: 'test_alert_type',
|
||||
schedule: { interval: '5d' },
|
||||
actions: [],
|
||||
params: { name: 'test alert type name' },
|
||||
scheduledTaskId: null,
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'pending',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'test alert error',
|
||||
tags: ['tag1'],
|
||||
enabled: true,
|
||||
alertTypeId: 'test_alert_type',
|
||||
schedule: { interval: '5d' },
|
||||
actions: [{ id: 'test', group: 'alert', params: { message: 'test' } }],
|
||||
params: { name: 'test alert type name' },
|
||||
scheduledTaskId: null,
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'error',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
error: {
|
||||
reason: AlertExecutionStatusErrorReasons.Unknown,
|
||||
message: 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'test alert license error',
|
||||
tags: ['tag1'],
|
||||
enabled: true,
|
||||
alertTypeId: 'test_alert_type',
|
||||
schedule: { interval: '5d' },
|
||||
actions: [{ id: 'test', group: 'alert', params: { message: 'test' } }],
|
||||
params: { name: 'test alert type name' },
|
||||
scheduledTaskId: null,
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'error',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
error: {
|
||||
reason: AlertExecutionStatusErrorReasons.License,
|
||||
message: 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
async function setup() {
|
||||
loadAlerts.mockResolvedValue({
|
||||
page: 1,
|
||||
perPage: 10000,
|
||||
total: 4,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'test alert',
|
||||
tags: ['tag1'],
|
||||
enabled: true,
|
||||
alertTypeId: 'test_alert_type',
|
||||
schedule: { interval: '5d' },
|
||||
actions: [],
|
||||
params: { name: 'test alert type name' },
|
||||
scheduledTaskId: null,
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'active',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'test alert ok',
|
||||
tags: ['tag1'],
|
||||
enabled: true,
|
||||
alertTypeId: 'test_alert_type',
|
||||
schedule: { interval: '5d' },
|
||||
actions: [],
|
||||
params: { name: 'test alert type name' },
|
||||
scheduledTaskId: null,
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'ok',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'test alert pending',
|
||||
tags: ['tag1'],
|
||||
enabled: true,
|
||||
alertTypeId: 'test_alert_type',
|
||||
schedule: { interval: '5d' },
|
||||
actions: [],
|
||||
params: { name: 'test alert type name' },
|
||||
scheduledTaskId: null,
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'pending',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
error: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'test alert error',
|
||||
tags: ['tag1'],
|
||||
enabled: true,
|
||||
alertTypeId: 'test_alert_type',
|
||||
schedule: { interval: '5d' },
|
||||
actions: [{ id: 'test', group: 'alert', params: { message: 'test' } }],
|
||||
params: { name: 'test alert type name' },
|
||||
scheduledTaskId: null,
|
||||
createdBy: null,
|
||||
updatedBy: null,
|
||||
apiKeyOwner: null,
|
||||
throttle: '1m',
|
||||
muteAll: false,
|
||||
mutedInstanceIds: [],
|
||||
executionStatus: {
|
||||
status: 'error',
|
||||
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
|
||||
error: {
|
||||
reason: AlertExecutionStatusErrorReasons.Unknown,
|
||||
message: 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
data: mockedAlertsData,
|
||||
});
|
||||
loadActionTypes.mockResolvedValue([
|
||||
{
|
||||
|
@ -271,21 +303,66 @@ describe('alerts_list component with items', () => {
|
|||
it('renders table of alerts', async () => {
|
||||
await setup();
|
||||
expect(wrapper.find('EuiBasicTable')).toHaveLength(1);
|
||||
expect(wrapper.find('EuiTableRow')).toHaveLength(4);
|
||||
expect(wrapper.find('[data-test-subj="alertsTableCell-status"]').length).toBeGreaterThan(0);
|
||||
expect(wrapper.find('[data-test-subj="alertStatus-active"]').length).toBeGreaterThan(0);
|
||||
expect(wrapper.find('[data-test-subj="alertStatus-error"]').length).toBeGreaterThan(0);
|
||||
expect(wrapper.find('[data-test-subj="alertStatus-ok"]').length).toBeGreaterThan(0);
|
||||
expect(wrapper.find('[data-test-subj="alertStatus-pending"]').length).toBeGreaterThan(0);
|
||||
expect(wrapper.find('[data-test-subj="alertStatus-unknown"]').length).toBe(0);
|
||||
expect(wrapper.find('EuiTableRow')).toHaveLength(mockedAlertsData.length);
|
||||
expect(wrapper.find('EuiTableRowCell[data-test-subj="alertsTableCell-status"]').length).toEqual(
|
||||
mockedAlertsData.length
|
||||
);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="alertStatus-active"]').length).toEqual(1);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="alertStatus-ok"]').length).toEqual(1);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="alertStatus-pending"]').length).toEqual(1);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="alertStatus-unknown"]').length).toEqual(0);
|
||||
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="alertStatus-error"]').length).toEqual(2);
|
||||
expect(wrapper.find('[data-test-subj="alertStatus-error-tooltip"]').length).toEqual(2);
|
||||
expect(
|
||||
wrapper.find('EuiButtonEmpty[data-test-subj="alertStatus-error-license-fix"]').length
|
||||
).toEqual(1);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="refreshAlertsButton"]').exists()).toBeTruthy();
|
||||
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="alertStatus-error"]').first().text()).toEqual(
|
||||
'Error'
|
||||
);
|
||||
expect(wrapper.find('EuiHealth[data-test-subj="alertStatus-error"]').last().text()).toEqual(
|
||||
'License Error'
|
||||
);
|
||||
});
|
||||
|
||||
it('loads alerts when refresh button is clicked', async () => {
|
||||
await setup();
|
||||
wrapper.find('[data-test-subj="refreshAlertsButton"]').first().simulate('click');
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(loadAlerts).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders license errors and manage license modal on click', async () => {
|
||||
global.open = jest.fn();
|
||||
await setup();
|
||||
expect(wrapper.find('ManageLicenseModal').exists()).toBeFalsy();
|
||||
expect(
|
||||
wrapper.find('EuiButtonEmpty[data-test-subj="alertStatus-error-license-fix"]').length
|
||||
).toEqual(1);
|
||||
wrapper
|
||||
.find('EuiButtonEmpty[data-test-subj="alertStatus-error-license-fix"]')
|
||||
.simulate('click');
|
||||
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(wrapper.find('ManageLicenseModal').exists()).toBeTruthy();
|
||||
expect(wrapper.find('EuiButton[data-test-subj="confirmModalConfirmButton"]').text()).toEqual(
|
||||
'Manage license'
|
||||
);
|
||||
wrapper.find('EuiButton[data-test-subj="confirmModalConfirmButton"]').simulate('click');
|
||||
expect(global.open).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('alerts_list component empty with show only capability', () => {
|
||||
|
@ -308,7 +385,9 @@ describe('alerts_list component empty with show only capability', () => {
|
|||
name: 'Test2',
|
||||
},
|
||||
]);
|
||||
loadAlertTypes.mockResolvedValue([{ id: 'test_alert_type', name: 'some alert type' }]);
|
||||
loadAlertTypes.mockResolvedValue([
|
||||
{ id: 'test_alert_type', name: 'some alert type', authorizedConsumers: {} },
|
||||
]);
|
||||
loadAllActions.mockResolvedValue([]);
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useKibanaMock().services.alertTypeRegistry = alertTypeRegistry;
|
||||
|
|
|
@ -53,14 +53,15 @@ import {
|
|||
AlertExecutionStatus,
|
||||
AlertExecutionStatusValues,
|
||||
ALERTS_FEATURE_ID,
|
||||
AlertExecutionStatusErrorReasons,
|
||||
} from '../../../../../../alerts/common';
|
||||
import { hasAllPrivilege } from '../../../lib/capabilities';
|
||||
import { alertsStatusesTranslationsMapping } from '../translations';
|
||||
import { alertsStatusesTranslationsMapping, ALERT_STATUS_LICENSE_ERROR } from '../translations';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { checkAlertTypeEnabled } from '../../../lib/check_alert_type_enabled';
|
||||
import { DEFAULT_HIDDEN_ACTION_TYPES } from '../../../../common/constants';
|
||||
import './alerts_list.scss';
|
||||
import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner';
|
||||
import { ManageLicenseModal } from './manage_license_modal';
|
||||
|
||||
const ENTER_KEY = 13;
|
||||
|
||||
|
@ -97,7 +98,11 @@ export const AlertsList: React.FunctionComponent = () => {
|
|||
const [actionTypesFilter, setActionTypesFilter] = useState<string[]>([]);
|
||||
const [alertStatusesFilter, setAlertStatusesFilter] = useState<string[]>([]);
|
||||
const [alertFlyoutVisible, setAlertFlyoutVisibility] = useState<boolean>(false);
|
||||
const [dissmissAlertErrors, setDissmissAlertErrors] = useState<boolean>(false);
|
||||
const [dismissAlertErrors, setDismissAlertErrors] = useState<boolean>(false);
|
||||
const [manageLicenseModalOpts, setManageLicenseModalOpts] = useState<{
|
||||
licenseType: string;
|
||||
alertTypeId: string;
|
||||
} | null>(null);
|
||||
const [alertsStatusesTotal, setAlertsStatusesTotal] = useState<Record<string, number>>(
|
||||
AlertExecutionStatusValues.reduce(
|
||||
(prev: Record<string, number>, status: string) =>
|
||||
|
@ -238,25 +243,64 @@ export const AlertsList: React.FunctionComponent = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const renderAlertExecutionStatus = (
|
||||
executionStatus: AlertExecutionStatus,
|
||||
item: AlertTableItem
|
||||
) => {
|
||||
const healthColor = getHealthColor(executionStatus.status);
|
||||
const tooltipMessage =
|
||||
executionStatus.status === 'error' ? `Error: ${executionStatus?.error?.message}` : null;
|
||||
const isLicenseError =
|
||||
executionStatus.error?.reason === AlertExecutionStatusErrorReasons.License;
|
||||
const statusMessage = isLicenseError
|
||||
? ALERT_STATUS_LICENSE_ERROR
|
||||
: alertsStatusesTranslationsMapping[executionStatus.status];
|
||||
|
||||
const health = (
|
||||
<EuiHealth data-test-subj={`alertStatus-${executionStatus.status}`} color={healthColor}>
|
||||
{statusMessage}
|
||||
</EuiHealth>
|
||||
);
|
||||
|
||||
const healthWithTooltip = tooltipMessage ? (
|
||||
<EuiToolTip
|
||||
data-test-subj="alertStatus-error-tooltip"
|
||||
position="top"
|
||||
content={tooltipMessage}
|
||||
>
|
||||
{health}
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
health
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>{healthWithTooltip}</EuiFlexItem>
|
||||
{isLicenseError && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
data-test-subj="alertStatus-error-license-fix"
|
||||
onClick={() =>
|
||||
setManageLicenseModalOpts({
|
||||
licenseType: alertTypesState.data.get(item.alertTypeId)?.minimumLicenseRequired!,
|
||||
alertTypeId: item.alertTypeId,
|
||||
})
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertsList.fixLicenseLink"
|
||||
defaultMessage="Fix"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const alertsTableColumns = [
|
||||
{
|
||||
field: 'executionStatus',
|
||||
name: i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.statusTitle',
|
||||
{ defaultMessage: 'Status' }
|
||||
),
|
||||
sortable: false,
|
||||
truncateText: false,
|
||||
'data-test-subj': 'alertsTableCell-status',
|
||||
render: (executionStatus: AlertExecutionStatus) => {
|
||||
const healthColor = getHealthColor(executionStatus.status);
|
||||
return (
|
||||
<EuiHealth data-test-subj={`alertStatus-${executionStatus.status}`} color={healthColor}>
|
||||
{alertsStatusesTranslationsMapping[executionStatus.status]}
|
||||
</EuiHealth>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
name: i18n.translate(
|
||||
|
@ -265,12 +309,10 @@ export const AlertsList: React.FunctionComponent = () => {
|
|||
),
|
||||
sortable: false,
|
||||
truncateText: true,
|
||||
width: '35%',
|
||||
'data-test-subj': 'alertsTableCell-name',
|
||||
render: (name: string, alert: AlertTableItem) => {
|
||||
const checkEnabledResult = checkAlertTypeEnabled(
|
||||
alertTypesState.data.get(alert.alertTypeId)
|
||||
);
|
||||
const link = (
|
||||
return (
|
||||
<EuiLink
|
||||
title={name}
|
||||
onClick={() => {
|
||||
|
@ -280,17 +322,20 @@ export const AlertsList: React.FunctionComponent = () => {
|
|||
{name}
|
||||
</EuiLink>
|
||||
);
|
||||
return checkEnabledResult.isEnabled ? (
|
||||
link
|
||||
) : (
|
||||
<EuiToolTip
|
||||
position="top"
|
||||
data-test-subj={`${alert.id}-disabledTooltip`}
|
||||
content={checkEnabledResult.message}
|
||||
>
|
||||
{link}
|
||||
</EuiToolTip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'executionStatus',
|
||||
name: i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.statusTitle',
|
||||
{ defaultMessage: 'Status' }
|
||||
),
|
||||
sortable: false,
|
||||
truncateText: false,
|
||||
width: '150px',
|
||||
'data-test-subj': 'alertsTableCell-status',
|
||||
render: (executionStatus: AlertExecutionStatus, item: AlertTableItem) => {
|
||||
return renderAlertExecutionStatus(executionStatus, item);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -492,7 +537,7 @@ export const AlertsList: React.FunctionComponent = () => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
{!dissmissAlertErrors && alertsStatusesTotal.error > 0 ? (
|
||||
{!dismissAlertErrors && alertsStatusesTotal.error > 0 ? (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut
|
||||
|
@ -521,7 +566,7 @@ export const AlertsList: React.FunctionComponent = () => {
|
|||
defaultMessage="View"
|
||||
/>
|
||||
</EuiButton>
|
||||
<EuiButtonEmpty color="danger" onClick={() => setDissmissAlertErrors(true)}>
|
||||
<EuiButtonEmpty color="danger" onClick={() => setDismissAlertErrors(true)}>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertsList.dismissBunnerButtonLabel"
|
||||
defaultMessage="Dismiss"
|
||||
|
@ -639,6 +684,17 @@ export const AlertsList: React.FunctionComponent = () => {
|
|||
setPage(changedPage);
|
||||
}}
|
||||
/>
|
||||
{manageLicenseModalOpts && (
|
||||
<ManageLicenseModal
|
||||
licenseType={manageLicenseModalOpts.licenseType}
|
||||
alertTypeId={manageLicenseModalOpts.alertTypeId}
|
||||
onConfirm={() => {
|
||||
window.open(`${http.basePath.get()}/app/management/stack/license_management`, '_blank');
|
||||
setManageLicenseModalOpts(null);
|
||||
}}
|
||||
onCancel={() => setManageLicenseModalOpts(null)}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui';
|
||||
import { capitalize } from 'lodash';
|
||||
|
||||
interface Props {
|
||||
licenseType: string;
|
||||
alertTypeId: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export const ManageLicenseModal: React.FC<Props> = ({
|
||||
licenseType,
|
||||
alertTypeId,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}) => {
|
||||
const licenseRequired = capitalize(licenseType);
|
||||
return (
|
||||
<EuiOverlayMask>
|
||||
<EuiConfirmModal
|
||||
title={i18n.translate('xpack.triggersActionsUI.sections.manageLicense.manageLicenseTitle', {
|
||||
defaultMessage: '{licenseRequired} license required',
|
||||
values: { licenseRequired },
|
||||
})}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
confirmButtonText={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.manageLicense.manageLicenseConfirmButtonText',
|
||||
{
|
||||
defaultMessage: 'Manage license',
|
||||
}
|
||||
)}
|
||||
cancelButtonText={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.manageLicense.manageLicenseCancelButtonText',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
}
|
||||
)}
|
||||
defaultFocusedButton="confirm"
|
||||
data-test-subj="manageLicenseModal"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.manageLicense.manageLicenseMessage"
|
||||
defaultMessage="Alert {alertTypeId} is disabled because it requires a {licenseRequired} license. Continue to License Management to view upgrade options."
|
||||
values={{ alertTypeId, licenseRequired }}
|
||||
/>
|
||||
</p>
|
||||
</EuiConfirmModal>
|
||||
</EuiOverlayMask>
|
||||
);
|
||||
};
|
|
@ -28,6 +28,13 @@ export const ALERT_STATUS_ERROR = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const ALERT_STATUS_LICENSE_ERROR = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertsList.alertStatusLicenseError',
|
||||
{
|
||||
defaultMessage: 'License Error',
|
||||
}
|
||||
);
|
||||
|
||||
export const ALERT_STATUS_PENDING = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertsList.alertStatusPending',
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ export default function emailTest({ getService }: FtrProviderContext) {
|
|||
statusCode: 403,
|
||||
error: 'Forbidden',
|
||||
message:
|
||||
'Alert test.gold.noop is disabled because it requires a Gold license. Contact your administrator to upgrade your license.',
|
||||
'Alert test.gold.noop is disabled because it requires a Gold license. Go to License Management to view upgrade options.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue