Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Monitoring] Cluster state watch to Kibana alerting #61685

Merged
merged 11 commits into from
Apr 6, 2020
8 changes: 6 additions & 2 deletions x-pack/legacy/plugins/monitoring/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,15 @@ export const ALERT_TYPE_PREFIX = 'monitoring_';
* This is the alert type id for the license expiration alert
*/
export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`;
/**
* This is the alert type id for the cluster state alert
*/
export const ALERT_TYPE_CLUSTER_STATE = `${ALERT_TYPE_PREFIX}alert_type_cluster_state`;

/**
* A listing of all alert types
*/
export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION];
export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_STATE];

/**
* Matches the id for the built-in in email action type
Expand All @@ -254,7 +258,7 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email';
/**
* The number of alerts that have been migrated
*/
export const NUMBER_OF_MIGRATED_ALERTS = 1;
export const NUMBER_OF_MIGRATED_ALERTS = 2;

/**
* The advanced settings config name for the email address
Expand Down
44 changes: 27 additions & 17 deletions x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@

import React from 'react';
import chrome from '../../np_imports/ui/chrome';
import { capitalize } from 'lodash';
import { capitalize, get } from 'lodash';
import { formatDateTimeLocal } from '../../../common/formatting';
import { formatTimestampToDuration } from '../../../common';
import { CALCULATE_DURATION_SINCE, EUI_SORT_DESCENDING } from '../../../common/constants';
import {
CALCULATE_DURATION_SINCE,
EUI_SORT_DESCENDING,
ALERT_TYPE_LICENSE_EXPIRATION,
ALERT_TYPE_CLUSTER_STATE,
} from '../../../common/constants';
import { mapSeverity } from './map_severity';
import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert';
import { EuiMonitoringTable } from 'plugins/monitoring/components/table';
Expand All @@ -21,6 +26,8 @@ const linkToCategories = {
'elasticsearch/indices': 'Elasticsearch Indices',
'kibana/instances': 'Kibana Instances',
'logstash/instances': 'Logstash Nodes',
[ALERT_TYPE_LICENSE_EXPIRATION]: 'License expiration',
[ALERT_TYPE_CLUSTER_STATE]: 'Cluster state',
};
const getColumns = (kbnUrl, scope, timezone) => [
{
Expand Down Expand Up @@ -94,19 +101,22 @@ const getColumns = (kbnUrl, scope, timezone) => [
}),
field: 'message',
sortable: true,
render: (message, alert) => (
<FormattedAlert
prefix={alert.prefix}
suffix={alert.suffix}
message={message}
metadata={alert.metadata}
changeUrl={target => {
scope.$evalAsync(() => {
kbnUrl.changePath(target);
});
}}
/>
),
render: (_message, alert) => {
const message = get(alert, 'message.text', get(alert, 'message', ''));
return (
<FormattedAlert
prefix={alert.prefix}
suffix={alert.suffix}
message={message}
metadata={alert.metadata}
changeUrl={target => {
scope.$evalAsync(() => {
kbnUrl.changePath(target);
});
}}
/>
);
},
},
{
name: i18n.translate('xpack.monitoring.alerts.categoryColumnTitle', {
Expand Down Expand Up @@ -148,8 +158,8 @@ const getColumns = (kbnUrl, scope, timezone) => [
export const Alerts = ({ alerts, angular, sorting, pagination, onTableChange }) => {
const alertsFlattened = alerts.map(alert => ({
...alert,
status: alert.metadata.severity,
category: alert.metadata.link,
status: get(alert, 'metadata.severity', get(alert, 'severity', 0)),
category: get(alert, 'metadata.link', get(alert, 'type', null)),
}));

const injector = chrome.dangerouslyGetActiveInjector();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import { kfetch } from 'ui/kfetch';
import { AlertsStatus, AlertsStatusProps } from './status';
import { ALERT_TYPE_PREFIX } from '../../../common/constants';
import { ALERT_TYPES } from '../../../common/constants';
import { getSetupModeState } from '../../lib/setup_mode';
import { mockUseEffects } from '../../jest.helpers';

Expand Down Expand Up @@ -63,11 +63,7 @@ describe('Status', () => {

it('should render a success message if all alerts have been migrated and in setup mode', async () => {
(kfetch as jest.Mock).mockReturnValue({
data: [
{
alertTypeId: ALERT_TYPE_PREFIX,
},
],
data: ALERT_TYPES.map(type => ({ alertTypeId: type })),
});

(getSetupModeState as jest.Mock).mockReturnValue({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export const AlertsStatus: React.FC<AlertsStatusProps> = (props: AlertsStatusPro
);
}

const allMigrated = kibanaAlerts.length === NUMBER_OF_MIGRATED_ALERTS;
const allMigrated = kibanaAlerts.length >= NUMBER_OF_MIGRATED_ALERTS;
if (allMigrated) {
if (setupModeEnabled) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@

import React, { Fragment } from 'react';
import moment from 'moment-timezone';
import chrome from '../../../np_imports/ui/chrome';
import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert';
import { mapSeverity } from 'plugins/monitoring/components/alerts/map_severity';
import { formatTimestampToDuration } from '../../../../common/format_timestamp_to_duration';
import {
CALCULATE_DURATION_SINCE,
KIBANA_ALERTING_ENABLED,
ALERT_TYPE_LICENSE_EXPIRATION,
CALCULATE_DURATION_UNTIL,
} from '../../../../common/constants';
import { formatDateTimeLocal } from '../../../../common/formatting';
Expand All @@ -31,6 +29,37 @@ import {
EuiLink,
} from '@elastic/eui';

function replaceTokens(alert) {
if (!alert.message.tokens) {
return alert.message.text;
}

let text = alert.message.text;

for (const token of alert.message.tokens) {
if (token.type === 'time') {
text = text.replace(
token.startToken,
token.isRelative
? formatTimestampToDuration(alert.expirationTime, CALCULATE_DURATION_UNTIL)
: moment.tz(alert.expirationTime, moment.tz.guess()).format('LLL z')
);
} else if (token.type === 'link') {
const linkPart = new RegExp(`${token.startToken}(.+?)${token.endToken}`).exec(text);
// TODO: we assume this is at the end, which works for now but will not always work
const nonLinkText = text.replace(linkPart[0], '');
text = (
<Fragment>
{nonLinkText}
<EuiLink href={`#${token.url}`}>{linkPart[1]}</EuiLink>
</Fragment>
);
}
}

