diff --git a/x-pack/plugins/apm/common/service_map.ts b/x-pack/plugins/apm/common/service_map.ts
index 75c1c945c5d261..2ff30a61499b6b 100644
--- a/x-pack/plugins/apm/common/service_map.ts
+++ b/x-pack/plugins/apm/common/service_map.ts
@@ -9,7 +9,6 @@ import { ILicense } from '../../licensing/public';
import {
AGENT_NAME,
SERVICE_ENVIRONMENT,
- SERVICE_FRAMEWORK_NAME,
SERVICE_NAME,
SPAN_SUBTYPE,
SPAN_TYPE,
@@ -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 {
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx
index ad77434bca9f43..797145368b4b75 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx
@@ -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]);
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx
index bc3434f277d1c0..7e15d0116b84d1 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx
@@ -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;
@@ -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.
//
@@ -51,6 +85,37 @@ const FlexColumnGroup = (props: {
const FlexColumnItem = (props: { children: React.ReactNode }) =>
isIE11 ?
: ;
+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,
@@ -58,7 +123,23 @@ export function Contents({
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 (
+ {isService && (
+
+
+
+ {ANOMALY_DETECTION_TITLE}
+
+
+
+
+ {hasAnomalyDetection ? (
+ <>
+
+
+
+
+
+
+ {ANOMALY_DETECTION_SCORE_METRIC}
+
+
+
+
+
+ {asInteger(maxScore)}
+ ({anomalyDescription})
+
+
+
+
+
+
+ {ANOMALY_DETECTION_LINK}
+
+
+ >
+ ) : (
+ {ANOMALY_DETECTION_ENABLE_TEXT}
+ )}
+
+
+ )}
{isService ? (
-
+
) : (
)}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
index 23e9e737be9a65..e5962afd76eb8d 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx
@@ -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}
/>
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricFetcher.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricFetcher.tsx
index 5e6412333a2e18..6f67f7a4bed7af 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricFetcher.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricFetcher.tsx
@@ -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 {
@@ -39,11 +37,5 @@ export function ServiceMetricFetcher({
);
const isLoading = status === 'loading';
- return (
-
- );
+ return ;
}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx
index 3cee986261a68b..5c28fc0a5a7d0e 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx
@@ -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;
}
@@ -58,7 +57,6 @@ export function ServiceMetricList({
avgErrorsPerMinute,
avgCpuUsage,
avgMemoryUsage,
- frameworkName,
numInstances,
isLoading
}: ServiceMetricListProps) {
@@ -112,7 +110,7 @@ export function ServiceMetricList({
: null
}
];
- const showBadgeRow = frameworkName || numInstances > 1;
+ const showBadgeRow = numInstances > 1;
return isLoading ? (
@@ -121,7 +119,6 @@ export function ServiceMetricList({
{showBadgeRow && (
- {frameworkName && {frameworkName}}
{numInstances > 1 && (
{i18n.translate('xpack.apm.serviceMap.numInstancesMetric', {
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape-layout-test-response.json b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape-layout-test-response.json
index 4b4b5c2ed802e8..e55ba65bcbcb91 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape-layout-test-response.json
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape-layout-test-response.json
@@ -3,38 +3,19 @@
{
"data": {
"source": "apm-server",
- "target": ">172.17.0.1",
- "id": "apm-server~>172.17.0.1",
+ "target": ">elasticsearch",
+ "id": "apm-server~>elasticsearch",
"sourceData": {
- "service.environment": null,
+ "id": "apm-server",
"service.name": "apm-server",
- "agent.name": "go",
- "id": "apm-server"
- },
- "targetData": {
- "destination.address": "172.17.0.1",
- "span.subtype": "http",
- "span.type": "external",
- "id": ">172.17.0.1"
- }
- }
- },
- {
- "data": {
- "source": "client",
- "target": ">opbeans-node",
- "id": "client~>opbeans-node",
- "sourceData": {
- "service.environment": null,
- "service.name": "client",
- "agent.name": "js-base",
- "id": "client"
+ "agent.name": "go"
},
"targetData": {
- "destination.address": "opbeans-node",
- "span.subtype": null,
- "span.type": "resource",
- "id": ">opbeans-node"
+ "span.subtype": "elasticsearch",
+ "span.destination.service.resource": "elasticsearch",
+ "span.type": "db",
+ "id": ">elasticsearch",
+ "label": "elasticsearch"
}
}
},
@@ -44,135 +25,39 @@
"target": "opbeans-node",
"id": "client~opbeans-node",
"sourceData": {
- "service.environment": null,
+ "id": "client",
"service.name": "client",
- "agent.name": "js-base",
- "id": "client"
+ "agent.name": "rum-js"
},
"targetData": {
+ "id": "opbeans-node",
"service.environment": "production",
"service.name": "opbeans-node",
"agent.name": "nodejs",
- "id": "opbeans-node"
+ "service.framework.name": "express"
}
}
},
- {
- "data": {
- "source": "opbeans-dotnet",
- "target": "opbeans-go",
- "id": "opbeans-dotnet~opbeans-go",
- "sourceData": {
- "service.environment": "production",
- "service.name": "opbeans-dotnet",
- "agent.name": "dotnet",
- "id": "opbeans-dotnet"
- },
- "targetData": {
- "service.environment": "production",
- "service.name": "opbeans-go",
- "agent.name": "go",
- "id": "opbeans-go"
- },
- "bidirectional": true
- }
- },
- {
- "data": {
- "source": "opbeans-dotnet",
- "target": "opbeans-java",
- "id": "opbeans-dotnet~opbeans-java",
- "sourceData": {
- "service.environment": "production",
- "service.name": "opbeans-dotnet",
- "agent.name": "dotnet",
- "id": "opbeans-dotnet"
- },
- "targetData": {
- "service.environment": "production",
- "service.name": "opbeans-java",
- "agent.name": "java",
- "id": "opbeans-java"
- },
- "bidirectional": true
- }
- },
- {
- "data": {
- "source": "opbeans-dotnet",
- "target": "opbeans-node",
- "id": "opbeans-dotnet~opbeans-node",
- "sourceData": {
- "service.environment": "production",
- "service.name": "opbeans-dotnet",
- "agent.name": "dotnet",
- "id": "opbeans-dotnet"
- },
- "targetData": {
- "service.environment": "production",
- "service.name": "opbeans-node",
- "agent.name": "nodejs",
- "id": "opbeans-node"
- },
- "bidirectional": true
- }
- },
- {
- "data": {
- "source": "opbeans-dotnet",
- "target": "opbeans-python",
- "id": "opbeans-dotnet~opbeans-python",
- "sourceData": {
- "service.environment": "production",
- "service.name": "opbeans-dotnet",
- "agent.name": "dotnet",
- "id": "opbeans-dotnet"
- },
- "targetData": {
- "service.environment": "production",
- "service.name": "opbeans-python",
- "agent.name": "python",
- "id": "opbeans-python"
- },
- "bidirectional": true
- }
- },
- {
- "data": {
- "source": "opbeans-dotnet",
- "target": "opbeans-ruby",
- "id": "opbeans-dotnet~opbeans-ruby",
- "sourceData": {
- "service.environment": "production",
- "service.name": "opbeans-dotnet",
- "agent.name": "dotnet",
- "id": "opbeans-dotnet"
- },
- "targetData": {
- "service.environment": "production",
- "service.name": "opbeans-ruby",
- "agent.name": "ruby",
- "id": "opbeans-ruby"
- },
- "bidirectional": true
- }
- },
{
"data": {
"source": "opbeans-go",
- "target": ">postgres",
- "id": "opbeans-go~>postgres",
+ "target": ">postgresql",
+ "id": "opbeans-go~>postgresql",
"sourceData": {
+ "id": "opbeans-go",
"service.environment": "production",
"service.name": "opbeans-go",
"agent.name": "go",
- "id": "opbeans-go"
+ "service.framework.name": "gin",
+ "max_score": 92.40731,
+ "severity": "critical"
},
"targetData": {
- "destination.address": "postgres",
"span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
"span.type": "db",
- "id": ">postgres"
+ "id": ">postgresql",
+ "label": "postgresql"
}
}
},
@@ -182,18 +67,22 @@
"target": "opbeans-dotnet",
"id": "opbeans-go~opbeans-dotnet",
"sourceData": {
+ "id": "opbeans-go",
"service.environment": "production",
"service.name": "opbeans-go",
"agent.name": "go",
- "id": "opbeans-go"
+ "service.framework.name": "gin",
+ "max_score": 92.40731,
+ "severity": "critical"
},
"targetData": {
- "service.environment": "production",
+ "id": "opbeans-dotnet",
"service.name": "opbeans-dotnet",
"agent.name": "dotnet",
- "id": "opbeans-dotnet"
- },
- "isInverseEdge": true
+ "service.framework.name": "ASP.NET Core",
+ "max_score": 43.63972429875661,
+ "severity": "minor"
+ }
}
},
{
@@ -202,16 +91,21 @@
"target": "opbeans-java",
"id": "opbeans-go~opbeans-java",
"sourceData": {
+ "id": "opbeans-go",
"service.environment": "production",
"service.name": "opbeans-go",
"agent.name": "go",
- "id": "opbeans-go"
+ "service.framework.name": "gin",
+ "max_score": 92.40731,
+ "severity": "critical"
},
"targetData": {
+ "id": "opbeans-java",
"service.environment": "production",
"service.name": "opbeans-java",
"agent.name": "java",
- "id": "opbeans-java"
+ "max_score": 31.374423806075157,
+ "severity": "minor"
},
"bidirectional": true
}
@@ -222,16 +116,20 @@
"target": "opbeans-node",
"id": "opbeans-go~opbeans-node",
"sourceData": {
+ "id": "opbeans-go",
"service.environment": "production",
"service.name": "opbeans-go",
"agent.name": "go",
- "id": "opbeans-go"
+ "service.framework.name": "gin",
+ "max_score": 92.40731,
+ "severity": "critical"
},
"targetData": {
+ "id": "opbeans-node",
"service.environment": "production",
"service.name": "opbeans-node",
"agent.name": "nodejs",
- "id": "opbeans-node"
+ "service.framework.name": "express"
},
"bidirectional": true
}
@@ -242,16 +140,22 @@
"target": "opbeans-python",
"id": "opbeans-go~opbeans-python",
"sourceData": {
+ "id": "opbeans-go",
"service.environment": "production",
"service.name": "opbeans-go",
"agent.name": "go",
- "id": "opbeans-go"
+ "service.framework.name": "gin",
+ "max_score": 92.40731,
+ "severity": "critical"
},
"targetData": {
+ "id": "opbeans-python",
"service.environment": "production",
"service.name": "opbeans-python",
"agent.name": "python",
- "id": "opbeans-python"
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
},
"bidirectional": true
}
@@ -262,16 +166,20 @@
"target": "opbeans-ruby",
"id": "opbeans-go~opbeans-ruby",
"sourceData": {
+ "id": "opbeans-go",
"service.environment": "production",
"service.name": "opbeans-go",
"agent.name": "go",
- "id": "opbeans-go"
+ "service.framework.name": "gin",
+ "max_score": 92.40731,
+ "severity": "critical"
},
"targetData": {
+ "id": "opbeans-ruby",
"service.environment": "production",
"service.name": "opbeans-ruby",
"agent.name": "ruby",
- "id": "opbeans-ruby"
+ "service.framework.name": "Ruby on Rails"
},
"bidirectional": true
}
@@ -279,19 +187,22 @@
{
"data": {
"source": "opbeans-java",
- "target": ">postgres",
- "id": "opbeans-java~>postgres",
+ "target": ">postgresql",
+ "id": "opbeans-java~>postgresql",
"sourceData": {
+ "id": "opbeans-java",
"service.environment": "production",
"service.name": "opbeans-java",
"agent.name": "java",
- "id": "opbeans-java"
+ "max_score": 31.374423806075157,
+ "severity": "minor"
},
"targetData": {
- "destination.address": "postgres",
"span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
"span.type": "db",
- "id": ">postgres"
+ "id": ">postgresql",
+ "label": "postgresql"
}
}
},
@@ -301,18 +212,21 @@
"target": "opbeans-dotnet",
"id": "opbeans-java~opbeans-dotnet",
"sourceData": {
+ "id": "opbeans-java",
"service.environment": "production",
"service.name": "opbeans-java",
"agent.name": "java",
- "id": "opbeans-java"
+ "max_score": 31.374423806075157,
+ "severity": "minor"
},
"targetData": {
- "service.environment": "production",
+ "id": "opbeans-dotnet",
"service.name": "opbeans-dotnet",
"agent.name": "dotnet",
- "id": "opbeans-dotnet"
- },
- "isInverseEdge": true
+ "service.framework.name": "ASP.NET Core",
+ "max_score": 43.63972429875661,
+ "severity": "minor"
+ }
}
},
{
@@ -321,16 +235,21 @@
"target": "opbeans-go",
"id": "opbeans-java~opbeans-go",
"sourceData": {
+ "id": "opbeans-java",
"service.environment": "production",
"service.name": "opbeans-java",
"agent.name": "java",
- "id": "opbeans-java"
+ "max_score": 31.374423806075157,
+ "severity": "minor"
},
"targetData": {
+ "id": "opbeans-go",
"service.environment": "production",
"service.name": "opbeans-go",
"agent.name": "go",
- "id": "opbeans-go"
+ "service.framework.name": "gin",
+ "max_score": 92.40731,
+ "severity": "critical"
},
"isInverseEdge": true
}
@@ -341,16 +260,19 @@
"target": "opbeans-node",
"id": "opbeans-java~opbeans-node",
"sourceData": {
+ "id": "opbeans-java",
"service.environment": "production",
"service.name": "opbeans-java",
"agent.name": "java",
- "id": "opbeans-java"
+ "max_score": 31.374423806075157,
+ "severity": "minor"
},
"targetData": {
+ "id": "opbeans-node",
"service.environment": "production",
"service.name": "opbeans-node",
"agent.name": "nodejs",
- "id": "opbeans-node"
+ "service.framework.name": "express"
},
"bidirectional": true
}
@@ -361,16 +283,21 @@
"target": "opbeans-python",
"id": "opbeans-java~opbeans-python",
"sourceData": {
+ "id": "opbeans-java",
"service.environment": "production",
"service.name": "opbeans-java",
"agent.name": "java",
- "id": "opbeans-java"
+ "max_score": 31.374423806075157,
+ "severity": "minor"
},
"targetData": {
+ "id": "opbeans-python",
"service.environment": "production",
"service.name": "opbeans-python",
"agent.name": "python",
- "id": "opbeans-python"
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
},
"bidirectional": true
}
@@ -381,56 +308,43 @@
"target": "opbeans-ruby",
"id": "opbeans-java~opbeans-ruby",
"sourceData": {
+ "id": "opbeans-java",
"service.environment": "production",
"service.name": "opbeans-java",
"agent.name": "java",
- "id": "opbeans-java"
+ "max_score": 31.374423806075157,
+ "severity": "minor"
},
"targetData": {
+ "id": "opbeans-ruby",
"service.environment": "production",
"service.name": "opbeans-ruby",
"agent.name": "ruby",
- "id": "opbeans-ruby"
+ "service.framework.name": "Ruby on Rails"
},
"bidirectional": true
}
},
- {
- "data": {
- "source": "opbeans-node",
- "target": "opbeans-dotnet",
- "id": "opbeans-node~opbeans-dotnet",
- "sourceData": {
- "service.environment": "production",
- "service.name": "opbeans-node",
- "agent.name": "nodejs",
- "id": "opbeans-node"
- },
- "targetData": {
- "service.environment": "production",
- "service.name": "opbeans-dotnet",
- "agent.name": "dotnet",
- "id": "opbeans-dotnet"
- },
- "isInverseEdge": true
- }
- },
{
"data": {
"source": "opbeans-node",
"target": "opbeans-go",
"id": "opbeans-node~opbeans-go",
"sourceData": {
+ "id": "opbeans-node",
"service.environment": "production",
"service.name": "opbeans-node",
"agent.name": "nodejs",
- "id": "opbeans-node"
+ "service.framework.name": "express"
},
"targetData": {
+ "id": "opbeans-go",
"service.environment": "production",
"service.name": "opbeans-go",
"agent.name": "go",
- "id": "opbeans-go"
+ "service.framework.name": "gin",
+ "max_score": 92.40731,
+ "severity": "critical"
},
"isInverseEdge": true
}
@@ -441,16 +355,19 @@
"target": "opbeans-java",
"id": "opbeans-node~opbeans-java",
"sourceData": {
+ "id": "opbeans-node",
"service.environment": "production",
"service.name": "opbeans-node",
"agent.name": "nodejs",
- "id": "opbeans-node"
+ "service.framework.name": "express"
},
"targetData": {
+ "id": "opbeans-java",
"service.environment": "production",
"service.name": "opbeans-java",
"agent.name": "java",
- "id": "opbeans-java"
+ "max_score": 31.374423806075157,
+ "severity": "minor"
},
"isInverseEdge": true
}
@@ -461,16 +378,20 @@
"target": "opbeans-python",
"id": "opbeans-node~opbeans-python",
"sourceData": {
+ "id": "opbeans-node",
"service.environment": "production",
"service.name": "opbeans-node",
"agent.name": "nodejs",
- "id": "opbeans-node"
+ "service.framework.name": "express"
},
"targetData": {
+ "id": "opbeans-python",
"service.environment": "production",
"service.name": "opbeans-python",
"agent.name": "python",
- "id": "opbeans-python"
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
},
"bidirectional": true
}
@@ -481,38 +402,113 @@
"target": "opbeans-ruby",
"id": "opbeans-node~opbeans-ruby",
"sourceData": {
+ "id": "opbeans-node",
"service.environment": "production",
"service.name": "opbeans-node",
"agent.name": "nodejs",
- "id": "opbeans-node"
+ "service.framework.name": "express"
},
"targetData": {
+ "id": "opbeans-ruby",
"service.environment": "production",
"service.name": "opbeans-ruby",
"agent.name": "ruby",
- "id": "opbeans-ruby"
+ "service.framework.name": "Ruby on Rails"
},
"bidirectional": true
}
},
+ {
+ "data": {
+ "source": "opbeans-python",
+ "target": ">elasticsearch",
+ "id": "opbeans-python~>elasticsearch",
+ "sourceData": {
+ "id": "opbeans-python",
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python",
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
+ },
+ "targetData": {
+ "span.subtype": "elasticsearch",
+ "span.destination.service.resource": "elasticsearch",
+ "span.type": "db",
+ "id": ">elasticsearch",
+ "label": "elasticsearch"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "opbeans-python",
+ "target": ">postgresql",
+ "id": "opbeans-python~>postgresql",
+ "sourceData": {
+ "id": "opbeans-python",
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python",
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
+ },
+ "targetData": {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db",
+ "id": ">postgresql",
+ "label": "postgresql"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "opbeans-python",
+ "target": ">redis",
+ "id": "opbeans-python~>redis",
+ "sourceData": {
+ "id": "opbeans-python",
+ "service.environment": "production",
+ "service.name": "opbeans-python",
+ "agent.name": "python",
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
+ },
+ "targetData": {
+ "span.subtype": "redis",
+ "span.destination.service.resource": "redis",
+ "span.type": "db",
+ "id": ">redis",
+ "label": "redis"
+ }
+ }
+ },
{
"data": {
"source": "opbeans-python",
"target": "opbeans-dotnet",
"id": "opbeans-python~opbeans-dotnet",
"sourceData": {
+ "id": "opbeans-python",
"service.environment": "production",
"service.name": "opbeans-python",
"agent.name": "python",
- "id": "opbeans-python"
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
},
"targetData": {
- "service.environment": "production",
+ "id": "opbeans-dotnet",
"service.name": "opbeans-dotnet",
"agent.name": "dotnet",
- "id": "opbeans-dotnet"
- },
- "isInverseEdge": true
+ "service.framework.name": "ASP.NET Core",
+ "max_score": 43.63972429875661,
+ "severity": "minor"
+ }
}
},
{
@@ -521,16 +517,22 @@
"target": "opbeans-go",
"id": "opbeans-python~opbeans-go",
"sourceData": {
+ "id": "opbeans-python",
"service.environment": "production",
"service.name": "opbeans-python",
"agent.name": "python",
- "id": "opbeans-python"
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
},
"targetData": {
+ "id": "opbeans-go",
"service.environment": "production",
"service.name": "opbeans-go",
"agent.name": "go",
- "id": "opbeans-go"
+ "service.framework.name": "gin",
+ "max_score": 92.40731,
+ "severity": "critical"
},
"isInverseEdge": true
}
@@ -541,16 +543,21 @@
"target": "opbeans-java",
"id": "opbeans-python~opbeans-java",
"sourceData": {
+ "id": "opbeans-python",
"service.environment": "production",
"service.name": "opbeans-python",
"agent.name": "python",
- "id": "opbeans-python"
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
},
"targetData": {
+ "id": "opbeans-java",
"service.environment": "production",
"service.name": "opbeans-java",
"agent.name": "java",
- "id": "opbeans-java"
+ "max_score": 31.374423806075157,
+ "severity": "minor"
},
"isInverseEdge": true
}
@@ -561,16 +568,20 @@
"target": "opbeans-node",
"id": "opbeans-python~opbeans-node",
"sourceData": {
+ "id": "opbeans-python",
"service.environment": "production",
"service.name": "opbeans-python",
"agent.name": "python",
- "id": "opbeans-python"
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
},
"targetData": {
+ "id": "opbeans-node",
"service.environment": "production",
"service.name": "opbeans-node",
"agent.name": "nodejs",
- "id": "opbeans-node"
+ "service.framework.name": "express"
},
"isInverseEdge": true
}
@@ -581,38 +592,65 @@
"target": "opbeans-ruby",
"id": "opbeans-python~opbeans-ruby",
"sourceData": {
+ "id": "opbeans-python",
"service.environment": "production",
"service.name": "opbeans-python",
"agent.name": "python",
- "id": "opbeans-python"
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
},
"targetData": {
+ "id": "opbeans-ruby",
"service.environment": "production",
"service.name": "opbeans-ruby",
"agent.name": "ruby",
- "id": "opbeans-ruby"
+ "service.framework.name": "Ruby on Rails"
},
"bidirectional": true
}
},
+ {
+ "data": {
+ "source": "opbeans-ruby",
+ "target": ">postgresql",
+ "id": "opbeans-ruby~>postgresql",
+ "sourceData": {
+ "id": "opbeans-ruby",
+ "service.environment": "production",
+ "service.name": "opbeans-ruby",
+ "agent.name": "ruby",
+ "service.framework.name": "Ruby on Rails"
+ },
+ "targetData": {
+ "span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
+ "span.type": "db",
+ "id": ">postgresql",
+ "label": "postgresql"
+ }
+ }
+ },
{
"data": {
"source": "opbeans-ruby",
"target": "opbeans-dotnet",
"id": "opbeans-ruby~opbeans-dotnet",
"sourceData": {
+ "id": "opbeans-ruby",
"service.environment": "production",
"service.name": "opbeans-ruby",
"agent.name": "ruby",
- "id": "opbeans-ruby"
+ "service.framework.name": "Ruby on Rails"
},
"targetData": {
- "service.environment": "production",
+ "id": "opbeans-dotnet",
"service.name": "opbeans-dotnet",
"agent.name": "dotnet",
- "id": "opbeans-dotnet"
- },
- "isInverseEdge": true
+ "service.framework.name": "ASP.NET Core",
+ "max_score": 43.63972429875661,
+ "severity": "minor"
+ }
}
},
{
@@ -621,16 +659,20 @@
"target": "opbeans-go",
"id": "opbeans-ruby~opbeans-go",
"sourceData": {
+ "id": "opbeans-ruby",
"service.environment": "production",
"service.name": "opbeans-ruby",
"agent.name": "ruby",
- "id": "opbeans-ruby"
+ "service.framework.name": "Ruby on Rails"
},
"targetData": {
+ "id": "opbeans-go",
"service.environment": "production",
"service.name": "opbeans-go",
"agent.name": "go",
- "id": "opbeans-go"
+ "service.framework.name": "gin",
+ "max_score": 92.40731,
+ "severity": "critical"
},
"isInverseEdge": true
}
@@ -641,16 +683,19 @@
"target": "opbeans-java",
"id": "opbeans-ruby~opbeans-java",
"sourceData": {
+ "id": "opbeans-ruby",
"service.environment": "production",
"service.name": "opbeans-ruby",
"agent.name": "ruby",
- "id": "opbeans-ruby"
+ "service.framework.name": "Ruby on Rails"
},
"targetData": {
+ "id": "opbeans-java",
"service.environment": "production",
"service.name": "opbeans-java",
"agent.name": "java",
- "id": "opbeans-java"
+ "max_score": 31.374423806075157,
+ "severity": "minor"
},
"isInverseEdge": true
}
@@ -661,16 +706,18 @@
"target": "opbeans-node",
"id": "opbeans-ruby~opbeans-node",
"sourceData": {
+ "id": "opbeans-ruby",
"service.environment": "production",
"service.name": "opbeans-ruby",
"agent.name": "ruby",
- "id": "opbeans-ruby"
+ "service.framework.name": "Ruby on Rails"
},
"targetData": {
+ "id": "opbeans-node",
"service.environment": "production",
"service.name": "opbeans-node",
"agent.name": "nodejs",
- "id": "opbeans-node"
+ "service.framework.name": "express"
},
"isInverseEdge": true
}
@@ -681,178 +728,123 @@
"target": "opbeans-python",
"id": "opbeans-ruby~opbeans-python",
"sourceData": {
+ "id": "opbeans-ruby",
"service.environment": "production",
"service.name": "opbeans-ruby",
"agent.name": "ruby",
- "id": "opbeans-ruby"
+ "service.framework.name": "Ruby on Rails"
},
"targetData": {
+ "id": "opbeans-python",
"service.environment": "production",
"service.name": "opbeans-python",
"agent.name": "python",
- "id": "opbeans-python"
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
},
"isInverseEdge": true
}
},
{
"data": {
- "service.environment": null,
- "service.name": "client",
- "agent.name": "js-base",
- "id": "client"
- }
- },
- {
- "data": {
- "service.environment": "production",
- "service.name": "opbeans-node",
- "agent.name": "nodejs",
- "id": "opbeans-node"
- }
- },
- {
- "data": {
+ "id": "opbeans-java",
"service.environment": "production",
- "service.name": "opbeans-go",
- "agent.name": "go",
- "id": "opbeans-go"
+ "service.name": "opbeans-java",
+ "agent.name": "java",
+ "max_score": 31.374423806075157,
+ "severity": "minor"
}
},
{
"data": {
+ "id": "opbeans-python",
"service.environment": "production",
- "service.name": "opbeans-java",
- "agent.name": "java",
- "id": "opbeans-java"
+ "service.name": "opbeans-python",
+ "agent.name": "python",
+ "service.framework.name": "django",
+ "max_score": 92.48735,
+ "severity": "critical"
}
},
{
"data": {
- "destination.address": "postgres",
"span.subtype": "postgresql",
+ "span.destination.service.resource": "postgresql",
"span.type": "db",
- "id": ">postgres"
+ "id": ">postgresql",
+ "label": "postgresql"
}
},
{
"data": {
+ "id": "opbeans-ruby",
"service.environment": "production",
"service.name": "opbeans-ruby",
"agent.name": "ruby",
- "id": "opbeans-ruby"
- }
- },
- {
- "data": {
- "service.environment": "production",
- "service.name": "opbeans-dotnet",
- "agent.name": "dotnet",
- "id": "opbeans-dotnet"
+ "service.framework.name": "Ruby on Rails"
}
},
{
"data": {
+ "id": "opbeans-go",
"service.environment": "production",
- "service.name": "opbeans-python",
- "agent.name": "python",
- "id": "opbeans-python"
- }
- },
- {
- "data": {
- "destination.address": "opbeans-node",
- "span.subtype": null,
- "span.type": "resource",
- "id": ">opbeans-node"
- }
- },
- {
- "data": {
- "service.environment": null,
- "service.name": "apm-server",
+ "service.name": "opbeans-go",
"agent.name": "go",
- "id": "apm-server"
- }
- },
- {
- "data": {
- "destination.address": "172.17.0.1",
- "span.subtype": "http",
- "span.type": "external",
- "id": ">172.17.0.1"
+ "service.framework.name": "gin",
+ "max_score": 92.40731,
+ "severity": "critical"
}
},
{
"data": {
+ "id": "apm-server",
"service.name": "apm-server",
- "agent.name": "go",
- "service.environment": null,
- "service.framework.name": null,
- "id": "apm-server"
- }
- },
- {
- "data": {
- "service.name": "opbeans-python",
- "agent.name": "python",
- "service.environment": null,
- "service.framework.name": "django",
- "id": "opbeans-python"
+ "agent.name": "go"
}
},
{
"data": {
- "service.name": "opbeans-ruby",
- "agent.name": "ruby",
- "service.environment": null,
- "service.framework.name": "Ruby on Rails",
- "id": "opbeans-ruby"
+ "span.subtype": "elasticsearch",
+ "span.destination.service.resource": "elasticsearch",
+ "span.type": "db",
+ "id": ">elasticsearch",
+ "label": "elasticsearch"
}
},
{
"data": {
+ "id": "opbeans-node",
+ "service.environment": "production",
"service.name": "opbeans-node",
"agent.name": "nodejs",
- "service.environment": null,
- "service.framework.name": "express",
- "id": "opbeans-node"
+ "service.framework.name": "express"
}
},
{
"data": {
- "service.name": "opbeans-go",
- "agent.name": "go",
- "service.environment": null,
- "service.framework.name": "gin",
- "id": "opbeans-go"
- }
- },
- {
- "data": {
- "service.name": "opbeans-java",
- "agent.name": "java",
- "service.environment": null,
- "service.framework.name": null,
- "id": "opbeans-java"
+ "id": "opbeans-dotnet",
+ "service.name": "opbeans-dotnet",
+ "agent.name": "dotnet",
+ "service.framework.name": "ASP.NET Core",
+ "max_score": 43.63972429875661,
+ "severity": "minor"
}
},
{
"data": {
- "service.name": "opbeans-dotnet",
- "agent.name": "dotnet",
- "service.environment": null,
- "service.framework.name": "ASP.NET Core",
- "id": "opbeans-dotnet"
+ "span.subtype": "redis",
+ "span.destination.service.resource": "redis",
+ "span.type": "db",
+ "id": ">redis",
+ "label": "redis"
}
},
{
"data": {
+ "id": "client",
"service.name": "client",
- "agent.name": "js-base",
- "service.environment": null,
- "service.framework.name": null,
- "id": "client"
+ "agent.name": "rum-js"
}
}
]
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts
index 3bb4319d0722dd..0cdc7c4eb124d5 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts
@@ -13,9 +13,7 @@ import {
import { severity } from '../../../../common/ml_job_constants';
import { defaultIcon, iconForNode } from './icons';
-const getBorderColor = (el: cytoscape.NodeSingular) => {
- const nodeSeverity = el.data('severity');
-
+export const getSeverityColor = (nodeSeverity: string) => {
switch (nodeSeverity) {
case severity.warning:
return theme.euiColorVis0;
@@ -24,11 +22,20 @@ const getBorderColor = (el: cytoscape.NodeSingular) => {
case severity.critical:
return theme.euiColorVis9;
default:
- if (el.hasClass('primary') || el.selected()) {
- return theme.euiColorPrimary;
- } else {
- return theme.euiColorMediumShade;
- }
+ return;
+ }
+};
+
+const getBorderColor = (el: cytoscape.NodeSingular) => {
+ const nodeSeverity = el.data('severity');
+ const severityColor = getSeverityColor(nodeSeverity);
+ if (severityColor) {
+ return severityColor;
+ }
+ if (el.hasClass('primary') || el.selected()) {
+ return theme.euiColorPrimary;
+ } else {
+ return theme.euiColorMediumShade;
}
};
diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx
index 75a247a1aae40b..9065f20ae600e6 100644
--- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx
@@ -10,7 +10,7 @@ import { getRenderedHref } from '../../../../utils/testHelpers';
import { MLJobLink } from './MLJobLink';
describe('MLJobLink', () => {
- it('should produce the correct URL', async () => {
+ it('should produce the correct URL with serviceName', async () => {
const href = await getRenderedHref(
() => (
{
{ search: '?rangeFrom=now/w&rangeTo=now-4h' } as Location
);
+ expect(href).toEqual(
+ `/basepath/app/ml#/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:true,value:'0'),time:(from:now%2Fw,to:now-4h))`
+ );
+ });
+ it('should produce the correct URL with jobId', async () => {
+ const href = await getRenderedHref(
+ () => (
+
+ ),
+ { search: '?rangeFrom=now/w&rangeTo=now-4h' } as Location
+ );
+
expect(href).toEqual(
`/basepath/app/ml#/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:true,value:'0'),time:(from:now%2Fw,to:now-4h))`
);
diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.tsx
index 81c5d17d491c06..b085fab2b7ed66 100644
--- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.tsx
@@ -8,22 +8,33 @@ import React from 'react';
import { getMlJobId } from '../../../../../common/ml_job_constants';
import { MLLink } from './MLLink';
-interface Props {
+interface PropsServiceName {
serviceName: string;
transactionType?: string;
}
+interface PropsJobId {
+ jobId: string;
+}
+
+type Props = (PropsServiceName | PropsJobId) & {
+ external?: boolean;
+};
-export const MLJobLink: React.FC = ({
- serviceName,
- transactionType,
- children
-}) => {
- const jobId = getMlJobId(serviceName, transactionType);
+export const MLJobLink: React.FC = props => {
+ const jobId =
+ 'jobId' in props
+ ? props.jobId
+ : getMlJobId(props.serviceName, props.transactionType);
const query = {
ml: { jobIds: [jobId] }
};
return (
-
+
);
};
diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx
index 3671a0089fd6e2..7b57c193d896d2 100644
--- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx
@@ -22,9 +22,10 @@ interface Props {
query?: MlRisonData;
path?: string;
children?: React.ReactNode;
+ external?: boolean;
}
-export function MLLink({ children, path = '', query = {} }: Props) {
+export function MLLink({ children, path = '', query = {}, external }: Props) {
const { core } = useApmPluginContext();
const location = useLocation();
@@ -41,5 +42,12 @@ export function MLLink({ children, path = '', query = {} }: Props) {
hash: `${path}?_g=${rison.encode(risonQuery as RisonValue)}`
});
- return ;
+ return (
+
+ );
}
diff --git a/x-pack/plugins/apm/server/lib/helpers/range_filter.ts b/x-pack/plugins/apm/server/lib/helpers/range_filter.ts
index 39581687f04f2b..0647144a19c1dc 100644
--- a/x-pack/plugins/apm/server/lib/helpers/range_filter.ts
+++ b/x-pack/plugins/apm/server/lib/helpers/range_filter.ts
@@ -4,9 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export function rangeFilter(start: number, end: number) {
+export function rangeFilter(
+ start: number,
+ end: number,
+ timestampField = '@timestamp'
+) {
return {
- '@timestamp': {
+ [timestampField]: {
gte: start,
lte: end,
format: 'epoch_millis'
diff --git a/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.test.ts b/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.test.ts
index 572d73e368c7ad..4af8a541392042 100644
--- a/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.test.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/dedupe_connections/index.test.ts
@@ -9,7 +9,6 @@ import {
SPAN_DESTINATION_SERVICE_RESOURCE,
SERVICE_NAME,
SERVICE_ENVIRONMENT,
- SERVICE_FRAMEWORK_NAME,
AGENT_NAME,
SPAN_TYPE,
SPAN_SUBTYPE
@@ -19,7 +18,6 @@ import { dedupeConnections } from './';
const nodejsService = {
[SERVICE_NAME]: 'opbeans-node',
[SERVICE_ENVIRONMENT]: 'production',
- [SERVICE_FRAMEWORK_NAME]: null,
[AGENT_NAME]: 'nodejs'
};
@@ -32,7 +30,6 @@ const nodejsExternal = {
const javaService = {
[SERVICE_NAME]: 'opbeans-java',
[SERVICE_ENVIRONMENT]: 'production',
- [SERVICE_FRAMEWORK_NAME]: null,
[AGENT_NAME]: 'java'
};
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
index adb2c9b7cb084c..7d5f0a75d2208c 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
@@ -7,7 +7,6 @@ import { chunk } from 'lodash';
import {
AGENT_NAME,
SERVICE_ENVIRONMENT,
- SERVICE_FRAMEWORK_NAME,
SERVICE_NAME
} from '../../../common/elasticsearch_fieldnames';
import { getServicesProjection } from '../../../common/projections/services';
@@ -19,6 +18,7 @@ import { getServiceMapFromTraceIds } from './get_service_map_from_trace_ids';
import { getTraceSampleIds } from './get_trace_sample_ids';
import { addAnomaliesToServicesData } from './ml_helpers';
import { getMlIndex } from '../../../common/ml_job_constants';
+import { rangeFilter } from '../helpers/range_filter';
export interface IEnvOptions {
setup: Setup & SetupTimeRange;
@@ -107,11 +107,6 @@ async function getServicesData(options: IEnvOptions) {
terms: {
field: AGENT_NAME
}
- },
- service_framework_name: {
- terms: {
- field: SERVICE_FRAMEWORK_NAME
- }
}
}
}
@@ -129,38 +124,32 @@ async function getServicesData(options: IEnvOptions) {
[SERVICE_NAME]: bucket.key as string,
[AGENT_NAME]:
(bucket.agent_name.buckets[0]?.key as string | undefined) || '',
- [SERVICE_ENVIRONMENT]: options.environment || null,
- [SERVICE_FRAMEWORK_NAME]:
- (bucket.service_framework_name.buckets[0]?.key as
- | string
- | undefined) || null
+ [SERVICE_ENVIRONMENT]: options.environment || null
};
}) || []
);
}
function getAnomaliesData(options: IEnvOptions) {
- const { client } = options.setup;
+ const { start, end, client } = options.setup;
+ const rangeQuery = { range: rangeFilter(start, end, 'timestamp') };
const params = {
index: getMlIndex('*'),
body: {
size: 0,
query: {
- exists: {
- field: 'bucket_span'
- }
+ bool: { filter: [{ term: { result_type: 'record' } }, rangeQuery] }
},
aggs: {
jobs: {
- terms: {
- field: 'job_id',
- size: 10
- },
+ terms: { field: 'job_id', size: 10 },
aggs: {
- max_score: {
- max: {
- field: 'anomaly_score'
+ top_score_hits: {
+ top_hits: {
+ sort: [{ record_score: { order: 'desc' as const } }],
+ _source: ['job_id', 'record_score', 'typical', 'actual'],
+ size: 1
}
}
}
@@ -178,7 +167,13 @@ export type ServicesResponse = PromiseReturnType;
export type ServiceMapAPIResponse = PromiseReturnType;
export async function getServiceMap(options: IEnvOptions) {
- const [connectionData, servicesData, anomaliesData] = await Promise.all([
+ const [connectionData, servicesData, anomaliesData]: [
+ // explicit types to avoid TS "excessively deep" error
+ ConnectionsResponse,
+ ServicesResponse,
+ AnomaliesResponse
+ // @ts-ignore
+ ] = await Promise.all([
getConnectionData(options),
getServicesData(options),
getAnomaliesData(options)
diff --git a/x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts b/x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts
index c6680ecd6375bf..c80ba8dba01eaa 100644
--- a/x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts
@@ -13,14 +13,12 @@ describe('addAnomaliesToServicesData', () => {
{
'service.name': 'opbeans-ruby',
'agent.name': 'ruby',
- 'service.environment': null,
- 'service.framework.name': 'Ruby on Rails'
+ 'service.environment': null
},
{
'service.name': 'opbeans-java',
'agent.name': 'java',
- 'service.environment': null,
- 'service.framework.name': null
+ 'service.environment': null
}
];
@@ -30,11 +28,37 @@ describe('addAnomaliesToServicesData', () => {
buckets: [
{
key: 'opbeans-ruby-request-high_mean_response_time',
- max_score: { value: 50 }
+ top_score_hits: {
+ hits: {
+ hits: [
+ {
+ _source: {
+ record_score: 50,
+ actual: [2000],
+ typical: [1000],
+ job_id: 'opbeans-ruby-request-high_mean_response_time'
+ }
+ }
+ ]
+ }
+ }
},
{
key: 'opbeans-java-request-high_mean_response_time',
- max_score: { value: 100 }
+ top_score_hits: {
+ hits: {
+ hits: [
+ {
+ _source: {
+ record_score: 100,
+ actual: [9000],
+ typical: [3000],
+ job_id: 'opbeans-java-request-high_mean_response_time'
+ }
+ }
+ ]
+ }
+ }
}
]
}
@@ -46,17 +70,21 @@ describe('addAnomaliesToServicesData', () => {
'service.name': 'opbeans-ruby',
'agent.name': 'ruby',
'service.environment': null,
- 'service.framework.name': 'Ruby on Rails',
max_score: 50,
- severity: 'major'
+ severity: 'major',
+ actual_value: 2000,
+ typical_value: 1000,
+ job_id: 'opbeans-ruby-request-high_mean_response_time'
},
{
'service.name': 'opbeans-java',
'agent.name': 'java',
'service.environment': null,
- 'service.framework.name': null,
max_score: 100,
- severity: 'critical'
+ severity: 'critical',
+ actual_value: 9000,
+ typical_value: 3000,
+ job_id: 'opbeans-java-request-high_mean_response_time'
}
];
diff --git a/x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts b/x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts
index 26a964bfb4dd21..9789911660bd03 100644
--- a/x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts
@@ -18,29 +18,54 @@ export function addAnomaliesToServicesData(
const anomaliesMap = (
anomaliesResponse.aggregations?.jobs.buckets ?? []
).reduce<{
- [key: string]: { max_score?: number };
+ [key: string]: {
+ max_score?: number;
+ actual_value?: number;
+ typical_value?: number;
+ job_id?: string;
+ };
}>((previousValue, currentValue) => {
const key = getMlJobServiceName(currentValue.key.toString());
+ const hitSource = currentValue.top_score_hits.hits.hits[0]._source as {
+ record_score: number;
+ actual: [number];
+ typical: [number];
+ job_id: string;
+ };
+ const maxScore = hitSource.record_score;
+ const actualValue = hitSource.actual[0];
+ const typicalValue = hitSource.typical[0];
+ const jobId = hitSource.job_id;
+
+ if ((previousValue[key]?.max_score ?? 0) > maxScore) {
+ return previousValue;
+ }
return {
...previousValue,
[key]: {
- max_score: Math.max(
- previousValue[key]?.max_score ?? 0,
- currentValue.max_score.value ?? 0
- )
+ max_score: maxScore,
+ actual_value: actualValue,
+ typical_value: typicalValue,
+ job_id: jobId
}
};
}, {});
const servicesDataWithAnomalies = servicesData.map(service => {
- const score = anomaliesMap[service[SERVICE_NAME]]?.max_score;
-
- return {
- ...service,
- max_score: score,
- severity: getSeverity(score)
- };
+ const serviceAnomalies = anomaliesMap[service[SERVICE_NAME]];
+ if (serviceAnomalies) {
+ const maxScore = serviceAnomalies.max_score;
+ return {
+ ...service,
+ max_score: maxScore,
+ severity: getSeverity(maxScore),
+ actual_value: serviceAnomalies.actual_value,
+ typical_value: serviceAnomalies.typical_value,
+ job_id: serviceAnomalies.job_id
+ };
+ }
+ return service;
});
return servicesDataWithAnomalies;
diff --git a/x-pack/plugins/ml/public/index.ts b/x-pack/plugins/ml/public/index.ts
index 8070f94a1264dd..c23d042822816d 100755
--- a/x-pack/plugins/ml/public/index.ts
+++ b/x-pack/plugins/ml/public/index.ts
@@ -13,6 +13,7 @@ import {
MlSetupDependencies,
MlStartDependencies,
} from './plugin';
+import { getMetricChangeDescription } from './application/formatters/metric_change_description';
export const plugin: PluginInitializer<
MlPluginSetup,
@@ -21,4 +22,4 @@ export const plugin: PluginInitializer<
MlStartDependencies
> = () => new MlPlugin();
-export { MlPluginSetup, MlPluginStart };
+export { MlPluginSetup, MlPluginStart, getMetricChangeDescription };