Skip to content

Commit

Permalink
Merge branch 'master' into 69294-followup-add-schema
Browse files Browse the repository at this point in the history
  • Loading branch information
elasticmachine committed Jul 9, 2020
2 parents 9c65db3 + 59d3096 commit 07f6964
Show file tree
Hide file tree
Showing 92 changed files with 2,535 additions and 2,169 deletions.
23 changes: 15 additions & 8 deletions test/functional/apps/discover/_discover.js
Expand Up @@ -96,25 +96,32 @@ export default function ({ getService, getPageObjects }) {
it('should modify the time range when a bar is clicked', async function () {
await PageObjects.timePicker.setDefaultAbsoluteRange();
await PageObjects.discover.clickHistogramBar();
await PageObjects.discover.waitUntilSearchingHasFinished();
const time = await PageObjects.timePicker.getTimeConfig();
expect(time.start).to.be('Sep 21, 2015 @ 09:00:00.000');
expect(time.end).to.be('Sep 21, 2015 @ 12:00:00.000');
const rowData = await PageObjects.discover.getDocTableField(1);
expect(rowData).to.have.string('Sep 21, 2015 @ 11:59:22.316');
await retry.waitFor('doc table to contain the right search result', async () => {
const rowData = await PageObjects.discover.getDocTableField(1);
log.debug(`The first timestamp value in doc table: ${rowData}`);
return rowData.includes('Sep 21, 2015 @ 11:59:22.316');
});
});

it('should modify the time range when the histogram is brushed', async function () {
await PageObjects.timePicker.setDefaultAbsoluteRange();
await PageObjects.discover.brushHistogram();
await PageObjects.discover.waitUntilSearchingHasFinished();

const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
expect(Math.round(newDurationHours)).to.be(24);
const rowData = await PageObjects.discover.getDocTableField(1);
log.debug(`The first timestamp value in doc table: ${rowData}`);
expect(Date.parse(rowData)).to.be.within(
Date.parse('Sep 20, 2015 @ 17:30:00.000'),
Date.parse('Sep 20, 2015 @ 23:30:00.000')
);

await retry.waitFor('doc table to contain the right search result', async () => {
const rowData = await PageObjects.discover.getDocTableField(1);
log.debug(`The first timestamp value in doc table: ${rowData}`);
const dateParsed = Date.parse(rowData);
//compare against the parsed date of Sep 20, 2015 @ 17:30:00.000 and Sep 20, 2015 @ 23:30:00.000
return dateParsed >= 1442770200000 && dateParsed <= 1442791800000;
});
});

it('should show correct initial chart interval of Auto', async function () {
Expand Down
3 changes: 2 additions & 1 deletion test/functional/apps/discover/_doc_navigation.js
Expand Up @@ -28,7 +28,8 @@ export default function ({ getService, getPageObjects }) {
const esArchiver = getService('esArchiver');
const retry = getService('retry');

describe('doc link in discover', function contextSize() {
// Flaky: https://github.com/elastic/kibana/issues/71216
describe.skip('doc link in discover', function contextSize() {
beforeEach(async function () {
log.debug('load kibana index with default index pattern');
await esArchiver.loadIfNeeded('discover');
Expand Down
11 changes: 6 additions & 5 deletions test/functional/apps/discover/_saved_queries.js
Expand Up @@ -20,6 +20,7 @@
import expect from '@kbn/expect';

export default function ({ getService, getPageObjects }) {
const retry = getService('retry');
const log = getService('log');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
Expand Down Expand Up @@ -93,7 +94,10 @@ export default function ({ getService, getPageObjects }) {
expect(await filterBar.hasFilter('extension.raw', 'jpg')).to.be(true);
expect(timePickerValues.start).to.not.eql(PageObjects.timePicker.defaultStartTime);
expect(timePickerValues.end).to.not.eql(PageObjects.timePicker.defaultEndTime);
expect(await PageObjects.discover.getHitCount()).to.be('2,792');
await retry.waitFor(
'the right hit count',
async () => (await PageObjects.discover.getHitCount()) === '2,792'
);
expect(await savedQueryManagementComponent.getCurrentlyLoadedQueryID()).to.be('OkResponse');
});

Expand Down Expand Up @@ -149,7 +153,6 @@ export default function ({ getService, getPageObjects }) {
expect(await queryBar.getQueryString()).to.eql('');
});

// https://github.com/elastic/kibana/issues/63505
it('allows clearing if non default language was remembered in localstorage', async () => {
await queryBar.switchQueryLanguage('lucene');
await PageObjects.common.navigateToApp('discover'); // makes sure discovered is reloaded without any state in url
Expand All @@ -160,9 +163,7 @@ export default function ({ getService, getPageObjects }) {
await queryBar.expectQueryLanguageOrFail('lucene');
});

// fails: bug in discover https://github.com/elastic/kibana/issues/63561
// unskip this test when bug is fixed
it.skip('changing language removes saved query', async () => {
it('changing language removes saved query', async () => {
await savedQueryManagementComponent.loadSavedQuery('OkResponse');
await queryBar.switchQueryLanguage('lucene');
expect(await queryBar.getQueryString()).to.eql('');
Expand Down
5 changes: 2 additions & 3 deletions test/functional/apps/visualize/_data_table_nontimeindex.js
Expand Up @@ -112,8 +112,7 @@ export default function ({ getService, getPageObjects }) {
expect(data.trim().split('\n')).to.be.eql(['14,004 1,412.6']);
});

// bug https://github.com/elastic/kibana/issues/68977
describe.skip('data table with date histogram', async () => {
describe('data table with date histogram', async () => {
before(async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickDataTable();
Expand All @@ -123,7 +122,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.visEditor.clickBucket('Split rows');
await PageObjects.visEditor.selectAggregation('Date Histogram');
await PageObjects.visEditor.selectField('@timestamp');
await PageObjects.visEditor.setInterval('Daily');
await PageObjects.visEditor.setInterval('Day');
await PageObjects.visEditor.clickGo();
});

Expand Down
10 changes: 6 additions & 4 deletions test/functional/page_objects/management/saved_objects_page.ts
Expand Up @@ -87,13 +87,15 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv

async waitTableIsLoaded() {
return retry.try(async () => {
const exists = await find.existsByDisplayedByCssSelector(
'*[data-test-subj="savedObjectsTable"] .euiBasicTable-loading'
const isLoaded = await find.existsByDisplayedByCssSelector(
'*[data-test-subj="savedObjectsTable"] :not(.euiBasicTable-loading)'
);
if (exists) {

if (isLoaded) {
return true;
} else {
throw new Error('Waiting');
}
return true;
});
}

Expand Down
12 changes: 12 additions & 0 deletions x-pack/plugins/apm/common/anomaly_detection.ts
@@ -0,0 +1,12 @@
/*
* 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.
*/

export interface ServiceAnomalyStats {
transactionType?: string;
anomalyScore?: number;
actualValue?: number;
jobId?: string;
}
8 changes: 6 additions & 2 deletions x-pack/plugins/apm/common/service_map.ts
Expand Up @@ -15,11 +15,13 @@ import {
SPAN_SUBTYPE,
SPAN_TYPE,
} from './elasticsearch_fieldnames';
import { ServiceAnomalyStats } from './anomaly_detection';

export interface ServiceConnectionNode extends cytoscape.NodeDataDefinition {
[SERVICE_NAME]: string;
[SERVICE_ENVIRONMENT]: string | null;
[AGENT_NAME]: string;
serviceAnomalyStats?: ServiceAnomalyStats;
}
export interface ExternalConnectionNode extends cytoscape.NodeDataDefinition {
[SPAN_DESTINATION_SERVICE_RESOURCE]: string;
Expand All @@ -37,8 +39,10 @@ export interface Connection {
export interface ServiceNodeMetrics {
avgMemoryUsage: number | null;
avgCpuUsage: number | null;
avgTransactionDuration: number | null;
avgRequestsPerMinute: number | null;
transactionStats: {
avgTransactionDuration: number | null;
avgRequestsPerMinute: number | null;
};
avgErrorsPerMinute: number | null;
}

Expand Down
@@ -0,0 +1,158 @@
/*
* 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 {
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiIconTip,
EuiHealth,
} from '@elastic/eui';
import { useTheme } from '../../../../hooks/useTheme';
import { fontSize, px } from '../../../../style/variables';
import { asInteger, asDuration } from '../../../../utils/formatters';
import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink';
import { getSeverityColor, popoverWidth } from '../cytoscapeOptions';
import { getSeverity } from '../../../../../common/ml_job_constants';
import { TRANSACTION_REQUEST } from '../../../../../common/transaction_types';
import { ServiceAnomalyStats } from '../../../../../common/anomaly_detection';

const HealthStatusTitle = styled(EuiTitle)`
display: inline;
text-transform: uppercase;
`;

const VerticallyCentered = styled.div`
display: flex;
align-items: center;
`;

const SubduedText = styled.span`
color: ${({ theme }) => theme.eui.euiTextSubduedColor};
`;

const EnableText = styled.section`
color: ${({ theme }) => theme.eui.euiTextSubduedColor};
line-height: 1.4;
font-size: ${fontSize};
width: ${px(popoverWidth)};
`;

export const ContentLine = styled.section`
line-height: 2;
`;

interface Props {
serviceName: string;
serviceAnomalyStats: ServiceAnomalyStats | undefined;
}
export function AnomalyDetection({ serviceName, serviceAnomalyStats }: Props) {
const theme = useTheme();

const anomalyScore = serviceAnomalyStats?.anomalyScore;
const anomalySeverity = getSeverity(anomalyScore);
const actualValue = serviceAnomalyStats?.actualValue;
const mlJobId = serviceAnomalyStats?.jobId;
const transactionType =
serviceAnomalyStats?.transactionType ?? TRANSACTION_REQUEST;
const hasAnomalyDetectionScore = anomalyScore !== undefined;

return (
<>
<section>
<HealthStatusTitle size="xxs">
<h3>{ANOMALY_DETECTION_TITLE}</h3>
</HealthStatusTitle>
&nbsp;
<EuiIconTip type="iInCircle" content={ANOMALY_DETECTION_TOOLTIP} />
{!mlJobId && <EnableText>{ANOMALY_DETECTION_DISABLED_TEXT}</EnableText>}
</section>
{hasAnomalyDetectionScore && (
<ContentLine>
<EuiFlexGroup>
<EuiFlexItem>
<VerticallyCentered>
<EuiHealth color={getSeverityColor(theme, anomalySeverity)} />
<SubduedText>{ANOMALY_DETECTION_SCORE_METRIC}</SubduedText>
</VerticallyCentered>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div>
{getDisplayedAnomalyScore(anomalyScore as number)}
{actualValue && (
<SubduedText>&nbsp;({asDuration(actualValue)})</SubduedText>
)}
</div>
</EuiFlexItem>
</EuiFlexGroup>
</ContentLine>
)}
{mlJobId && !hasAnomalyDetectionScore && (
<EnableText>{ANOMALY_DETECTION_NO_DATA_TEXT}</EnableText>
)}
{mlJobId && (
<ContentLine>
<MLJobLink
external
jobId={mlJobId}
serviceName={serviceName}
transactionType={transactionType}
>
{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 in APM settings.',
}
);

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.`,
}
);
Expand Up @@ -15,7 +15,7 @@ import React, { MouseEvent } from 'react';
import { Buttons } from './Buttons';
import { Info } from './Info';
import { ServiceMetricFetcher } from './ServiceMetricFetcher';
import { popoverMinWidth } from '../cytoscapeOptions';
import { popoverWidth } from '../cytoscapeOptions';

interface ContentsProps {
isService: boolean;
Expand Down Expand Up @@ -60,24 +60,20 @@ export function Contents({
<FlexColumnGroup
direction="column"
gutterSize="s"
style={{ minWidth: popoverMinWidth }}
style={{ width: popoverWidth }}
>
<FlexColumnItem>
<EuiTitle size="xxs">
<h3>{label}</h3>
</EuiTitle>
<EuiHorizontalRule margin="xs" />
</FlexColumnItem>
{/* //TODO [APM ML] add service health stats here:
isService && (
<FlexColumnItem>
<ServiceHealth serviceNodeData={selectedNodeData} />
<EuiHorizontalRule margin="xs" />
</FlexColumnItem>
)*/}
<FlexColumnItem>
{isService ? (
<ServiceMetricFetcher serviceName={selectedNodeServiceName} />
<ServiceMetricFetcher
serviceName={selectedNodeServiceName}
serviceAnomalyStats={selectedNodeData.serviceAnomalyStats}
/>
) : (
<Info {...selectedNodeData} />
)}
Expand Down

0 comments on commit 07f6964

Please sign in to comment.