return text;
}

export function AlertsPanel({ alerts, changeUrl }) {
const goToAlerts = () => changeUrl('/alerts');

Expand Down Expand Up @@ -58,9 +87,6 @@ export function AlertsPanel({ alerts, changeUrl }) {
severityIcon.iconType = 'check';
}

const injector = chrome.dangerouslyGetActiveInjector();
const timezone = injector.get('config').get('dateFormat:tz');

return (
<EuiCallOut
key={`alert-item-${index}`}
Expand All @@ -83,7 +109,7 @@ export function AlertsPanel({ alerts, changeUrl }) {
id="xpack.monitoring.cluster.overview.alertsPanel.lastCheckedTimeText"
defaultMessage="Last checked {updateDateTime} (triggered {duration} ago)"
values={{
updateDateTime: formatDateTimeLocal(item.update_timestamp, timezone),
updateDateTime: formatDateTimeLocal(item.update_timestamp),
duration: formatTimestampToDuration(item.timestamp, CALCULATE_DURATION_SINCE),
}}
/>
Expand All @@ -96,14 +122,7 @@ export function AlertsPanel({ alerts, changeUrl }) {
const alertsList = KIBANA_ALERTING_ENABLED
? alerts.map((alert, idx) => {
const callOutProps = mapSeverity(alert.severity);
let message = alert.message
// scan message prefix and replace relative times
// \w: Matches any alphanumeric character from the basic Latin alphabet, including the underscore. Equivalent to [A-Za-z0-9_].
.replace(
'#relative',
formatTimestampToDuration(alert.expirationTime, CALCULATE_DURATION_UNTIL)
)
.replace('#absolute', moment.tz(alert.expirationTime, moment.tz.guess()).format('LLL z'));
const message = replaceTokens(alert);

if (!alert.isFiring) {
callOutProps.title = i18n.translate(
Expand All @@ -118,22 +137,30 @@ export function AlertsPanel({ alerts, changeUrl }) {
);
callOutProps.color = 'success';
callOutProps.iconType = 'check';
} else {
if (alert.type === ALERT_TYPE_LICENSE_EXPIRATION) {
message = (
<Fragment>
{message}
&nbsp;
<EuiLink href="#license">Please update your license</EuiLink>
</Fragment>
);
}
}

return (
<EuiCallOut key={idx} {...callOutProps}>
<p>{message}</p>
</EuiCallOut>
<Fragment key={idx}>
<EuiCallOut {...callOutProps}>
<p>{message}</p>
<EuiText size="xs">
<p data-test-subj="alertMeta" className="monCallout--meta">
<FormattedMessage
id="xpack.monitoring.cluster.overview.alertsPanel.lastCheckedTimeText"
defaultMessage="Last checked {updateDateTime} (triggered {duration} ago)"
values={{
updateDateTime: formatDateTimeLocal(alert.lastCheckedMS),
duration: formatTimestampToDuration(
alert.triggeredMS,
CALCULATE_DURATION_SINCE
),
}}
/>
</p>
</EuiText>
</EuiCallOut>
<EuiSpacer />
</Fragment>
);
})
: alerts.map((item, index) => (
Expand Down
30 changes: 21 additions & 9 deletions x-pack/legacy/plugins/monitoring/public/views/alerts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,37 @@ import { Alerts } from '../../components/alerts';
import { MonitoringViewBaseEuiTableController } from '../base_eui_table_controller';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLink } from '@elastic/eui';
import { CODE_PATH_ALERTS } from '../../../common/constants';
import { CODE_PATH_ALERTS, KIBANA_ALERTING_ENABLED } from '../../../common/constants';

function getPageData($injector) {
const globalState = $injector.get('globalState');
const $http = $injector.get('$http');
const Private = $injector.get('Private');
const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/legacy_alerts`;
const url = KIBANA_ALERTING_ENABLED
? `../api/monitoring/v1/alert_status`
: `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/legacy_alerts`;

const timeBounds = timefilter.getBounds();
const data = {
timeRange: {
min: timeBounds.min.toISOString(),
max: timeBounds.max.toISOString(),
},
};

if (!KIBANA_ALERTING_ENABLED) {
data.ccs = globalState.ccs;
}

return $http
.post(url, {
ccs: globalState.ccs,
timeRange: {
min: timeBounds.min.toISOString(),
max: timeBounds.max.toISOString(),
},
.post(url, data)
.then(response => {
const result = get(response, 'data', []);
if (KIBANA_ALERTING_ENABLED) {
return result.alerts;
}
return result;
})
.then(response => get(response, 'data', []))
.catch(err => {
const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider);
return ajaxErrorHandlers(err);
Expand Down
8 changes: 6 additions & 2 deletions x-pack/plugins/monitoring/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,15 @@ export const ALERT_TYPE_PREFIX = 'monitoring_';
* This is the alert type id for the license expiration alert
*/
export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`;
/**
* This is the alert type id for the cluster state alert
*/
export const ALERT_TYPE_CLUSTER_STATE = `${ALERT_TYPE_PREFIX}alert_type_cluster_state`;

/**
* A listing of all alert types
*/
export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION];
export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_STATE];

/**
* Matches the id for the built-in in email action type
Expand All @@ -254,7 +258,7 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email';
/**
* The number of alerts that have been migrated
*/
export const NUMBER_OF_MIGRATED_ALERTS = 1;
export const NUMBER_OF_MIGRATED_ALERTS = 2;

/**
* The advanced settings config name for the email address
Expand Down
Loading