Skip to content

Commit

Permalink
[APM] Service maps anomaly detection status in popover (elastic#65217)
Browse files Browse the repository at this point in the history
* Adds ML status to the service maps popover. Modifies the query a bit to
get the actual vs typical values necessary to generate a proper anomaly description

* fixed failures and updated tests

* component clean up

* makes the ML link open in a new window

* - Closes elastic#64278 by removing the framework badge.
- changes anomaly score display to integer formatting
- update link test to 'View anomalies'

* - Closes elastic#65244 by displaying a message for services without anomalies detected
- removes unecessary service framework name in service map queries
- adds date range filter for anomaly detection
  • Loading branch information
ogupte committed May 5, 2020
1 parent bec09fd commit 4896b65
Show file tree
Hide file tree
Showing 17 changed files with 611 additions and 421 deletions.
2 changes: 0 additions & 2 deletions x-pack/plugins/apm/common/service_map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { ILicense } from '../../licensing/public';
import {
AGENT_NAME,
SERVICE_ENVIRONMENT,
SERVICE_FRAMEWORK_NAME,
SERVICE_NAME,
SPAN_SUBTYPE,
SPAN_TYPE,
Expand All @@ -19,7 +18,6 @@ import {
export interface ServiceConnectionNode {
[SERVICE_NAME]: string;
[SERVICE_ENVIRONMENT]: string | null;
[SERVICE_FRAMEWORK_NAME]: string | null;
[AGENT_NAME]: string;
}
export interface ExternalConnectionNode {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ export function Cytoscape({
);
cy.remove(absentElements);
cy.add(elements);
// ensure all elements get latest data properties
elements.forEach(elementDefinition => {
const el = cy.getElementById(elementDefinition.data.id as string);
el.data(elementDefinition.data);
});
cy.trigger('data');
}
}, [cy, elements]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiHorizontalRule,
EuiTitle
EuiTitle,
EuiIconTip,
EuiHealth
} from '@elastic/eui';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import cytoscape from 'cytoscape';
import React from 'react';
import { SERVICE_FRAMEWORK_NAME } from '../../../../../common/elasticsearch_fieldnames';
import styled from 'styled-components';
import { fontSize, px } from '../../../../style/variables';
import { Buttons } from './Buttons';
import { Info } from './Info';
import { ServiceMetricFetcher } from './ServiceMetricFetcher';
import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink';
import { getSeverityColor } from '../cytoscapeOptions';
import { asInteger } from '../../../../utils/formatters';
import { getMetricChangeDescription } from '../../../../../../ml/public';

const popoverMinWidth = 280;

Expand All @@ -27,6 +36,31 @@ interface ContentsProps {
selectedNodeServiceName: string;
}

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;
`;

// IE 11 does not handle flex properties as expected. With browser detection,
// we can use regular div elements to render contents that are almost identical.
//
Expand All @@ -51,14 +85,61 @@ const FlexColumnGroup = (props: {
const FlexColumnItem = (props: { children: React.ReactNode }) =>
isIE11 ? <div {...props} /> : <EuiFlexItem {...props} />;

const ANOMALY_DETECTION_TITLE = i18n.translate(
'xpack.apm.serviceMap.anomalyDetectionPopoverTitle',
{ defaultMessage: 'Anomaly Detection' }
);

const ANOMALY_DETECTION_INFO = i18n.translate(
'xpack.apm.serviceMap.anomalyDetectionPopoverInfo',
{
defaultMessage:
'Display the health of your service by enabling 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_ENABLE_TEXT = i18n.translate(
'xpack.apm.serviceMap.anomalyDetectionPopoverEnable',
{
defaultMessage:
'Enable anomaly detection from the Integrations menu in the Service details view.'
}
);

export function Contents({
selectedNodeData,
isService,
label,
onFocusClick,
selectedNodeServiceName
}: ContentsProps) {
const frameworkName = selectedNodeData[SERVICE_FRAMEWORK_NAME];
// Anomaly Detection
const severity = selectedNodeData.severity;
const maxScore = selectedNodeData.max_score;
const actualValue = selectedNodeData.actual_value;
const typicalValue = selectedNodeData.typical_value;
const jobId = selectedNodeData.job_id;
const hasAnomalyDetection = [
severity,
maxScore,
actualValue,
typicalValue,
jobId
].every(value => value !== undefined);
const anomalyDescription = hasAnomalyDetection
? getMetricChangeDescription(actualValue, typicalValue).message
: null;

return (
<FlexColumnGroup
direction="column"
Expand All @@ -71,12 +152,50 @@ export function Contents({
</EuiTitle>
<EuiHorizontalRule margin="xs" />
</FlexColumnItem>
{isService && (
<FlexColumnItem>
<section>
<HealthStatusTitle size="xxs">
<h3>{ANOMALY_DETECTION_TITLE}</h3>
</HealthStatusTitle>
&nbsp;
<EuiIconTip type="iInCircle" content={ANOMALY_DETECTION_INFO} />
</section>
{hasAnomalyDetection ? (
<>
<ContentLine>
<EuiFlexGroup>
<EuiFlexItem>
<VerticallyCentered>
<EuiHealth color={getSeverityColor(severity)} />
<SubduedText>
{ANOMALY_DETECTION_SCORE_METRIC}
</SubduedText>
</VerticallyCentered>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div>
{asInteger(maxScore)}
<SubduedText>&nbsp;({anomalyDescription})</SubduedText>
</div>
</EuiFlexItem>
</EuiFlexGroup>
</ContentLine>
<ContentLine>
<MLJobLink external jobId={jobId}>
{ANOMALY_DETECTION_LINK}
</MLJobLink>
</ContentLine>
</>
) : (
<EnableText>{ANOMALY_DETECTION_ENABLE_TEXT}</EnableText>
)}
<EuiHorizontalRule margin="xs" />
</FlexColumnItem>
)}
<FlexColumnItem>
{isService ? (
<ServiceMetricFetcher
frameworkName={frameworkName}
serviceName={selectedNodeServiceName}
/>
<ServiceMetricFetcher serviceName={selectedNodeServiceName} />
) : (
<Info {...selectedNodeData} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ storiesOf('app/ServiceMap/Popover/ServiceMetricList', module)
avgRequestsPerMinute={164.47222031860858}
avgCpuUsage={0.32809666568309237}
avgMemoryUsage={0.5504868173242986}
frameworkName="Spring"
numInstances={2}
isLoading={false}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import { useUrlParams } from '../../../../hooks/useUrlParams';
import { ServiceMetricList } from './ServiceMetricList';

interface ServiceMetricFetcherProps {
frameworkName?: string;
serviceName: string;
}

export function ServiceMetricFetcher({
frameworkName,
serviceName
}: ServiceMetricFetcherProps) {
const {
Expand All @@ -39,11 +37,5 @@ export function ServiceMetricFetcher({
);
const isLoading = status === 'loading';

return (
<ServiceMetricList
{...data}
frameworkName={frameworkName}
isLoading={isLoading}
/>
);
return <ServiceMetricList {...data} isLoading={isLoading} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,20 @@ const BadgeRow = styled(EuiFlexItem)`
padding-bottom: ${lightTheme.gutterTypes.gutterSmall};
`;

const ItemRow = styled('tr')`
export const ItemRow = styled('tr')`
line-height: 2;
`;

const ItemTitle = styled('td')`
export const ItemTitle = styled('td')`
color: ${lightTheme.textColors.subdued};
padding-right: 1rem;
`;

const ItemDescription = styled('td')`
export const ItemDescription = styled('td')`
text-align: right;
`;

interface ServiceMetricListProps extends ServiceNodeMetrics {
frameworkName?: string;
isLoading: boolean;
}

Expand All @@ -58,7 +57,6 @@ export function ServiceMetricList({
avgErrorsPerMinute,
avgCpuUsage,
avgMemoryUsage,
frameworkName,
numInstances,
isLoading
}: ServiceMetricListProps) {
Expand Down Expand Up @@ -112,7 +110,7 @@ export function ServiceMetricList({
: null
}
];
const showBadgeRow = frameworkName || numInstances > 1;
const showBadgeRow = numInstances > 1;

return isLoading ? (
<LoadingSpinner />
Expand All @@ -121,7 +119,6 @@ export function ServiceMetricList({
{showBadgeRow && (
<BadgeRow>
<EuiFlexGroup gutterSize="none">
{frameworkName && <EuiBadge>{frameworkName}</EuiBadge>}
{numInstances > 1 && (
<EuiBadge iconType="apps" color="hollow">
{i18n.translate('xpack.apm.serviceMap.numInstancesMetric', {
Expand Down
Loading

0 comments on commit 4896b65

Please sign in to comment.