forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[APM] Service maps - Fix missing ML status for services with jobs but…
… no anomalies (elastic#68486) * Closes elastic#68485 by: - use the ML plugin to query for all APM jobs - inspect the ml job groups to find all jobs related to a particular service - use the mlAnomalySearch client to get ml job buckets with the max anomaly score - query for the model_plot buckets to obtain actual/median values for the ML description - return the relevant ML job with the max anomaly score for a service - indicate to the user that no anomalies were found for a service with an ml job * - Use the anomalyDetectorsProvider jobs API rather than the search endpoint directly - Defines a specific return type for the ml jobs api - Update the empty anomaly data message * Code and types cleanup * Return to using record result type on anomaly queries. These are the same values used in the anomaly explorer and it includes actual & typical values which greatly improve performance of the previous query. * - If anomaly data is missing show a gray border around node * - moved AnomalyDetection out of service map Contents into own component Co-authored-by: Elastic Machine <[email protected]>
- Loading branch information
1 parent
8fcd6a9
commit 56a37be
Showing
20 changed files
with
488 additions
and
303 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* 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 { Assign, Omit } from 'utility-types'; | ||
|
||
export function leftJoin< | ||
TL extends object, | ||
K extends keyof TL, | ||
TR extends Pick<TL, K> | ||
>(leftRecords: TL[], matchKey: K, rightRecords: TR[]) { | ||
const rightLookup = new Map( | ||
rightRecords.map((record) => [record[matchKey], record]) | ||
); | ||
return leftRecords.map((record) => { | ||
const matchProp = (record[matchKey] as unknown) as TR[K]; | ||
const matchingRightRecord = rightLookup.get(matchProp); | ||
return { ...record, ...matchingRightRecord }; | ||
}) as Array<Assign<TL, Partial<Omit<TR, K>>>>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,8 @@ | |
"actions", | ||
"alerts", | ||
"observability", | ||
"security" | ||
"security", | ||
"ml" | ||
], | ||
"server": true, | ||
"ui": true, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
156 changes: 156 additions & 0 deletions
156
x-pack/plugins/apm/public/components/app/ServiceMap/Popover/anomaly_detection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
/* | ||
* 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 React from 'react'; | ||
import styled from 'styled-components'; | ||
import theme from '@elastic/eui/dist/eui_theme_light.json'; | ||
import { | ||
EuiFlexGroup, | ||
EuiFlexItem, | ||
EuiTitle, | ||
EuiIconTip, | ||
EuiHealth, | ||
} from '@elastic/eui'; | ||
import { fontSize, px } from '../../../../style/variables'; | ||
import { asInteger } from '../../../../utils/formatters'; | ||
import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink'; | ||
import { getSeverityColor, popoverMinWidth } from '../cytoscapeOptions'; | ||
import { getMetricChangeDescription } from '../../../../../../ml/public'; | ||
import { ServiceNode } from '../../../../../common/service_map'; | ||
|
||
const HealthStatusTitle = styled(EuiTitle)` | ||
display: inline; | ||
text-transform: uppercase; | ||
`; | ||
|
||
const VerticallyCentered = styled.div` | ||
display: flex; | ||
align-items: center; | ||
`; | ||
|
||
const SubduedText = styled.span` | ||
color: ${theme.euiTextSubduedColor}; | ||
`; | ||
|
||
const EnableText = styled.section` | ||
color: ${theme.euiTextSubduedColor}; | ||
line-height: 1.4; | ||
font-size: ${fontSize}; | ||
width: ${px(popoverMinWidth)}; | ||
`; | ||
|
||
export const ContentLine = styled.section` | ||
line-height: 2; | ||
`; | ||
|
||
interface AnomalyDetectionProps { | ||
serviceNodeData: cytoscape.NodeDataDefinition & ServiceNode; | ||
} | ||
|
||
export function AnomalyDetection({ serviceNodeData }: AnomalyDetectionProps) { | ||
const anomalySeverity = serviceNodeData.anomaly_severity; | ||
const anomalyScore = serviceNodeData.anomaly_score; | ||
const actualValue = serviceNodeData.actual_value; | ||
const typicalValue = serviceNodeData.typical_value; | ||
const mlJobId = serviceNodeData.ml_job_id; | ||
const hasAnomalyDetectionScore = | ||
anomalySeverity !== undefined && anomalyScore !== undefined; | ||
const anomalyDescription = | ||
hasAnomalyDetectionScore && | ||
actualValue !== undefined && | ||
typicalValue !== undefined | ||
? getMetricChangeDescription(actualValue, typicalValue).message | ||
: null; | ||
|
||
return ( | ||
<> | ||
<section> | ||
<HealthStatusTitle size="xxs"> | ||
<h3>{ANOMALY_DETECTION_TITLE}</h3> | ||
</HealthStatusTitle> | ||
| ||
<EuiIconTip type="iInCircle" content={ANOMALY_DETECTION_TOOLTIP} /> | ||
{!mlJobId && <EnableText>{ANOMALY_DETECTION_DISABLED_TEXT}</EnableText>} | ||
</section> | ||
{hasAnomalyDetectionScore && ( | ||
<ContentLine> | ||
<EuiFlexGroup> | ||
<EuiFlexItem> | ||
<VerticallyCentered> | ||
<EuiHealth color={getSeverityColor(anomalySeverity)} /> | ||
<SubduedText>{ANOMALY_DETECTION_SCORE_METRIC}</SubduedText> | ||
</VerticallyCentered> | ||
</EuiFlexItem> | ||
<EuiFlexItem grow={false}> | ||
<div> | ||
{getDisplayedAnomalyScore(anomalyScore as number)} | ||
{anomalyDescription && ( | ||
<SubduedText> ({anomalyDescription})</SubduedText> | ||
)} | ||
</div> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
</ContentLine> | ||
)} | ||
{mlJobId && !hasAnomalyDetectionScore && ( | ||
<EnableText>{ANOMALY_DETECTION_NO_DATA_TEXT}</EnableText> | ||
)} | ||
{mlJobId && ( | ||
<ContentLine> | ||
<MLJobLink external jobId={mlJobId}> | ||
{ANOMALY_DETECTION_LINK} | ||
</MLJobLink> | ||
</ContentLine> | ||
)} | ||
</> | ||
); | ||
} | ||
|
||
function getDisplayedAnomalyScore(score: number) { | ||
if (score > 0 && score < 1) { | ||
return '< 1'; | ||
} | ||
return asInteger(score); | ||
} | ||
|
||
const ANOMALY_DETECTION_TITLE = i18n.translate( | ||
'xpack.apm.serviceMap.anomalyDetectionPopoverTitle', | ||
{ defaultMessage: 'Anomaly Detection' } | ||
); | ||
|
||
const ANOMALY_DETECTION_TOOLTIP = i18n.translate( | ||
'xpack.apm.serviceMap.anomalyDetectionPopoverTooltip', | ||
{ | ||
defaultMessage: | ||
'Service health indicators are powered by the anomaly detection feature in machine learning', | ||
} | ||
); | ||
|
||
const ANOMALY_DETECTION_SCORE_METRIC = i18n.translate( | ||
'xpack.apm.serviceMap.anomalyDetectionPopoverScoreMetric', | ||
{ defaultMessage: 'Score (max.)' } | ||
); | ||
|
||
const ANOMALY_DETECTION_LINK = i18n.translate( | ||
'xpack.apm.serviceMap.anomalyDetectionPopoverLink', | ||
{ defaultMessage: 'View anomalies' } | ||
); | ||
|
||
const ANOMALY_DETECTION_DISABLED_TEXT = i18n.translate( | ||
'xpack.apm.serviceMap.anomalyDetectionPopoverDisabled', | ||
{ | ||
defaultMessage: | ||
'Display service health indicators by enabling anomaly detection from the Integrations menu in the Service details view.', | ||
} | ||
); | ||
|
||
const ANOMALY_DETECTION_NO_DATA_TEXT = i18n.translate( | ||
'xpack.apm.serviceMap.anomalyDetectionPopoverNoData', | ||
{ | ||
defaultMessage: `We couldn't find an anomaly score within the selected time range. See details in the anomaly explorer.`, | ||
} | ||
); |
Oops, something went wrong.