[APM] Align APM severity levels with ML (#77818)
This commit is contained in:
parent
b08594ad5c
commit
217276e8a9
|
@ -5,6 +5,8 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ValuesType } from 'utility-types';
|
||||
import { ANOMALY_SEVERITY, ANOMALY_THRESHOLD } from '../../ml/common';
|
||||
|
||||
export enum AlertType {
|
||||
ErrorCount = 'apm.error_rate', // ErrorRate was renamed to ErrorCount but the key is kept as `error_rate` for backwards-compat.
|
||||
|
@ -55,6 +57,41 @@ export const ALERT_TYPES_CONFIG = {
|
|||
},
|
||||
};
|
||||
|
||||
export const ANOMALY_ALERT_SEVERITY_TYPES = [
|
||||
{
|
||||
type: ANOMALY_SEVERITY.CRITICAL,
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.criticalLabel', {
|
||||
defaultMessage: 'critical',
|
||||
}),
|
||||
threshold: ANOMALY_THRESHOLD.CRITICAL,
|
||||
},
|
||||
{
|
||||
type: ANOMALY_SEVERITY.MAJOR,
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.majorLabel', {
|
||||
defaultMessage: 'major',
|
||||
}),
|
||||
threshold: ANOMALY_THRESHOLD.MAJOR,
|
||||
},
|
||||
{
|
||||
type: ANOMALY_SEVERITY.MINOR,
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.minor', {
|
||||
defaultMessage: 'minor',
|
||||
}),
|
||||
threshold: ANOMALY_THRESHOLD.MINOR,
|
||||
},
|
||||
{
|
||||
type: ANOMALY_SEVERITY.WARNING,
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.warningLabel', {
|
||||
defaultMessage: 'warning',
|
||||
}),
|
||||
threshold: ANOMALY_THRESHOLD.WARNING,
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type AnomalyAlertSeverityType = ValuesType<
|
||||
typeof ANOMALY_ALERT_SEVERITY_TYPES
|
||||
>['type'];
|
||||
|
||||
// Server side registrations
|
||||
// x-pack/plugins/apm/server/lib/alerts/<alert>.ts
|
||||
// x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* 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 { getSeverity, Severity } from './anomaly_detection';
|
||||
|
||||
describe('getSeverity', () => {
|
||||
describe('when score is undefined', () => {
|
||||
it('returns undefined', () => {
|
||||
expect(getSeverity(undefined)).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when score < 25', () => {
|
||||
it('returns warning', () => {
|
||||
expect(getSeverity(10)).toEqual(Severity.warning);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when score is between 25 and 50', () => {
|
||||
it('returns minor', () => {
|
||||
expect(getSeverity(40)).toEqual(Severity.minor);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when score is between 50 and 75', () => {
|
||||
it('returns major', () => {
|
||||
expect(getSeverity(60)).toEqual(Severity.major);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when score is 75 or more', () => {
|
||||
it('returns critical', () => {
|
||||
expect(getSeverity(100)).toEqual(Severity.critical);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,89 +5,31 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiTheme } from '../../../legacy/common/eui_styled_components';
|
||||
import { ANOMALY_SEVERITY } from '../../ml/common';
|
||||
import {
|
||||
getSeverityType,
|
||||
getSeverityColor as mlGetSeverityColor,
|
||||
} from '../../ml/common';
|
||||
import { ServiceHealthStatus } from './service_health_status';
|
||||
|
||||
export interface ServiceAnomalyStats {
|
||||
transactionType?: string;
|
||||
anomalyScore?: number;
|
||||
actualValue?: number;
|
||||
jobId?: string;
|
||||
healthStatus: ServiceHealthStatus;
|
||||
}
|
||||
|
||||
export enum Severity {
|
||||
critical = 'critical',
|
||||
major = 'major',
|
||||
minor = 'minor',
|
||||
warning = 'warning',
|
||||
}
|
||||
|
||||
// TODO: Replace with `getSeverity` from:
|
||||
// https://github.com/elastic/kibana/blob/0f964f66916480f2de1f4b633e5afafc08cf62a0/x-pack/plugins/ml/common/util/anomaly_utils.ts#L129
|
||||
export function getSeverity(score?: number) {
|
||||
if (typeof score !== 'number') {
|
||||
return undefined;
|
||||
} else if (score < 25) {
|
||||
return Severity.warning;
|
||||
} else if (score >= 25 && score < 50) {
|
||||
return Severity.minor;
|
||||
} else if (score >= 50 && score < 75) {
|
||||
return Severity.major;
|
||||
} else if (score >= 75) {
|
||||
return Severity.critical;
|
||||
} else {
|
||||
return undefined;
|
||||
export function getSeverity(score: number | undefined) {
|
||||
if (score === undefined) {
|
||||
return ANOMALY_SEVERITY.UNKNOWN;
|
||||
}
|
||||
|
||||
return getSeverityType(score);
|
||||
}
|
||||
|
||||
export function getSeverityColor(theme: EuiTheme, severity?: Severity) {
|
||||
switch (severity) {
|
||||
case Severity.warning:
|
||||
return theme.eui.euiColorVis0;
|
||||
case Severity.minor:
|
||||
case Severity.major:
|
||||
return theme.eui.euiColorVis5;
|
||||
case Severity.critical:
|
||||
return theme.eui.euiColorVis9;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export function getSeverityLabel(severity?: Severity) {
|
||||
switch (severity) {
|
||||
case Severity.critical:
|
||||
return i18n.translate(
|
||||
'xpack.apm.servicesTable.serviceHealthStatus.critical',
|
||||
{
|
||||
defaultMessage: 'Critical',
|
||||
}
|
||||
);
|
||||
|
||||
case Severity.major:
|
||||
case Severity.minor:
|
||||
return i18n.translate(
|
||||
'xpack.apm.servicesTable.serviceHealthStatus.warning',
|
||||
{
|
||||
defaultMessage: 'Warning',
|
||||
}
|
||||
);
|
||||
|
||||
case Severity.warning:
|
||||
return i18n.translate(
|
||||
'xpack.apm.servicesTable.serviceHealthStatus.healthy',
|
||||
{
|
||||
defaultMessage: 'Healthy',
|
||||
}
|
||||
);
|
||||
|
||||
default:
|
||||
return i18n.translate(
|
||||
'xpack.apm.servicesTable.serviceHealthStatus.unknown',
|
||||
{
|
||||
defaultMessage: 'Unknown',
|
||||
}
|
||||
);
|
||||
}
|
||||
export function getSeverityColor(score: number) {
|
||||
return mlGetSeverityColor(score);
|
||||
}
|
||||
|
||||
export const ML_ERRORS = {
|
||||
|
|
79
x-pack/plugins/apm/common/service_health_status.ts
Normal file
79
x-pack/plugins/apm/common/service_health_status.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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';
|
||||
import { ANOMALY_SEVERITY } from '../../ml/common';
|
||||
|
||||
import { EuiTheme } from '../../../legacy/common/eui_styled_components';
|
||||
|
||||
export enum ServiceHealthStatus {
|
||||
healthy = 'healthy',
|
||||
critical = 'critical',
|
||||
warning = 'warning',
|
||||
unknown = 'unknown',
|
||||
}
|
||||
|
||||
export function getServiceHealthStatus({
|
||||
severity,
|
||||
}: {
|
||||
severity: ANOMALY_SEVERITY;
|
||||
}) {
|
||||
switch (severity) {
|
||||
case ANOMALY_SEVERITY.CRITICAL:
|
||||
case ANOMALY_SEVERITY.MAJOR:
|
||||
return ServiceHealthStatus.critical;
|
||||
|
||||
case ANOMALY_SEVERITY.MINOR:
|
||||
case ANOMALY_SEVERITY.WARNING:
|
||||
return ServiceHealthStatus.warning;
|
||||
|
||||
case ANOMALY_SEVERITY.LOW:
|
||||
return ServiceHealthStatus.healthy;
|
||||
|
||||
case ANOMALY_SEVERITY.UNKNOWN:
|
||||
return ServiceHealthStatus.unknown;
|
||||
}
|
||||
}
|
||||
|
||||
export function getServiceHealthStatusColor(
|
||||
theme: EuiTheme,
|
||||
status: ServiceHealthStatus
|
||||
) {
|
||||
switch (status) {
|
||||
case ServiceHealthStatus.healthy:
|
||||
return theme.eui.euiColorVis0;
|
||||
case ServiceHealthStatus.warning:
|
||||
return theme.eui.euiColorVis5;
|
||||
case ServiceHealthStatus.critical:
|
||||
return theme.eui.euiColorVis9;
|
||||
case ServiceHealthStatus.unknown:
|
||||
return theme.eui.euiColorMediumShade;
|
||||
}
|
||||
}
|
||||
|
||||
export function getServiceHealthStatusLabel(status: ServiceHealthStatus) {
|
||||
switch (status) {
|
||||
case ServiceHealthStatus.critical:
|
||||
return i18n.translate('xpack.apm.serviceHealthStatus.critical', {
|
||||
defaultMessage: 'Critical',
|
||||
});
|
||||
|
||||
case ServiceHealthStatus.warning:
|
||||
return i18n.translate('xpack.apm.serviceHealthStatus.warning', {
|
||||
defaultMessage: 'Warning',
|
||||
});
|
||||
|
||||
case ServiceHealthStatus.healthy:
|
||||
return i18n.translate('xpack.apm.serviceHealthStatus.healthy', {
|
||||
defaultMessage: 'Healthy',
|
||||
});
|
||||
|
||||
case ServiceHealthStatus.unknown:
|
||||
return i18n.translate('xpack.apm.serviceHealthStatus.unknown', {
|
||||
defaultMessage: 'Unknown',
|
||||
});
|
||||
}
|
||||
}
|
|
@ -23,13 +23,19 @@
|
|||
],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"configPath": ["xpack", "apm"],
|
||||
"extraPublicDirs": ["public/style/variables"],
|
||||
"configPath": [
|
||||
"xpack",
|
||||
"apm"
|
||||
],
|
||||
"extraPublicDirs": [
|
||||
"public/style/variables"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"kibanaReact",
|
||||
"kibanaUtils",
|
||||
"observability",
|
||||
"home",
|
||||
"maps"
|
||||
"maps",
|
||||
"ml"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,105 +5,60 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui';
|
||||
import { getSeverityColor } from '../../../../common/anomaly_detection';
|
||||
import {
|
||||
getSeverityColor,
|
||||
Severity,
|
||||
} from '../../../../common/anomaly_detection';
|
||||
import { useTheme } from '../../../hooks/useTheme';
|
||||
AnomalyAlertSeverityType,
|
||||
ANOMALY_ALERT_SEVERITY_TYPES,
|
||||
} from '../../../../common/alert_types';
|
||||
|
||||
type SeverityScore = 0 | 25 | 50 | 75;
|
||||
const ANOMALY_SCORES: SeverityScore[] = [0, 25, 50, 75];
|
||||
|
||||
const anomalyScoreSeverityMap: {
|
||||
[key in SeverityScore]: { label: string; severity: Severity };
|
||||
} = {
|
||||
0: {
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.warningLabel', {
|
||||
defaultMessage: 'warning',
|
||||
}),
|
||||
severity: Severity.warning,
|
||||
},
|
||||
25: {
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.minorLabel', {
|
||||
defaultMessage: 'minor',
|
||||
}),
|
||||
severity: Severity.minor,
|
||||
},
|
||||
50: {
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.majorLabel', {
|
||||
defaultMessage: 'major',
|
||||
}),
|
||||
severity: Severity.major,
|
||||
},
|
||||
75: {
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.criticalLabel', {
|
||||
defaultMessage: 'critical',
|
||||
}),
|
||||
severity: Severity.critical,
|
||||
},
|
||||
};
|
||||
|
||||
export function AnomalySeverity({
|
||||
severityScore,
|
||||
}: {
|
||||
severityScore: SeverityScore;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const { label, severity } = anomalyScoreSeverityMap[severityScore];
|
||||
const defaultColor = theme.eui.euiColorMediumShade;
|
||||
const color = getSeverityColor(theme, severity) || defaultColor;
|
||||
export function AnomalySeverity({ type }: { type: AnomalyAlertSeverityType }) {
|
||||
const selectedOption = ANOMALY_ALERT_SEVERITY_TYPES.find(
|
||||
(option) => option.type === type
|
||||
)!;
|
||||
return (
|
||||
<EuiHealth color={color} style={{ lineHeight: 'inherit' }}>
|
||||
{label}
|
||||
<EuiHealth
|
||||
color={getSeverityColor(selectedOption.threshold)}
|
||||
style={{ lineHeight: 'inherit' }}
|
||||
>
|
||||
{selectedOption.label}
|
||||
</EuiHealth>
|
||||
);
|
||||
}
|
||||
|
||||
const getOption = (value: SeverityScore) => {
|
||||
return {
|
||||
value: value.toString(10),
|
||||
inputDisplay: <AnomalySeverity severityScore={value} />,
|
||||
dropdownDisplay: (
|
||||
<>
|
||||
<AnomalySeverity severityScore={value} />
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText size="xs" color="subdued">
|
||||
<p className="euiTextColor--subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.apm.alerts.anomalySeverity.scoreDetailsDescription"
|
||||
defaultMessage="score {value} and above"
|
||||
values={{ value }}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
interface Props {
|
||||
onChange: (value: SeverityScore) => void;
|
||||
value: SeverityScore;
|
||||
onChange: (value: AnomalyAlertSeverityType) => void;
|
||||
value: AnomalyAlertSeverityType;
|
||||
}
|
||||
|
||||
export function SelectAnomalySeverity({ onChange, value }: Props) {
|
||||
const options = ANOMALY_SCORES.map((anomalyScore) => getOption(anomalyScore));
|
||||
|
||||
return (
|
||||
<EuiSuperSelect
|
||||
hasDividers
|
||||
style={{ width: 200 }}
|
||||
options={options}
|
||||
valueOfSelected={value.toString(10)}
|
||||
onChange={(selectedValue: string) => {
|
||||
const selectedAnomalyScore = parseInt(
|
||||
selectedValue,
|
||||
10
|
||||
) as SeverityScore;
|
||||
onChange(selectedAnomalyScore);
|
||||
options={ANOMALY_ALERT_SEVERITY_TYPES.map((option) => ({
|
||||
value: option.type,
|
||||
inputDisplay: <AnomalySeverity type={option.type} />,
|
||||
dropdownDisplay: (
|
||||
<>
|
||||
<AnomalySeverity type={option.type} />
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText size="xs" color="subdued">
|
||||
<p className="euiTextColor--subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.apm.alerts.anomalySeverity.scoreDetailsDescription"
|
||||
defaultMessage="score {value} and above"
|
||||
values={{ value }}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</>
|
||||
),
|
||||
}))}
|
||||
valueOfSelected={value}
|
||||
onChange={(selectedValue: AnomalyAlertSeverityType) => {
|
||||
onChange(selectedValue);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { useParams } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { ANOMALY_SEVERITY } from '../../../../../ml/common';
|
||||
import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types';
|
||||
import { useEnvironments } from '../../../hooks/useEnvironments';
|
||||
import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes';
|
||||
|
@ -34,7 +35,11 @@ interface Params {
|
|||
serviceName: string;
|
||||
transactionType: string;
|
||||
environment: string;
|
||||
anomalyScore: 0 | 25 | 50 | 75;
|
||||
anomalySeverityType:
|
||||
| ANOMALY_SEVERITY.CRITICAL
|
||||
| ANOMALY_SEVERITY.MAJOR
|
||||
| ANOMALY_SEVERITY.MINOR
|
||||
| ANOMALY_SEVERITY.WARNING;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
@ -67,7 +72,7 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) {
|
|||
transactionType,
|
||||
serviceName,
|
||||
environment: urlParams.environment || ENVIRONMENT_ALL.value,
|
||||
anomalyScore: 75,
|
||||
anomalySeverityType: ANOMALY_SEVERITY.CRITICAL,
|
||||
};
|
||||
|
||||
const params = {
|
||||
|
@ -84,7 +89,7 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) {
|
|||
onChange={(e) => setAlertParams('environment', e.target.value)}
|
||||
/>,
|
||||
<PopoverExpression
|
||||
value={<AnomalySeverity severityScore={params.anomalyScore} />}
|
||||
value={<AnomalySeverity type={params.anomalySeverityType} />}
|
||||
title={i18n.translate(
|
||||
'xpack.apm.transactionDurationAnomalyAlertTrigger.anomalySeverity',
|
||||
{
|
||||
|
@ -93,9 +98,9 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) {
|
|||
)}
|
||||
>
|
||||
<SelectAnomalySeverity
|
||||
value={params.anomalyScore}
|
||||
value={params.anomalySeverityType}
|
||||
onChange={(value) => {
|
||||
setAlertParams('anomalyScore', value);
|
||||
setAlertParams('anomalySeverityType', value);
|
||||
}}
|
||||
/>
|
||||
</PopoverExpression>,
|
||||
|
|
|
@ -14,6 +14,10 @@ import {
|
|||
EuiIconTip,
|
||||
EuiHealth,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
getServiceHealthStatus,
|
||||
getServiceHealthStatusColor,
|
||||
} from '../../../../../common/service_health_status';
|
||||
import { useTheme } from '../../../../hooks/useTheme';
|
||||
import { fontSize, px } from '../../../../style/variables';
|
||||
import { asInteger, asDuration } from '../../../../utils/formatters';
|
||||
|
@ -22,7 +26,6 @@ import { popoverWidth } from '../cytoscapeOptions';
|
|||
import { TRANSACTION_REQUEST } from '../../../../../common/transaction_types';
|
||||
import {
|
||||
getSeverity,
|
||||
getSeverityColor,
|
||||
ServiceAnomalyStats,
|
||||
} from '../../../../../common/anomaly_detection';
|
||||
|
||||
|
@ -59,13 +62,15 @@ export function AnomalyDetection({ serviceName, serviceAnomalyStats }: Props) {
|
|||
const theme = useTheme();
|
||||
|
||||
const anomalyScore = serviceAnomalyStats?.anomalyScore;
|
||||
const anomalySeverity = getSeverity(anomalyScore);
|
||||
const severity = getSeverity(anomalyScore);
|
||||
const actualValue = serviceAnomalyStats?.actualValue;
|
||||
const mlJobId = serviceAnomalyStats?.jobId;
|
||||
const transactionType =
|
||||
serviceAnomalyStats?.transactionType ?? TRANSACTION_REQUEST;
|
||||
const hasAnomalyDetectionScore = anomalyScore !== undefined;
|
||||
|
||||
const healthStatus = getServiceHealthStatus({ severity });
|
||||
|
||||
return (
|
||||
<>
|
||||
<section>
|
||||
|
@ -81,7 +86,9 @@ export function AnomalyDetection({ serviceName, serviceAnomalyStats }: Props) {
|
|||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<VerticallyCentered>
|
||||
<EuiHealth color={getSeverityColor(theme, anomalySeverity)} />
|
||||
<EuiHealth
|
||||
color={getServiceHealthStatusColor(theme, healthStatus)}
|
||||
/>
|
||||
<SubduedText>{ANOMALY_DETECTION_SCORE_METRIC}</SubduedText>
|
||||
</VerticallyCentered>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -5,26 +5,26 @@
|
|||
*/
|
||||
import cytoscape from 'cytoscape';
|
||||
import { CSSProperties } from 'react';
|
||||
import {
|
||||
getServiceHealthStatusColor,
|
||||
ServiceHealthStatus,
|
||||
} from '../../../../common/service_health_status';
|
||||
import {
|
||||
SERVICE_NAME,
|
||||
SPAN_DESTINATION_SERVICE_RESOURCE,
|
||||
} from '../../../../common/elasticsearch_fieldnames';
|
||||
import { EuiTheme } from '../../../../../observability/public';
|
||||
import { defaultIcon, iconForNode } from './icons';
|
||||
import {
|
||||
getSeverity,
|
||||
getSeverityColor,
|
||||
ServiceAnomalyStats,
|
||||
Severity,
|
||||
} from '../../../../common/anomaly_detection';
|
||||
import { ServiceAnomalyStats } from '../../../../common/anomaly_detection';
|
||||
|
||||
export const popoverWidth = 280;
|
||||
|
||||
function getNodeSeverity(el: cytoscape.NodeSingular) {
|
||||
function getServiceAnomalyStats(el: cytoscape.NodeSingular) {
|
||||
const serviceAnomalyStats: ServiceAnomalyStats | undefined = el.data(
|
||||
'serviceAnomalyStats'
|
||||
);
|
||||
return getSeverity(serviceAnomalyStats?.anomalyScore);
|
||||
|
||||
return serviceAnomalyStats;
|
||||
}
|
||||
|
||||
function getBorderColorFn(
|
||||
|
@ -32,10 +32,11 @@ function getBorderColorFn(
|
|||
): cytoscape.Css.MapperFunction<cytoscape.NodeSingular, string> {
|
||||
return (el: cytoscape.NodeSingular) => {
|
||||
const hasAnomalyDetectionJob = el.data('serviceAnomalyStats') !== undefined;
|
||||
const nodeSeverity = getNodeSeverity(el);
|
||||
const anomalyStats = getServiceAnomalyStats(el);
|
||||
if (hasAnomalyDetectionJob) {
|
||||
return (
|
||||
getSeverityColor(theme, nodeSeverity) || theme.eui.euiColorMediumShade
|
||||
return getServiceHealthStatusColor(
|
||||
theme,
|
||||
anomalyStats?.healthStatus ?? ServiceHealthStatus.unknown
|
||||
);
|
||||
}
|
||||
if (el.hasClass('primary') || el.selected()) {
|
||||
|
@ -49,8 +50,8 @@ const getBorderStyle: cytoscape.Css.MapperFunction<
|
|||
cytoscape.NodeSingular,
|
||||
cytoscape.Css.LineStyle
|
||||
> = (el: cytoscape.NodeSingular) => {
|
||||
const nodeSeverity = getNodeSeverity(el);
|
||||
if (nodeSeverity === Severity.critical) {
|
||||
const status = getServiceAnomalyStats(el)?.healthStatus;
|
||||
if (status === ServiceHealthStatus.critical) {
|
||||
return 'double';
|
||||
} else {
|
||||
return 'solid';
|
||||
|
@ -58,11 +59,11 @@ const getBorderStyle: cytoscape.Css.MapperFunction<
|
|||
};
|
||||
|
||||
function getBorderWidth(el: cytoscape.NodeSingular) {
|
||||
const nodeSeverity = getNodeSeverity(el);
|
||||
const status = getServiceAnomalyStats(el)?.healthStatus;
|
||||
|
||||
if (nodeSeverity === Severity.minor || nodeSeverity === Severity.major) {
|
||||
if (status === ServiceHealthStatus.warning) {
|
||||
return 4;
|
||||
} else if (nodeSeverity === Severity.critical) {
|
||||
} else if (status === ServiceHealthStatus.critical) {
|
||||
return 8;
|
||||
} else {
|
||||
return 4;
|
||||
|
|
|
@ -6,20 +6,22 @@
|
|||
import React from 'react';
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
import {
|
||||
getSeverityColor,
|
||||
getSeverityLabel,
|
||||
Severity,
|
||||
} from '../../../../../common/anomaly_detection';
|
||||
getServiceHealthStatusColor,
|
||||
getServiceHealthStatusLabel,
|
||||
ServiceHealthStatus,
|
||||
} from '../../../../../common/service_health_status';
|
||||
import { useTheme } from '../../../../hooks/useTheme';
|
||||
|
||||
export function HealthBadge({ severity }: { severity?: Severity }) {
|
||||
export function HealthBadge({
|
||||
healthStatus,
|
||||
}: {
|
||||
healthStatus: ServiceHealthStatus;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
|
||||
const unknownColor = theme.eui.euiColorLightShade;
|
||||
|
||||
return (
|
||||
<EuiBadge color={getSeverityColor(theme, severity) ?? unknownColor}>
|
||||
{getSeverityLabel(severity)}
|
||||
<EuiBadge color={getServiceHealthStatusColor(theme, healthStatus)}>
|
||||
{getServiceHealthStatusLabel(healthStatus)}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { shallow } from 'enzyme';
|
|||
import { ServiceList, SERVICE_COLUMNS } from '../index';
|
||||
import props from './props.json';
|
||||
import { mockMoment } from '../../../../../utils/testHelpers';
|
||||
import { ServiceHealthStatus } from '../../../../../../common/service_health_status';
|
||||
|
||||
describe('ServiceOverview -> List', () => {
|
||||
beforeAll(() => {
|
||||
|
@ -52,25 +53,28 @@ describe('ServiceOverview -> List', () => {
|
|||
|
||||
describe('without ML data', () => {
|
||||
it('does not render health column', () => {
|
||||
const wrapper = shallow(
|
||||
<ServiceList items={props.items} displayHealthStatus={false} />
|
||||
);
|
||||
const wrapper = shallow(<ServiceList items={props.items} />);
|
||||
|
||||
const columns = wrapper.props().columns;
|
||||
|
||||
expect(columns[0].field).not.toBe('severity');
|
||||
expect(columns[0].field).not.toBe('healthStatus');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with ML data', () => {
|
||||
it('renders health column', () => {
|
||||
const wrapper = shallow(
|
||||
<ServiceList items={props.items} displayHealthStatus />
|
||||
<ServiceList
|
||||
items={props.items.map((item) => ({
|
||||
...item,
|
||||
healthStatus: ServiceHealthStatus.warning,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
const columns = wrapper.props().columns;
|
||||
|
||||
expect(columns[0].field).toBe('severity');
|
||||
expect(columns[0].field).toBe('healthStatus');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ServiceOverview -> List renders columns correctly 1`] = `<HealthBadge />`;
|
||||
exports[`ServiceOverview -> List renders columns correctly 1`] = `
|
||||
<HealthBadge
|
||||
healthStatus="unknown"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`ServiceOverview -> List renders empty state 1`] = `
|
||||
<Memo(UnoptimizedManagedTable)
|
||||
|
@ -51,7 +55,7 @@ exports[`ServiceOverview -> List renders empty state 1`] = `
|
|||
}
|
||||
initialPageSize={50}
|
||||
initialSortDirection="desc"
|
||||
initialSortField="severity"
|
||||
initialSortField="healthStatus"
|
||||
items={Array []}
|
||||
sortFn={[Function]}
|
||||
/>
|
||||
|
@ -106,7 +110,7 @@ exports[`ServiceOverview -> List renders with data 1`] = `
|
|||
}
|
||||
initialPageSize={50}
|
||||
initialSortDirection="desc"
|
||||
initialSortField="severity"
|
||||
initialSortField="healthStatus"
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
|
|
|
@ -10,6 +10,7 @@ import React from 'react';
|
|||
import styled from 'styled-components';
|
||||
import { ValuesType } from 'utility-types';
|
||||
import { orderBy } from 'lodash';
|
||||
import { ServiceHealthStatus } from '../../../../../common/service_health_status';
|
||||
import { asPercent } from '../../../../../common/utils/formatters';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
import { ServiceListAPIResponse } from '../../../../../server/lib/services/get_services';
|
||||
|
@ -20,14 +21,12 @@ import { ManagedTable, ITableColumn } from '../../../shared/ManagedTable';
|
|||
import { EnvironmentBadge } from '../../../shared/EnvironmentBadge';
|
||||
import { TransactionOverviewLink } from '../../../shared/Links/apm/TransactionOverviewLink';
|
||||
import { AgentIcon } from '../../../shared/AgentIcon';
|
||||
import { Severity } from '../../../../../common/anomaly_detection';
|
||||
import { HealthBadge } from './HealthBadge';
|
||||
import { ServiceListMetric } from './ServiceListMetric';
|
||||
|
||||
interface Props {
|
||||
items: ServiceListAPIResponse['items'];
|
||||
noItemsMessage?: React.ReactNode;
|
||||
displayHealthStatus: boolean;
|
||||
}
|
||||
|
||||
type ServiceListItem = ValuesType<Props['items']>;
|
||||
|
@ -53,14 +52,18 @@ const AppLink = styled(TransactionOverviewLink)`
|
|||
|
||||
export const SERVICE_COLUMNS: Array<ITableColumn<ServiceListItem>> = [
|
||||
{
|
||||
field: 'severity',
|
||||
field: 'healthStatus',
|
||||
name: i18n.translate('xpack.apm.servicesTable.healthColumnLabel', {
|
||||
defaultMessage: 'Health',
|
||||
}),
|
||||
width: px(unit * 6),
|
||||
sortable: true,
|
||||
render: (_, { severity }) => {
|
||||
return <HealthBadge severity={severity} />;
|
||||
render: (_, { healthStatus }) => {
|
||||
return (
|
||||
<HealthBadge
|
||||
healthStatus={healthStatus ?? ServiceHealthStatus.unknown}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -172,40 +175,38 @@ export const SERVICE_COLUMNS: Array<ITableColumn<ServiceListItem>> = [
|
|||
},
|
||||
];
|
||||
|
||||
const SEVERITY_ORDER = [
|
||||
Severity.warning,
|
||||
Severity.minor,
|
||||
Severity.major,
|
||||
Severity.critical,
|
||||
const SERVICE_HEALTH_STATUS_ORDER = [
|
||||
ServiceHealthStatus.unknown,
|
||||
ServiceHealthStatus.healthy,
|
||||
ServiceHealthStatus.warning,
|
||||
ServiceHealthStatus.critical,
|
||||
];
|
||||
|
||||
export function ServiceList({
|
||||
items,
|
||||
displayHealthStatus,
|
||||
noItemsMessage,
|
||||
}: Props) {
|
||||
export function ServiceList({ items, noItemsMessage }: Props) {
|
||||
const displayHealthStatus = items.some((item) => 'healthStatus' in item);
|
||||
|
||||
const columns = displayHealthStatus
|
||||
? SERVICE_COLUMNS
|
||||
: SERVICE_COLUMNS.filter((column) => column.field !== 'severity');
|
||||
: SERVICE_COLUMNS.filter((column) => column.field !== 'healthStatus');
|
||||
|
||||
return (
|
||||
<ManagedTable
|
||||
columns={columns}
|
||||
items={items}
|
||||
noItemsMessage={noItemsMessage}
|
||||
initialSortField="severity"
|
||||
initialSortField="healthStatus"
|
||||
initialSortDirection="desc"
|
||||
initialPageSize={50}
|
||||
sortFn={(itemsToSort, sortField, sortDirection) => {
|
||||
// For severity, sort items by severity first, then by TPM
|
||||
// For healthStatus, sort items by healthStatus first, then by TPM
|
||||
|
||||
return sortField === 'severity'
|
||||
return sortField === 'healthStatus'
|
||||
? orderBy(
|
||||
itemsToSort,
|
||||
[
|
||||
(item) => {
|
||||
return item.severity
|
||||
? SEVERITY_ORDER.indexOf(item.severity)
|
||||
return item.healthStatus
|
||||
? SERVICE_HEALTH_STATUS_ORDER.indexOf(item.healthStatus)
|
||||
: -1;
|
||||
},
|
||||
(item) => item.transactionsPerMinute?.value ?? 0,
|
||||
|
|
|
@ -10,6 +10,7 @@ import { merge } from 'lodash';
|
|||
import React, { FunctionComponent, ReactChild } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { createKibanaReactContext } from 'src/plugins/kibana_react/public';
|
||||
import { ServiceHealthStatus } from '../../../../../common/service_health_status';
|
||||
import { ServiceOverview } from '..';
|
||||
import { EuiThemeProvider } from '../../../../../../observability/public';
|
||||
import { ApmPluginContextValue } from '../../../../context/ApmPluginContext';
|
||||
|
@ -114,7 +115,7 @@ describe('Service Overview -> View', () => {
|
|||
errorsPerMinute: 200,
|
||||
avgResponseTime: 300,
|
||||
environments: ['test', 'dev'],
|
||||
severity: 1,
|
||||
healthStatus: ServiceHealthStatus.warning,
|
||||
},
|
||||
{
|
||||
serviceName: 'My Go Service',
|
||||
|
@ -123,7 +124,7 @@ describe('Service Overview -> View', () => {
|
|||
errorsPerMinute: 500,
|
||||
avgResponseTime: 600,
|
||||
environments: [],
|
||||
severity: 10,
|
||||
severity: ServiceHealthStatus.healthy,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -252,7 +253,7 @@ describe('Service Overview -> View', () => {
|
|||
errorsPerMinute: 200,
|
||||
avgResponseTime: 300,
|
||||
environments: ['test', 'dev'],
|
||||
severity: 1,
|
||||
healthStatus: ServiceHealthStatus.warning,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -153,8 +153,8 @@ NodeList [
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color: rgb(211, 218, 230); color: rgb(0, 0, 0);"
|
||||
title="Unknown"
|
||||
style="background-color: rgb(214, 191, 87); color: rgb(0, 0, 0);"
|
||||
title="Warning"
|
||||
>
|
||||
<span
|
||||
class="euiBadge__content"
|
||||
|
@ -162,7 +162,7 @@ NodeList [
|
|||
<span
|
||||
class="euiBadge__text"
|
||||
>
|
||||
Unknown
|
||||
Warning
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -435,7 +435,7 @@ NodeList [
|
|||
>
|
||||
<span
|
||||
class="euiBadge euiBadge--iconLeft"
|
||||
style="background-color: rgb(211, 218, 230); color: rgb(0, 0, 0);"
|
||||
style="background-color: rgb(152, 162, 179); color: rgb(0, 0, 0);"
|
||||
title="Unknown"
|
||||
>
|
||||
<span
|
||||
|
|
|
@ -114,8 +114,6 @@ export function ServiceOverview() {
|
|||
canCreateJob &&
|
||||
!userHasDismissedCallout;
|
||||
|
||||
const displayHealthStatus = data.items.some((item) => 'severity' in item);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
@ -134,7 +132,6 @@ export function ServiceOverview() {
|
|||
<EuiPanel>
|
||||
<ServiceList
|
||||
items={data.items}
|
||||
displayHealthStatus={displayHealthStatus}
|
||||
noItemsMessage={
|
||||
<NoServicesMessage
|
||||
historicalDataFound={data.hasHistoricalData}
|
||||
|
|
|
@ -6,8 +6,13 @@
|
|||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ANOMALY_SEVERITY } from '../../../../ml/common';
|
||||
import { KibanaRequest } from '../../../../../../src/core/server';
|
||||
import { AlertType, ALERT_TYPES_CONFIG } from '../../../common/alert_types';
|
||||
import {
|
||||
AlertType,
|
||||
ALERT_TYPES_CONFIG,
|
||||
ANOMALY_ALERT_SEVERITY_TYPES,
|
||||
} from '../../../common/alert_types';
|
||||
import { AlertingPlugin } from '../../../../alerts/server';
|
||||
import { APMConfig } from '../..';
|
||||
import { MlPluginSetup } from '../../../../ml/server';
|
||||
|
@ -26,7 +31,12 @@ const paramsSchema = schema.object({
|
|||
windowSize: schema.number(),
|
||||
windowUnit: schema.string(),
|
||||
environment: schema.string(),
|
||||
anomalyScore: schema.number(),
|
||||
anomalySeverityType: schema.oneOf([
|
||||
schema.literal(ANOMALY_SEVERITY.CRITICAL),
|
||||
schema.literal(ANOMALY_SEVERITY.MAJOR),
|
||||
schema.literal(ANOMALY_SEVERITY.MINOR),
|
||||
schema.literal(ANOMALY_SEVERITY.WARNING),
|
||||
]),
|
||||
});
|
||||
|
||||
const alertTypeConfig =
|
||||
|
@ -67,6 +77,18 @@ export function registerTransactionDurationAnomalyAlertType({
|
|||
alertParams.environment
|
||||
);
|
||||
|
||||
const selectedOption = ANOMALY_ALERT_SEVERITY_TYPES.find(
|
||||
(option) => option.type === alertParams.anomalySeverityType
|
||||
);
|
||||
|
||||
if (!selectedOption) {
|
||||
throw new Error(
|
||||
`Anomaly alert severity type ${alertParams.anomalySeverityType} is not supported.`
|
||||
);
|
||||
}
|
||||
|
||||
const threshold = selectedOption.threshold;
|
||||
|
||||
if (mlJobIds.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
@ -96,7 +118,7 @@ export function registerTransactionDurationAnomalyAlertType({
|
|||
{
|
||||
range: {
|
||||
record_score: {
|
||||
gte: alertParams.anomalyScore,
|
||||
gte: threshold,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import Boom from 'boom';
|
||||
import { getServiceHealthStatus } from '../../../common/service_health_status';
|
||||
import { Setup, SetupTimeRange } from '../helpers/setup_request';
|
||||
import { PromiseReturnType } from '../../../typings/common';
|
||||
import {
|
||||
|
@ -12,6 +13,7 @@ import {
|
|||
} from '../../../common/transaction_types';
|
||||
import {
|
||||
ServiceAnomalyStats,
|
||||
getSeverity,
|
||||
ML_ERRORS,
|
||||
} from '../../../common/anomaly_detection';
|
||||
import { getMlJobsWithAPMGroup } from '../anomaly_detection/get_ml_jobs_with_apm_group';
|
||||
|
@ -130,13 +132,19 @@ function transformResponseToServiceAnomalies(
|
|||
response.aggregations?.services.buckets ?? []
|
||||
).reduce(
|
||||
(statsByServiceName, { key: serviceName, top_score: topScoreAgg }) => {
|
||||
const anomalyScore = topScoreAgg.hits.hits[0]?.sort?.[0];
|
||||
|
||||
const severity = getSeverity(anomalyScore);
|
||||
const healthStatus = getServiceHealthStatus({ severity });
|
||||
|
||||
return {
|
||||
...statsByServiceName,
|
||||
[serviceName]: {
|
||||
transactionType: topScoreAgg.hits.hits[0]?._source?.by_field_value,
|
||||
anomalyScore: topScoreAgg.hits.hits[0]?.sort?.[0],
|
||||
anomalyScore,
|
||||
actualValue: topScoreAgg.hits.hits[0]?._source?.actual?.[0],
|
||||
jobId: topScoreAgg.hits.hits[0]?._source?.job_id,
|
||||
healthStatus,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ServiceHealthStatus } from '../../../common/service_health_status';
|
||||
|
||||
import {
|
||||
AGENT_NAME,
|
||||
SERVICE_ENVIRONMENT,
|
||||
|
@ -43,6 +45,7 @@ const anomalies = {
|
|||
actualValue: 10000,
|
||||
anomalyScore: 50,
|
||||
jobId: 'apm-test-1234-ml-module-name',
|
||||
healthStatus: ServiceHealthStatus.warning,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { getServiceHealthStatus } from '../../../../common/service_health_status';
|
||||
import { EventOutcome } from '../../../../common/event_outcome';
|
||||
import { getSeverity } from '../../../../common/anomaly_detection';
|
||||
import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent';
|
||||
|
@ -413,10 +414,11 @@ export const getHealthStatuses = async (
|
|||
const stats = anomalies.serviceAnomalies[serviceName];
|
||||
|
||||
const severity = getSeverity(stats.anomalyScore);
|
||||
const healthStatus = getServiceHealthStatus({ severity });
|
||||
|
||||
return {
|
||||
serviceName,
|
||||
severity,
|
||||
healthStatus,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,3 +5,5 @@
|
|||
*/
|
||||
|
||||
export { SearchResponse7 } from './types/es_client';
|
||||
export { ANOMALY_SEVERITY, ANOMALY_THRESHOLD } from './constants/anomalies';
|
||||
export { getSeverityColor, getSeverityType } from './util/anomaly_utils';
|
||||
|
|
|
@ -35,5 +35,8 @@
|
|||
"dashboard",
|
||||
"savedObjects",
|
||||
"home"
|
||||
],
|
||||
"extraPublicDirs": [
|
||||
"common"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -104,6 +105,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -130,6 +132,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -156,6 +159,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -209,6 +213,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -255,6 +260,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -301,6 +307,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -407,6 +414,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -465,6 +473,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -491,6 +500,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -504,6 +514,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -523,6 +534,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -548,6 +560,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -573,6 +586,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -605,6 +619,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -687,6 +702,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -734,6 +750,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -748,6 +765,7 @@ Array [
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -806,6 +824,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -833,6 +852,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -859,6 +879,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -885,6 +906,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -938,6 +960,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -984,6 +1007,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1030,6 +1054,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1136,6 +1161,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1194,6 +1220,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1220,6 +1247,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1233,6 +1261,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1252,6 +1281,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1277,6 +1307,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1302,6 +1333,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1334,6 +1366,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1416,6 +1449,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1463,6 +1497,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -1477,6 +1512,7 @@ Object {
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
|
|
@ -165,6 +165,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext)
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 3933482.1764705875,
|
||||
"anomalyScore": 2.6101702751482714,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-testing-d457-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
@ -179,6 +180,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext)
|
|||
"serviceAnomalyStats": Object {
|
||||
"actualValue": 684716.5813953485,
|
||||
"anomalyScore": 0.20498907719907372,
|
||||
"healthStatus": "healthy",
|
||||
"jobId": "apm-production-229a-high_mean_transaction_duration",
|
||||
"transactionType": "request",
|
||||
},
|
||||
|
|
|
@ -45,24 +45,24 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
expect(response.body.items.length).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('some items have severity set', () => {
|
||||
it('some items have a health status set', () => {
|
||||
// Under the assumption that the loaded archive has
|
||||
// at least one APM ML job, and the time range is longer
|
||||
// than 15m, at least one items should have severity set.
|
||||
// Note that we currently have a bug where healthy services
|
||||
// report as unknown (so without any severity status):
|
||||
// than 15m, at least one items should have a health status
|
||||
// set. Note that we currently have a bug where healthy
|
||||
// services report as unknown (so without any health status):
|
||||
// https://github.com/elastic/kibana/issues/77083
|
||||
|
||||
const severityScores = response.body.items.map((item: any) => item.severity);
|
||||
const healthStatuses = response.body.items.map((item: any) => item.healthStatus);
|
||||
|
||||
expect(severityScores.filter(Boolean).length).to.be.greaterThan(0);
|
||||
expect(healthStatuses.filter(Boolean).length).to.be.greaterThan(0);
|
||||
|
||||
expectSnapshot(severityScores).toMatchInline(`
|
||||
expectSnapshot(healthStatuses).toMatchInline(`
|
||||
Array [
|
||||
undefined,
|
||||
undefined,
|
||||
"warning",
|
||||
"warning",
|
||||
"healthy",
|
||||
"healthy",
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
|
|
Loading…
Reference in a new issue