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

[APM] Service maps anomaly detection status in popover #65217

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 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);
});
Comment on lines +137 to +141
Copy link
Contributor Author

@ogupte ogupte May 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was necessary to add since the data of each node wasn't being updated when existing elements were re-added. Before adding this, the displayed anomaly data was stale after updating date ranges.

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];
sorenlouv marked this conversation as resolved.
Show resolved Hide resolved
// 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;
Copy link
Member

@sorenlouv sorenlouv May 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are all any because selectedNodeData is vaguely typed. Currently:

  selectedNodeData: cytoscape.NodeDataDefinition;

Should be changed to something like:

  selectedNodeData: ServiceNode;

where ServiceNode an intersection type:

type ServiceNode = cytoscape.NodeDataDefinition & TypeWithAnomalyDetectionFields

I expect TypeWithAnomalyDetectionFields is defined somewhere on the backend like:

interface TypeWithAnomalyDetectionFields {
  severity?: string;
  maxScore?: string;
 ...
}

(or perhaps it can be inferred from a ReturnType)

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