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] Missing monitoring data alert #78208

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
627afe3
WIP for alert
chrisronline Sep 21, 2020
2f496f1
Surface alert most places
chrisronline Sep 22, 2020
ea5d19a
Merge in master
chrisronline Sep 23, 2020
2190c2a
Fix up alert placement
chrisronline Sep 23, 2020
b244269
Fix tests
chrisronline Sep 23, 2020
8c24540
Type fix
chrisronline Sep 23, 2020
740ecf7
Update copy
chrisronline Sep 23, 2020
468da3b
Merge remote-tracking branch 'elastic/master' into monitoring/missing…
chrisronline Sep 24, 2020
8a69629
Add alert presence to APM in the UI
chrisronline Sep 24, 2020
35c77f5
Fetch data a little differently
chrisronline Sep 24, 2020
0b2db89
We don't need moment
chrisronline Sep 24, 2020
1ffd812
Add tests
chrisronline Sep 24, 2020
0c39b98
Merge remote-tracking branch 'elastic/master' into monitoring/missing…
chrisronline Sep 28, 2020
acc8502
PR feedback
chrisronline Sep 28, 2020
ce6d465
Merge remote-tracking branch 'elastic/master' into monitoring/missing…
chrisronline Sep 28, 2020
6514604
Update copy
chrisronline Sep 28, 2020
1fc2ce3
Merge remote-tracking branch 'elastic/master' into monitoring/missing…
chrisronline Sep 29, 2020
9c70e11
Fix up bug around grabbing old data
chrisronline Sep 29, 2020
a5ba085
Merge remote-tracking branch 'elastic/master' into monitoring/missing…
chrisronline Sep 30, 2020
2679131
Merge remote-tracking branch 'elastic/master' into monitoring/missing…
chrisronline Sep 30, 2020
f4c9b67
PR feedback
chrisronline Sep 30, 2020
490dac0
Merge remote-tracking branch 'elastic/master' into monitoring/missing…
chrisronline Oct 1, 2020
c262c6a
PR feedback
chrisronline Oct 1, 2020
08374e1
Fix tests
chrisronline Oct 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions x-pack/plugins/monitoring/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export const ALERT_NODES_CHANGED = `${ALERT_PREFIX}alert_nodes_changed`;
export const ALERT_ELASTICSEARCH_VERSION_MISMATCH = `${ALERT_PREFIX}alert_elasticsearch_version_mismatch`;
export const ALERT_KIBANA_VERSION_MISMATCH = `${ALERT_PREFIX}alert_kibana_version_mismatch`;
export const ALERT_LOGSTASH_VERSION_MISMATCH = `${ALERT_PREFIX}alert_logstash_version_mismatch`;
export const ALERT_MISSING_DATA = `${ALERT_PREFIX}alert_missing_data`;

/**
* A listing of all alert types
Expand All @@ -247,6 +248,7 @@ export const ALERTS = [
ALERT_ELASTICSEARCH_VERSION_MISMATCH,
ALERT_KIBANA_VERSION_MISMATCH,
ALERT_LOGSTASH_VERSION_MISMATCH,
ALERT_MISSING_DATA,
];

/**
Expand Down
6 changes: 5 additions & 1 deletion x-pack/plugins/monitoring/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ export interface CommonAlertState {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CommonAlertFilter {}

export interface CommonAlertCpuUsageFilter extends CommonAlertFilter {
export interface CommonAlertNodeUuidFilter extends CommonAlertFilter {
nodeUuid: string;
}

export interface CommonAlertStackProductFilter extends CommonAlertFilter {
stackProduct: string;
}

export interface CommonAlertParamDetail {
label: string;
type: AlertParamType;
Expand Down
15 changes: 11 additions & 4 deletions x-pack/plugins/monitoring/public/alerts/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { CommonAlertStatus, CommonAlertState } from '../../common/types';
import { AlertSeverity } from '../../common/enums';
// @ts-ignore
import { formatDateTimeLocal } from '../../common/formatting';
import { AlertState } from '../../server/alerts/types';
import { AlertMessage, AlertState } from '../../server/alerts/types';
import { AlertPanel } from './panel';
import { Legacy } from '../legacy_shims';
import { isInSetupMode } from '../lib/setup_mode';
Expand All @@ -39,9 +39,10 @@ interface AlertInPanel {
interface Props {
alerts: { [alertTypeId: string]: CommonAlertStatus };
stateFilter: (state: AlertState) => boolean;
nextStepsFilter: (nextStep: AlertMessage) => boolean;
}
export const AlertsBadge: React.FC<Props> = (props: Props) => {
const { stateFilter = () => true } = props;
const { stateFilter = () => true, nextStepsFilter = () => true } = props;
const [showPopover, setShowPopover] = React.useState<AlertSeverity | boolean | null>(null);
const inSetupMode = isInSetupMode();
const alerts = Object.values(props.alerts).filter(Boolean);
Expand Down Expand Up @@ -80,7 +81,7 @@ export const AlertsBadge: React.FC<Props> = (props: Props) => {
id: index + 1,
title: alertStatus.alert.label,
width: 400,
content: <AlertPanel alert={alertStatus} />,
content: <AlertPanel alert={alertStatus} nextStepsFilter={nextStepsFilter} />,
};
}),
];
Expand Down Expand Up @@ -158,7 +159,13 @@ export const AlertsBadge: React.FC<Props> = (props: Props) => {
id: index + 1,
title: getDateFromState(alertStatus.alertState),
width: 400,
content: <AlertPanel alert={alertStatus.alert} alertState={alertStatus.alertState} />,
content: (
<AlertPanel
alert={alertStatus.alert}
alertState={alertStatus.alertState}
nextStepsFilter={nextStepsFilter}
/>
),
};
}),
];
Expand Down
11 changes: 6 additions & 5 deletions x-pack/plugins/monitoring/public/alerts/callout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ const TYPES = [
interface Props {
alerts: { [alertTypeId: string]: CommonAlertStatus };
stateFilter: (state: AlertState) => boolean;
nextStepsFilter: (nextStep: AlertMessage) => boolean;
}
export const AlertsCallout: React.FC<Props> = (props: Props) => {
const { alerts, stateFilter = () => true } = props;
const { alerts, stateFilter = () => true, nextStepsFilter = () => true } = props;

const callouts = TYPES.map((type) => {
const list = [];
Expand All @@ -56,11 +57,11 @@ export const AlertsCallout: React.FC<Props> = (props: Props) => {
const nextStepsUi =
state.ui.message.nextSteps && state.ui.message.nextSteps.length ? (
<ul>
{state.ui.message.nextSteps.map(
(step: AlertMessage, nextStepIndex: number) => (
{state.ui.message.nextSteps
.filter(nextStepsFilter)
.map((step: AlertMessage, nextStepIndex: number) => (
<li key={nextStepIndex}>{replaceTokens(step)}</li>
)
)}
))}
</ul>
) : null;

Expand Down
23 changes: 23 additions & 0 deletions x-pack/plugins/monitoring/public/alerts/filter_alert_states.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 { CommonAlertState, CommonAlertStatus } from '../../common/types';

export function filterAlertStates(
alerts: { [type: string]: CommonAlertStatus },
filter: (type: string, state: CommonAlertState) => boolean
) {
return Object.keys(alerts).reduce(
(accum: { [type: string]: CommonAlertStatus }, type: string) => {
accum[type] = {
...alerts[type],
states: alerts[type].states.filter((state) => filter(type, state)),
};
return accum;
},
{}
);
}
Original file line number Diff line number Diff line change
@@ -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, { Fragment } from 'react';
import { EuiForm, EuiSpacer } from '@elastic/eui';
import { CommonAlertParamDetails } from '../../../common/types';
import { AlertParamDuration } from '../flyout_expressions/alert_param_duration';
import { AlertParamType } from '../../../common/enums';
import { AlertParamPercentage } from '../flyout_expressions/alert_param_percentage';

export interface Props {
alertParams: { [property: string]: any };
setAlertParams: (property: string, value: any) => void;
setAlertProperty: (property: string, value: any) => void;
errors: { [key: string]: string[] };
paramDetails: CommonAlertParamDetails;
}

export const Expression: React.FC<Props> = (props) => {
const { alertParams, paramDetails, setAlertParams, errors } = props;

const alertParamsUi = Object.keys(alertParams).map((alertParamName) => {
const details = paramDetails[alertParamName];
const value = alertParams[alertParamName];

switch (details.type) {
case AlertParamType.Duration:
return (
<AlertParamDuration
key={alertParamName}
name={alertParamName}
duration={value}
label={details.label}
errors={errors[alertParamName]}
setAlertParams={setAlertParams}
/>
);
case AlertParamType.Percentage:
return (
<AlertParamPercentage
key={alertParamName}
name={alertParamName}
label={details.label}
percentage={value}
errors={errors[alertParamName]}
setAlertParams={setAlertParams}
/>
);
}
});

return (
<Fragment>
<EuiForm component="form">{alertParamsUi}</EuiForm>
<EuiSpacer />
</Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* 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.
*/

export { createMissingDataAlertType } from './missing_data_alert';
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types';
import { validate } from './validation';
import { ALERT_MISSING_DATA } from '../../../common/constants';
import { Expression } from './expression';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { MissingDataAlert } from '../../../server/alerts';

export function createMissingDataAlertType(): AlertTypeModel {
const alert = new MissingDataAlert();
return {
id: ALERT_MISSING_DATA,
name: alert.label,
iconClass: 'bell',
alertParamsExpression: (props: any) => (
<Expression {...props} paramDetails={MissingDataAlert.paramDetails} />
),
validate,
defaultActionMessage: '{{context.internalFullMessage}}',
requiresAppContext: true,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 { i18n } from '@kbn/i18n';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { ValidationResult } from '../../../../triggers_actions_ui/public/types';

export function validate(opts: any): ValidationResult {
const validationResult = { errors: {} };

const errors: { [key: string]: string[] } = {
duration: [],
limit: [],
};
if (!opts.duration) {
errors.duration.push(
i18n.translate('xpack.monitoring.alerts.missingData.validation.duration', {
defaultMessage: 'A valid duration is required.',
})
);
}
if (!opts.limit) {
errors.limit.push(
i18n.translate('xpack.monitoring.alerts.missingData.validation.limit', {
defaultMessage: 'A valid limit is required.',
})
);
}

validationResult.errors = errors;
return validationResult;
}
10 changes: 7 additions & 3 deletions x-pack/plugins/monitoring/public/alerts/panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ import { BASE_ALERT_API_PATH } from '../../../alerts/common';
interface Props {
alert: CommonAlertStatus;
alertState?: CommonAlertState;
nextStepsFilter: (nextStep: AlertMessage) => boolean;
}
export const AlertPanel: React.FC<Props> = (props: Props) => {
const {
alert: { alert },
alertState,
nextStepsFilter = () => true,
} = props;
const [showFlyout, setShowFlyout] = React.useState(false);
const [isEnabled, setIsEnabled] = React.useState(alert.rawAlert.enabled);
Expand Down Expand Up @@ -198,9 +200,11 @@ export const AlertPanel: React.FC<Props> = (props: Props) => {
const nextStepsUi =
alertState.state.ui.message.nextSteps && alertState.state.ui.message.nextSteps.length ? (
<EuiListGroup>
{alertState.state.ui.message.nextSteps.map((step: AlertMessage, index: number) => (
<EuiListGroupItem size="s" key={index} label={replaceTokens(step)} />
))}
{alertState.state.ui.message.nextSteps
.filter(nextStepsFilter)
.map((step: AlertMessage, index: number) => (
<EuiListGroupItem size="s" key={index} label={replaceTokens(step)} />
))}
</EuiListGroup>
) : null;

Expand Down
15 changes: 12 additions & 3 deletions x-pack/plugins/monitoring/public/alerts/status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { CommonAlertStatus } from '../../common/types';
import { AlertSeverity } from '../../common/enums';
import { AlertState } from '../../server/alerts/types';
import { AlertMessage, AlertState } from '../../server/alerts/types';
import { AlertsBadge } from './badge';
import { isInSetupMode } from '../lib/setup_mode';

Expand All @@ -18,9 +18,16 @@ interface Props {
showBadge: boolean;
showOnlyCount: boolean;
stateFilter: (state: AlertState) => boolean;
nextStepsFilter: (nextStep: AlertMessage) => boolean;
}
export const AlertsStatus: React.FC<Props> = (props: Props) => {
const { alerts, showBadge = false, showOnlyCount = false, stateFilter = () => true } = props;
const {
alerts,
showBadge = false,
showOnlyCount = false,
stateFilter = () => true,
nextStepsFilter = () => true,
} = props;
const inSetupMode = isInSetupMode();

if (!alerts) {
Expand Down Expand Up @@ -71,7 +78,9 @@ export const AlertsStatus: React.FC<Props> = (props: Props) => {
}

if (showBadge || inSetupMode) {
return <AlertsBadge alerts={alerts} stateFilter={stateFilter} />;
return (
<AlertsBadge alerts={alerts} stateFilter={stateFilter} nextStepsFilter={nextStepsFilter} />
);
}

const severity = atLeastOneDanger ? AlertSeverity.Danger : AlertSeverity.Warning;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import {
} from '@elastic/eui';
import { Status } from './status';
import { FormattedMessage } from '@kbn/i18n/react';
import { AlertsCallout } from '../../../alerts/callout';

export function ApmServerInstance({ summary, metrics, ...props }) {
export function ApmServerInstance({ summary, metrics, alerts, ...props }) {
const seriesToShow = [
metrics.apm_requests,
metrics.apm_responses_valid,
Expand Down Expand Up @@ -58,9 +59,18 @@ export function ApmServerInstance({ summary, metrics, ...props }) {
</h1>
</EuiScreenReaderOnly>
<EuiPanel>
<Status stats={summary} />
<Status stats={summary} alerts={alerts} />
</EuiPanel>
<EuiSpacer size="m" />
<AlertsCallout
alerts={alerts}
nextStepsFilter={(nextStep) => {
if (nextStep.text.includes('APM servers')) {
return false;
}
return true;
}}
/>
<EuiPageContent>
<EuiFlexGroup wrap>{charts}</EuiFlexGroup>
</EuiPageContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { CALCULATE_DURATION_SINCE } from '../../../../common/constants';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';

export function Status({ stats }) {
export function Status({ alerts, stats }) {
const { name, output, version, uptime, timeOfLastEvent } = stats;

const metrics = [
Expand Down Expand Up @@ -78,6 +78,7 @@ export function Status({ stats }) {
return (
<SummaryStatus
metrics={metrics}
alerts={alerts}
IconComponent={IconComponent}
data-test-subj="apmDetailStatus"
/>
Expand Down
Loading