[ML] Fix custom index name settings, functional tests for APM Latency Correlation. (#105200)
Adds first functional tests for APM Latency Correlations code. - Fix: The log log chart's Y axis would only start at 1 by default which hides results for small datasets like the ones used in these tests. Starting the axis at 0 isn't supported to log based ones so we're setting it to just a small value above 0. - Fix: Instead of the hard coded apm-* index we passed on from the client, we now correctly consider APM's custom settings.
This commit is contained in:
parent
15a613f488
commit
daa81c9306
|
@ -19,7 +19,6 @@ export interface ResponseHit {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchServiceParams {
|
export interface SearchServiceParams {
|
||||||
index: string;
|
|
||||||
environment?: string;
|
environment?: string;
|
||||||
kuery?: string;
|
kuery?: string;
|
||||||
serviceName?: string;
|
serviceName?: string;
|
||||||
|
@ -31,6 +30,10 @@ export interface SearchServiceParams {
|
||||||
percentileThresholdValue?: number;
|
percentileThresholdValue?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SearchServiceFetchParams extends SearchServiceParams {
|
||||||
|
index: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SearchServiceValue {
|
export interface SearchServiceValue {
|
||||||
histogram: HistogramItem[];
|
histogram: HistogramItem[];
|
||||||
value: string;
|
value: string;
|
||||||
|
|
|
@ -70,6 +70,11 @@ const chartTheme: PartialTheme = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Log based axis cannot start a 0. Use a small positive number instead.
|
||||||
|
const yAxisDomain = {
|
||||||
|
min: 0.00001,
|
||||||
|
};
|
||||||
|
|
||||||
interface CorrelationsChartProps {
|
interface CorrelationsChartProps {
|
||||||
field?: string;
|
field?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
|
@ -140,7 +145,10 @@ export function CorrelationsChart({
|
||||||
const histogram = replaceHistogramDotsWithBars(originalHistogram);
|
const histogram = replaceHistogramDotsWithBars(originalHistogram);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
<div
|
||||||
|
data-test-subj="apmCorrelationsChart"
|
||||||
|
style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}
|
||||||
|
>
|
||||||
<Chart
|
<Chart
|
||||||
size={{
|
size={{
|
||||||
height: '250px',
|
height: '250px',
|
||||||
|
@ -168,6 +176,7 @@ export function CorrelationsChart({
|
||||||
/>
|
/>
|
||||||
<Axis
|
<Axis
|
||||||
id="y-axis"
|
id="y-axis"
|
||||||
|
domain={yAxisDomain}
|
||||||
title={i18n.translate(
|
title={i18n.translate(
|
||||||
'xpack.apm.correlations.latency.chart.numberOfTransactionsLabel',
|
'xpack.apm.correlations.latency.chart.numberOfTransactionsLabel',
|
||||||
{ defaultMessage: '# transactions' }
|
{ defaultMessage: '# transactions' }
|
||||||
|
|
|
@ -134,6 +134,7 @@ export function Correlations() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EuiButton
|
<EuiButton
|
||||||
|
data-test-subj="apmViewCorrelationsButton"
|
||||||
fill
|
fill
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsFlyoutVisible(true);
|
setIsFlyoutVisible(true);
|
||||||
|
@ -147,13 +148,17 @@ export function Correlations() {
|
||||||
{isFlyoutVisible && (
|
{isFlyoutVisible && (
|
||||||
<EuiPortal>
|
<EuiPortal>
|
||||||
<EuiFlyout
|
<EuiFlyout
|
||||||
|
data-test-subj="apmCorrelationsFlyout"
|
||||||
size="l"
|
size="l"
|
||||||
ownFocus
|
ownFocus
|
||||||
onClose={() => setIsFlyoutVisible(false)}
|
onClose={() => setIsFlyoutVisible(false)}
|
||||||
>
|
>
|
||||||
<EuiFlyoutHeader hasBorder aria-labelledby="correlations-flyout">
|
<EuiFlyoutHeader hasBorder aria-labelledby="correlations-flyout">
|
||||||
<EuiTitle>
|
<EuiTitle>
|
||||||
<h2 id="correlations-flyout">
|
<h2
|
||||||
|
data-test-subj="apmCorrelationsFlyoutHeader"
|
||||||
|
id="correlations-flyout"
|
||||||
|
>
|
||||||
{CORRELATIONS_TITLE}
|
{CORRELATIONS_TITLE}
|
||||||
|
|
||||||
<EuiBetaBadge
|
<EuiBetaBadge
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
|
EuiCallOut,
|
||||||
|
EuiCode,
|
||||||
|
EuiAccordion,
|
||||||
|
EuiPanel,
|
||||||
EuiIcon,
|
EuiIcon,
|
||||||
EuiBasicTableColumn,
|
EuiBasicTableColumn,
|
||||||
EuiButton,
|
EuiButton,
|
||||||
|
@ -34,7 +38,10 @@ import {
|
||||||
} from './correlations_table';
|
} from './correlations_table';
|
||||||
import { useCorrelations } from './use_correlations';
|
import { useCorrelations } from './use_correlations';
|
||||||
import { push } from '../../shared/Links/url_helpers';
|
import { push } from '../../shared/Links/url_helpers';
|
||||||
import { useUiTracker } from '../../../../../observability/public';
|
import {
|
||||||
|
enableInspectEsQueries,
|
||||||
|
useUiTracker,
|
||||||
|
} from '../../../../../observability/public';
|
||||||
import { asPreciseDecimal } from '../../../../common/utils/formatters';
|
import { asPreciseDecimal } from '../../../../common/utils/formatters';
|
||||||
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
||||||
import { LatencyCorrelationsHelpPopover } from './ml_latency_correlations_help_popover';
|
import { LatencyCorrelationsHelpPopover } from './ml_latency_correlations_help_popover';
|
||||||
|
@ -58,7 +65,7 @@ interface MlCorrelationsTerms {
|
||||||
|
|
||||||
export function MlLatencyCorrelations({ onClose }: Props) {
|
export function MlLatencyCorrelations({ onClose }: Props) {
|
||||||
const {
|
const {
|
||||||
core: { notifications },
|
core: { notifications, uiSettings },
|
||||||
} = useApmPluginContext();
|
} = useApmPluginContext();
|
||||||
|
|
||||||
const { serviceName, transactionType } = useApmServiceContext();
|
const { serviceName, transactionType } = useApmServiceContext();
|
||||||
|
@ -66,7 +73,11 @@ export function MlLatencyCorrelations({ onClose }: Props) {
|
||||||
|
|
||||||
const { environment, kuery, transactionName, start, end } = urlParams;
|
const { environment, kuery, transactionName, start, end } = urlParams;
|
||||||
|
|
||||||
|
const displayLog = uiSettings.get<boolean>(enableInspectEsQueries);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
ccsWarning,
|
||||||
|
log,
|
||||||
error,
|
error,
|
||||||
histograms,
|
histograms,
|
||||||
percentileThresholdValue,
|
percentileThresholdValue,
|
||||||
|
@ -76,7 +87,6 @@ export function MlLatencyCorrelations({ onClose }: Props) {
|
||||||
cancelFetch,
|
cancelFetch,
|
||||||
overallHistogram: originalOverallHistogram,
|
overallHistogram: originalOverallHistogram,
|
||||||
} = useCorrelations({
|
} = useCorrelations({
|
||||||
index: 'apm-*',
|
|
||||||
...{
|
...{
|
||||||
...{
|
...{
|
||||||
environment,
|
environment,
|
||||||
|
@ -286,9 +296,10 @@ export function MlLatencyCorrelations({ onClose }: Props) {
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
<EuiFlexGroup direction="column" gutterSize="none">
|
<EuiFlexGroup direction="column" gutterSize="none">
|
||||||
<EuiFlexItem>
|
<EuiFlexItem data-test-subj="apmCorrelationsLatencyCorrelationsProgressTitle">
|
||||||
<EuiText size="xs" color="subdued">
|
<EuiText size="xs" color="subdued">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
data-test-subj="apmCorrelationsLatencyCorrelationsProgressTitle"
|
||||||
id="xpack.apm.correlations.latencyCorrelations.progressTitle"
|
id="xpack.apm.correlations.latencyCorrelations.progressTitle"
|
||||||
defaultMessage="Progress: {progress}%"
|
defaultMessage="Progress: {progress}%"
|
||||||
values={{ progress: Math.round(progress * 100) }}
|
values={{ progress: Math.round(progress * 100) }}
|
||||||
|
@ -313,11 +324,35 @@ export function MlLatencyCorrelations({ onClose }: Props) {
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
|
||||||
|
{ccsWarning && (
|
||||||
|
<>
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
<EuiCallOut
|
||||||
|
title={i18n.translate(
|
||||||
|
'xpack.apm.correlations.latencyCorrelations.ccsWarningCalloutTitle',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Cross-cluster search compatibility',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
color="warning"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
{i18n.translate(
|
||||||
|
'xpack.apm.correlations.latencyCorrelations.ccsWarningCalloutBody',
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
'Data for the correlation analysis could not be fully retrieved. This feature is supported only for 7.14 and later versions.',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</EuiCallOut>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<EuiSpacer size="m" />
|
<EuiSpacer size="m" />
|
||||||
{overallHistogram !== undefined ? (
|
{overallHistogram !== undefined ? (
|
||||||
<>
|
<>
|
||||||
<EuiTitle size="xxs">
|
<EuiTitle size="xxs">
|
||||||
<h4>
|
<h4 data-test-subj="apmCorrelationsLatencyCorrelationsChartTitle">
|
||||||
{i18n.translate(
|
{i18n.translate(
|
||||||
'xpack.apm.correlations.latencyCorrelations.chartTitle',
|
'xpack.apm.correlations.latencyCorrelations.chartTitle',
|
||||||
{
|
{
|
||||||
|
@ -341,32 +376,58 @@ export function MlLatencyCorrelations({ onClose }: Props) {
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{histograms.length > 0 && selectedHistogram !== undefined && (
|
<div data-test-subj="apmCorrelationsTable">
|
||||||
<CorrelationsTable
|
{histograms.length > 0 && selectedHistogram !== undefined && (
|
||||||
// @ts-ignore correlations don't have the same column format other tables have
|
<CorrelationsTable
|
||||||
columns={mlCorrelationColumns}
|
// @ts-ignore correlations don't have the same column format other tables have
|
||||||
// @ts-expect-error correlations don't have the same significant term other tables have
|
columns={mlCorrelationColumns}
|
||||||
significantTerms={histogramTerms}
|
// @ts-expect-error correlations don't have the same significant term other tables have
|
||||||
status={FETCH_STATUS.SUCCESS}
|
significantTerms={histogramTerms}
|
||||||
setSelectedSignificantTerm={setSelectedSignificantTerm}
|
status={FETCH_STATUS.SUCCESS}
|
||||||
selectedTerm={{
|
setSelectedSignificantTerm={setSelectedSignificantTerm}
|
||||||
fieldName: selectedHistogram.field,
|
selectedTerm={{
|
||||||
fieldValue: selectedHistogram.value,
|
fieldName: selectedHistogram.field,
|
||||||
}}
|
fieldValue: selectedHistogram.value,
|
||||||
onFilter={onClose}
|
}}
|
||||||
/>
|
onFilter={onClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{histograms.length < 1 && progress > 0.99 ? (
|
||||||
|
<>
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
<EuiText textAlign="center">
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.apm.correlations.latencyCorrelations.noCorrelationsText"
|
||||||
|
defaultMessage="No significant correlations found"
|
||||||
|
/>
|
||||||
|
</EuiText>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{log.length > 0 && displayLog && (
|
||||||
|
<EuiAccordion
|
||||||
|
id="accordion1"
|
||||||
|
buttonContent={i18n.translate(
|
||||||
|
'xpack.apm.correlations.latencyCorrelations.logButtonContent',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Log',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<EuiPanel color="subdued">
|
||||||
|
{log.map((d, i) => {
|
||||||
|
const splitItem = d.split(': ');
|
||||||
|
return (
|
||||||
|
<p key={i}>
|
||||||
|
<small>
|
||||||
|
<EuiCode>{splitItem[0]}</EuiCode> {splitItem[1]}
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</EuiPanel>
|
||||||
|
</EuiAccordion>
|
||||||
)}
|
)}
|
||||||
{histograms.length < 1 && progress > 0.99 ? (
|
|
||||||
<>
|
|
||||||
<EuiSpacer size="m" />
|
|
||||||
<EuiText textAlign="center">
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.apm.correlations.latencyCorrelations.noCorrelationsText"
|
|
||||||
defaultMessage="No significant correlations found"
|
|
||||||
/>
|
|
||||||
</EuiText>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'
|
||||||
import { ApmPluginStartDeps } from '../../../plugin';
|
import { ApmPluginStartDeps } from '../../../plugin';
|
||||||
|
|
||||||
interface CorrelationsOptions {
|
interface CorrelationsOptions {
|
||||||
index: string;
|
|
||||||
environment?: string;
|
environment?: string;
|
||||||
kuery?: string;
|
kuery?: string;
|
||||||
serviceName?: string;
|
serviceName?: string;
|
||||||
|
@ -37,6 +36,7 @@ interface RawResponse {
|
||||||
values: SearchServiceValue[];
|
values: SearchServiceValue[];
|
||||||
overallHistogram: HistogramItem[];
|
overallHistogram: HistogramItem[];
|
||||||
log: string[];
|
log: string[];
|
||||||
|
ccsWarning: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCorrelations = (params: CorrelationsOptions) => {
|
export const useCorrelations = (params: CorrelationsOptions) => {
|
||||||
|
@ -106,6 +106,8 @@ export const useCorrelations = (params: CorrelationsOptions) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
ccsWarning: rawResponse?.ccsWarning ?? false,
|
||||||
|
log: rawResponse?.log ?? [],
|
||||||
error,
|
error,
|
||||||
histograms: rawResponse?.values ?? [],
|
histograms: rawResponse?.values ?? [],
|
||||||
percentileThresholdValue:
|
percentileThresholdValue:
|
||||||
|
|
|
@ -117,6 +117,7 @@ export function getServiceColumns({
|
||||||
)}
|
)}
|
||||||
<EuiFlexItem className="apmServiceList__serviceNameContainer">
|
<EuiFlexItem className="apmServiceList__serviceNameContainer">
|
||||||
<AppLink
|
<AppLink
|
||||||
|
data-test-subj="apmServiceListAppLink"
|
||||||
serviceName={serviceName}
|
serviceName={serviceName}
|
||||||
transactionType={transactionType}
|
transactionType={transactionType}
|
||||||
className="eui-textTruncate"
|
className="eui-textTruncate"
|
||||||
|
|
|
@ -93,7 +93,9 @@ function TemplateWithContext({
|
||||||
<EuiFlexGroup alignItems="center">
|
<EuiFlexGroup alignItems="center">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiTitle size="l">
|
<EuiTitle size="l">
|
||||||
<>{serviceName}</>
|
<h1 data-test-subj="apmMainTemplateHeaderServiceName">
|
||||||
|
{serviceName}
|
||||||
|
</h1>
|
||||||
</EuiTitle>
|
</EuiTitle>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import { shuffle, range } from 'lodash';
|
import { shuffle, range } from 'lodash';
|
||||||
import type { ElasticsearchClient } from 'src/core/server';
|
import type { ElasticsearchClient } from 'src/core/server';
|
||||||
|
import type { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices';
|
||||||
import { fetchTransactionDurationFieldCandidates } from './query_field_candidates';
|
import { fetchTransactionDurationFieldCandidates } from './query_field_candidates';
|
||||||
import { fetchTransactionDurationFieldValuePairs } from './query_field_value_pairs';
|
import { fetchTransactionDurationFieldValuePairs } from './query_field_value_pairs';
|
||||||
import { fetchTransactionDurationPercentiles } from './query_percentiles';
|
import { fetchTransactionDurationPercentiles } from './query_percentiles';
|
||||||
|
@ -16,6 +17,7 @@ import { fetchTransactionDurationRanges, HistogramItem } from './query_ranges';
|
||||||
import type {
|
import type {
|
||||||
AsyncSearchProviderProgress,
|
AsyncSearchProviderProgress,
|
||||||
SearchServiceParams,
|
SearchServiceParams,
|
||||||
|
SearchServiceFetchParams,
|
||||||
SearchServiceValue,
|
SearchServiceValue,
|
||||||
} from '../../../../common/search_strategies/correlations/types';
|
} from '../../../../common/search_strategies/correlations/types';
|
||||||
import { computeExpectationsAndRanges } from './utils/aggregation_utils';
|
import { computeExpectationsAndRanges } from './utils/aggregation_utils';
|
||||||
|
@ -28,11 +30,14 @@ const currentTimeAsString = () => new Date().toISOString();
|
||||||
|
|
||||||
export const asyncSearchServiceProvider = (
|
export const asyncSearchServiceProvider = (
|
||||||
esClient: ElasticsearchClient,
|
esClient: ElasticsearchClient,
|
||||||
params: SearchServiceParams
|
getApmIndices: () => Promise<ApmIndicesConfig>,
|
||||||
|
searchServiceParams: SearchServiceParams,
|
||||||
|
includeFrozen: boolean
|
||||||
) => {
|
) => {
|
||||||
let isCancelled = false;
|
let isCancelled = false;
|
||||||
let isRunning = true;
|
let isRunning = true;
|
||||||
let error: Error;
|
let error: Error;
|
||||||
|
let ccsWarning = false;
|
||||||
const log: string[] = [];
|
const log: string[] = [];
|
||||||
const logMessage = (message: string) =>
|
const logMessage = (message: string) =>
|
||||||
log.push(`${currentTimeAsString()}: ${message}`);
|
log.push(`${currentTimeAsString()}: ${message}`);
|
||||||
|
@ -63,7 +68,15 @@ export const asyncSearchServiceProvider = (
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchCorrelations = async () => {
|
const fetchCorrelations = async () => {
|
||||||
|
let params: SearchServiceFetchParams | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const indices = await getApmIndices();
|
||||||
|
params = {
|
||||||
|
...searchServiceParams,
|
||||||
|
index: indices['apm_oss.transactionIndices'],
|
||||||
|
};
|
||||||
|
|
||||||
// 95th percentile to be displayed as a marker in the log log chart
|
// 95th percentile to be displayed as a marker in the log log chart
|
||||||
const {
|
const {
|
||||||
totalDocs,
|
totalDocs,
|
||||||
|
@ -172,7 +185,7 @@ export const asyncSearchServiceProvider = (
|
||||||
|
|
||||||
async function* fetchTransactionDurationHistograms() {
|
async function* fetchTransactionDurationHistograms() {
|
||||||
for (const item of shuffle(fieldValuePairs)) {
|
for (const item of shuffle(fieldValuePairs)) {
|
||||||
if (item === undefined || isCancelled) {
|
if (params === undefined || item === undefined || isCancelled) {
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -222,10 +235,15 @@ export const asyncSearchServiceProvider = (
|
||||||
yield undefined;
|
yield undefined;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// don't fail the whole process for individual correlation queries, just add the error to the internal log.
|
// don't fail the whole process for individual correlation queries,
|
||||||
|
// just add the error to the internal log and check if we'd want to set the
|
||||||
|
// cross-cluster search compatibility warning to true.
|
||||||
logMessage(
|
logMessage(
|
||||||
`Failed to fetch correlation/kstest for '${item.field}/${item.value}'`
|
`Failed to fetch correlation/kstest for '${item.field}/${item.value}'`
|
||||||
);
|
);
|
||||||
|
if (params?.index.includes(':')) {
|
||||||
|
ccsWarning = true;
|
||||||
|
}
|
||||||
yield undefined;
|
yield undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,6 +265,10 @@ export const asyncSearchServiceProvider = (
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error !== undefined && params?.index.includes(':')) {
|
||||||
|
ccsWarning = true;
|
||||||
|
}
|
||||||
|
|
||||||
isRunning = false;
|
isRunning = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -256,6 +278,7 @@ export const asyncSearchServiceProvider = (
|
||||||
const sortedValues = values.sort((a, b) => b.correlation - a.correlation);
|
const sortedValues = values.sort((a, b) => b.correlation - a.correlation);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
ccsWarning,
|
||||||
error,
|
error,
|
||||||
log,
|
log,
|
||||||
isRunning,
|
isRunning,
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { pipe } from 'fp-ts/lib/pipeable';
|
||||||
import * as t from 'io-ts';
|
import * as t from 'io-ts';
|
||||||
import { failure } from 'io-ts/lib/PathReporter';
|
import { failure } from 'io-ts/lib/PathReporter';
|
||||||
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
||||||
import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types';
|
import type { SearchServiceFetchParams } from '../../../../common/search_strategies/correlations/types';
|
||||||
import { rangeRt } from '../../../routes/default_api_types';
|
import { rangeRt } from '../../../routes/default_api_types';
|
||||||
import { getCorrelationsFilters } from '../../correlations/get_filters';
|
import { getCorrelationsFilters } from '../../correlations/get_filters';
|
||||||
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
|
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
|
||||||
|
@ -40,7 +40,7 @@ export const getTermsQuery = (
|
||||||
};
|
};
|
||||||
|
|
||||||
interface QueryParams {
|
interface QueryParams {
|
||||||
params: SearchServiceParams;
|
params: SearchServiceFetchParams;
|
||||||
fieldName?: string;
|
fieldName?: string;
|
||||||
fieldValue?: string;
|
fieldValue?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type { estypes } from '@elastic/elasticsearch';
|
||||||
import type { ElasticsearchClient } from 'src/core/server';
|
import type { ElasticsearchClient } from 'src/core/server';
|
||||||
|
|
||||||
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
||||||
import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types';
|
import type { SearchServiceFetchParams } from '../../../../common/search_strategies/correlations/types';
|
||||||
|
|
||||||
import { getQueryWithParams } from './get_query_with_params';
|
import { getQueryWithParams } from './get_query_with_params';
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ export interface BucketCorrelation {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTransactionDurationCorrelationRequest = (
|
export const getTransactionDurationCorrelationRequest = (
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
expectations: number[],
|
expectations: number[],
|
||||||
ranges: estypes.AggregationsAggregationRange[],
|
ranges: estypes.AggregationsAggregationRange[],
|
||||||
fractions: number[],
|
fractions: number[],
|
||||||
|
@ -95,7 +95,7 @@ export const getTransactionDurationCorrelationRequest = (
|
||||||
|
|
||||||
export const fetchTransactionDurationCorrelation = async (
|
export const fetchTransactionDurationCorrelation = async (
|
||||||
esClient: ElasticsearchClient,
|
esClient: ElasticsearchClient,
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
expectations: number[],
|
expectations: number[],
|
||||||
ranges: estypes.AggregationsAggregationRange[],
|
ranges: estypes.AggregationsAggregationRange[],
|
||||||
fractions: number[],
|
fractions: number[],
|
||||||
|
|
|
@ -9,7 +9,7 @@ import type { estypes } from '@elastic/elasticsearch';
|
||||||
|
|
||||||
import type { ElasticsearchClient } from 'src/core/server';
|
import type { ElasticsearchClient } from 'src/core/server';
|
||||||
|
|
||||||
import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types';
|
import type { SearchServiceFetchParams } from '../../../../common/search_strategies/correlations/types';
|
||||||
|
|
||||||
import { getQueryWithParams } from './get_query_with_params';
|
import { getQueryWithParams } from './get_query_with_params';
|
||||||
import { Field } from './query_field_value_pairs';
|
import { Field } from './query_field_value_pairs';
|
||||||
|
@ -37,7 +37,7 @@ export const hasPrefixToInclude = (fieldName: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRandomDocsRequest = (
|
export const getRandomDocsRequest = (
|
||||||
params: SearchServiceParams
|
params: SearchServiceFetchParams
|
||||||
): estypes.SearchRequest => ({
|
): estypes.SearchRequest => ({
|
||||||
index: params.index,
|
index: params.index,
|
||||||
body: {
|
body: {
|
||||||
|
@ -56,7 +56,7 @@ export const getRandomDocsRequest = (
|
||||||
|
|
||||||
export const fetchTransactionDurationFieldCandidates = async (
|
export const fetchTransactionDurationFieldCandidates = async (
|
||||||
esClient: ElasticsearchClient,
|
esClient: ElasticsearchClient,
|
||||||
params: SearchServiceParams
|
params: SearchServiceFetchParams
|
||||||
): Promise<{ fieldCandidates: Field[] }> => {
|
): Promise<{ fieldCandidates: Field[] }> => {
|
||||||
const { index } = params;
|
const { index } = params;
|
||||||
// Get all fields with keyword mapping
|
// Get all fields with keyword mapping
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type { estypes } from '@elastic/elasticsearch';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AsyncSearchProviderProgress,
|
AsyncSearchProviderProgress,
|
||||||
SearchServiceParams,
|
SearchServiceFetchParams,
|
||||||
} from '../../../../common/search_strategies/correlations/types';
|
} from '../../../../common/search_strategies/correlations/types';
|
||||||
|
|
||||||
import { getQueryWithParams } from './get_query_with_params';
|
import { getQueryWithParams } from './get_query_with_params';
|
||||||
|
@ -26,7 +26,7 @@ type FieldValuePairs = FieldValuePair[];
|
||||||
export type Field = string;
|
export type Field = string;
|
||||||
|
|
||||||
export const getTermsAggRequest = (
|
export const getTermsAggRequest = (
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
fieldName: string
|
fieldName: string
|
||||||
): estypes.SearchRequest => ({
|
): estypes.SearchRequest => ({
|
||||||
index: params.index,
|
index: params.index,
|
||||||
|
@ -46,7 +46,7 @@ export const getTermsAggRequest = (
|
||||||
|
|
||||||
export const fetchTransactionDurationFieldValuePairs = async (
|
export const fetchTransactionDurationFieldValuePairs = async (
|
||||||
esClient: ElasticsearchClient,
|
esClient: ElasticsearchClient,
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
fieldCandidates: Field[],
|
fieldCandidates: Field[],
|
||||||
progress: AsyncSearchProviderProgress
|
progress: AsyncSearchProviderProgress
|
||||||
): Promise<FieldValuePairs> => {
|
): Promise<FieldValuePairs> => {
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
|
|
||||||
import { ElasticsearchClient } from 'kibana/server';
|
import { ElasticsearchClient } from 'kibana/server';
|
||||||
import { estypes } from '@elastic/elasticsearch';
|
import { estypes } from '@elastic/elasticsearch';
|
||||||
import { SearchServiceParams } from '../../../../common/search_strategies/correlations/types';
|
import { SearchServiceFetchParams } from '../../../../common/search_strategies/correlations/types';
|
||||||
import { getQueryWithParams } from './get_query_with_params';
|
import { getQueryWithParams } from './get_query_with_params';
|
||||||
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
||||||
|
|
||||||
export const getTransactionDurationRangesRequest = (
|
export const getTransactionDurationRangesRequest = (
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
ranges: estypes.AggregationsAggregationRange[]
|
ranges: estypes.AggregationsAggregationRange[]
|
||||||
): estypes.SearchRequest => ({
|
): estypes.SearchRequest => ({
|
||||||
index: params.index,
|
index: params.index,
|
||||||
|
@ -35,7 +35,7 @@ export const getTransactionDurationRangesRequest = (
|
||||||
*/
|
*/
|
||||||
export const fetchTransactionDurationFractions = async (
|
export const fetchTransactionDurationFractions = async (
|
||||||
esClient: ElasticsearchClient,
|
esClient: ElasticsearchClient,
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
ranges: estypes.AggregationsAggregationRange[]
|
ranges: estypes.AggregationsAggregationRange[]
|
||||||
): Promise<{ fractions: number[]; totalDocCount: number }> => {
|
): Promise<{ fractions: number[]; totalDocCount: number }> => {
|
||||||
const resp = await esClient.search(
|
const resp = await esClient.search(
|
||||||
|
|
|
@ -13,13 +13,13 @@ import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldname
|
||||||
import type {
|
import type {
|
||||||
HistogramItem,
|
HistogramItem,
|
||||||
ResponseHit,
|
ResponseHit,
|
||||||
SearchServiceParams,
|
SearchServiceFetchParams,
|
||||||
} from '../../../../common/search_strategies/correlations/types';
|
} from '../../../../common/search_strategies/correlations/types';
|
||||||
|
|
||||||
import { getQueryWithParams } from './get_query_with_params';
|
import { getQueryWithParams } from './get_query_with_params';
|
||||||
|
|
||||||
export const getTransactionDurationHistogramRequest = (
|
export const getTransactionDurationHistogramRequest = (
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
interval: number,
|
interval: number,
|
||||||
fieldName?: string,
|
fieldName?: string,
|
||||||
fieldValue?: string
|
fieldValue?: string
|
||||||
|
@ -42,7 +42,7 @@ export const getTransactionDurationHistogramRequest = (
|
||||||
|
|
||||||
export const fetchTransactionDurationHistogram = async (
|
export const fetchTransactionDurationHistogram = async (
|
||||||
esClient: ElasticsearchClient,
|
esClient: ElasticsearchClient,
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
interval: number,
|
interval: number,
|
||||||
fieldName?: string,
|
fieldName?: string,
|
||||||
fieldValue?: string
|
fieldValue?: string
|
||||||
|
|
|
@ -10,14 +10,14 @@ import type { estypes } from '@elastic/elasticsearch';
|
||||||
import type { ElasticsearchClient } from 'src/core/server';
|
import type { ElasticsearchClient } from 'src/core/server';
|
||||||
|
|
||||||
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
||||||
import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types';
|
import type { SearchServiceFetchParams } from '../../../../common/search_strategies/correlations/types';
|
||||||
|
|
||||||
import { getQueryWithParams } from './get_query_with_params';
|
import { getQueryWithParams } from './get_query_with_params';
|
||||||
|
|
||||||
const HISTOGRAM_INTERVALS = 1000;
|
const HISTOGRAM_INTERVALS = 1000;
|
||||||
|
|
||||||
export const getHistogramIntervalRequest = (
|
export const getHistogramIntervalRequest = (
|
||||||
params: SearchServiceParams
|
params: SearchServiceFetchParams
|
||||||
): estypes.SearchRequest => ({
|
): estypes.SearchRequest => ({
|
||||||
index: params.index,
|
index: params.index,
|
||||||
body: {
|
body: {
|
||||||
|
@ -32,7 +32,7 @@ export const getHistogramIntervalRequest = (
|
||||||
|
|
||||||
export const fetchTransactionDurationHistogramInterval = async (
|
export const fetchTransactionDurationHistogramInterval = async (
|
||||||
esClient: ElasticsearchClient,
|
esClient: ElasticsearchClient,
|
||||||
params: SearchServiceParams
|
params: SearchServiceFetchParams
|
||||||
): Promise<number> => {
|
): Promise<number> => {
|
||||||
const resp = await esClient.search(getHistogramIntervalRequest(params));
|
const resp = await esClient.search(getHistogramIntervalRequest(params));
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import type { estypes } from '@elastic/elasticsearch';
|
||||||
import type { ElasticsearchClient } from 'src/core/server';
|
import type { ElasticsearchClient } from 'src/core/server';
|
||||||
|
|
||||||
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
||||||
import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types';
|
import type { SearchServiceFetchParams } from '../../../../common/search_strategies/correlations/types';
|
||||||
|
|
||||||
import { getQueryWithParams } from './get_query_with_params';
|
import { getQueryWithParams } from './get_query_with_params';
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ const getHistogramRangeSteps = (min: number, max: number, steps: number) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getHistogramIntervalRequest = (
|
export const getHistogramIntervalRequest = (
|
||||||
params: SearchServiceParams
|
params: SearchServiceFetchParams
|
||||||
): estypes.SearchRequest => ({
|
): estypes.SearchRequest => ({
|
||||||
index: params.index,
|
index: params.index,
|
||||||
body: {
|
body: {
|
||||||
|
@ -47,7 +47,7 @@ export const getHistogramIntervalRequest = (
|
||||||
|
|
||||||
export const fetchTransactionDurationHistogramRangeSteps = async (
|
export const fetchTransactionDurationHistogramRangeSteps = async (
|
||||||
esClient: ElasticsearchClient,
|
esClient: ElasticsearchClient,
|
||||||
params: SearchServiceParams
|
params: SearchServiceFetchParams
|
||||||
): Promise<number[]> => {
|
): Promise<number[]> => {
|
||||||
const steps = 100;
|
const steps = 100;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type { estypes } from '@elastic/elasticsearch';
|
||||||
import type { ElasticsearchClient } from 'src/core/server';
|
import type { ElasticsearchClient } from 'src/core/server';
|
||||||
|
|
||||||
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
||||||
import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types';
|
import type { SearchServiceFetchParams } from '../../../../common/search_strategies/correlations/types';
|
||||||
|
|
||||||
import { getQueryWithParams } from './get_query_with_params';
|
import { getQueryWithParams } from './get_query_with_params';
|
||||||
import { SIGNIFICANT_VALUE_DIGITS } from './constants';
|
import { SIGNIFICANT_VALUE_DIGITS } from './constants';
|
||||||
|
@ -28,7 +28,7 @@ interface ResponseHit {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTransactionDurationPercentilesRequest = (
|
export const getTransactionDurationPercentilesRequest = (
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
percents?: number[],
|
percents?: number[],
|
||||||
fieldName?: string,
|
fieldName?: string,
|
||||||
fieldValue?: string
|
fieldValue?: string
|
||||||
|
@ -58,7 +58,7 @@ export const getTransactionDurationPercentilesRequest = (
|
||||||
|
|
||||||
export const fetchTransactionDurationPercentiles = async (
|
export const fetchTransactionDurationPercentiles = async (
|
||||||
esClient: ElasticsearchClient,
|
esClient: ElasticsearchClient,
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
percents?: number[],
|
percents?: number[],
|
||||||
fieldName?: string,
|
fieldName?: string,
|
||||||
fieldValue?: string
|
fieldValue?: string
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type { estypes } from '@elastic/elasticsearch';
|
||||||
import type { ElasticsearchClient } from 'src/core/server';
|
import type { ElasticsearchClient } from 'src/core/server';
|
||||||
|
|
||||||
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames';
|
||||||
import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types';
|
import type { SearchServiceFetchParams } from '../../../../common/search_strategies/correlations/types';
|
||||||
|
|
||||||
import { getQueryWithParams } from './get_query_with_params';
|
import { getQueryWithParams } from './get_query_with_params';
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ interface ResponseHit {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTransactionDurationRangesRequest = (
|
export const getTransactionDurationRangesRequest = (
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
rangesSteps: number[],
|
rangesSteps: number[],
|
||||||
fieldName?: string,
|
fieldName?: string,
|
||||||
fieldValue?: string
|
fieldValue?: string
|
||||||
|
@ -65,7 +65,7 @@ export const getTransactionDurationRangesRequest = (
|
||||||
|
|
||||||
export const fetchTransactionDurationRanges = async (
|
export const fetchTransactionDurationRanges = async (
|
||||||
esClient: ElasticsearchClient,
|
esClient: ElasticsearchClient,
|
||||||
params: SearchServiceParams,
|
params: SearchServiceFetchParams,
|
||||||
rangesSteps: number[],
|
rangesSteps: number[],
|
||||||
fieldName?: string,
|
fieldName?: string,
|
||||||
fieldValue?: string
|
fieldValue?: string
|
||||||
|
|
|
@ -9,6 +9,8 @@ import type { estypes } from '@elastic/elasticsearch';
|
||||||
|
|
||||||
import { SearchStrategyDependencies } from 'src/plugins/data/server';
|
import { SearchStrategyDependencies } from 'src/plugins/data/server';
|
||||||
|
|
||||||
|
import type { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
apmCorrelationsSearchStrategyProvider,
|
apmCorrelationsSearchStrategyProvider,
|
||||||
PartialSearchRequest,
|
PartialSearchRequest,
|
||||||
|
@ -94,10 +96,19 @@ const clientSearchMock = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getApmIndicesMock = async () =>
|
||||||
|
({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
'apm_oss.transactionIndices': 'apm-*',
|
||||||
|
} as ApmIndicesConfig);
|
||||||
|
|
||||||
describe('APM Correlations search strategy', () => {
|
describe('APM Correlations search strategy', () => {
|
||||||
describe('strategy interface', () => {
|
describe('strategy interface', () => {
|
||||||
it('returns a custom search strategy with a `search` and `cancel` function', async () => {
|
it('returns a custom search strategy with a `search` and `cancel` function', async () => {
|
||||||
const searchStrategy = await apmCorrelationsSearchStrategyProvider();
|
const searchStrategy = await apmCorrelationsSearchStrategyProvider(
|
||||||
|
getApmIndicesMock,
|
||||||
|
false
|
||||||
|
);
|
||||||
expect(typeof searchStrategy.search).toBe('function');
|
expect(typeof searchStrategy.search).toBe('function');
|
||||||
expect(typeof searchStrategy.cancel).toBe('function');
|
expect(typeof searchStrategy.cancel).toBe('function');
|
||||||
});
|
});
|
||||||
|
@ -106,12 +117,14 @@ describe('APM Correlations search strategy', () => {
|
||||||
describe('search', () => {
|
describe('search', () => {
|
||||||
let mockClientFieldCaps: jest.Mock;
|
let mockClientFieldCaps: jest.Mock;
|
||||||
let mockClientSearch: jest.Mock;
|
let mockClientSearch: jest.Mock;
|
||||||
|
let mockGetApmIndicesMock: jest.Mock;
|
||||||
let mockDeps: SearchStrategyDependencies;
|
let mockDeps: SearchStrategyDependencies;
|
||||||
let params: Required<PartialSearchRequest>['params'];
|
let params: Required<PartialSearchRequest>['params'];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockClientFieldCaps = jest.fn(clientFieldCapsMock);
|
mockClientFieldCaps = jest.fn(clientFieldCapsMock);
|
||||||
mockClientSearch = jest.fn(clientSearchMock);
|
mockClientSearch = jest.fn(clientSearchMock);
|
||||||
|
mockGetApmIndicesMock = jest.fn(getApmIndicesMock);
|
||||||
mockDeps = ({
|
mockDeps = ({
|
||||||
esClient: {
|
esClient: {
|
||||||
asCurrentUser: {
|
asCurrentUser: {
|
||||||
|
@ -121,7 +134,6 @@ describe('APM Correlations search strategy', () => {
|
||||||
},
|
},
|
||||||
} as unknown) as SearchStrategyDependencies;
|
} as unknown) as SearchStrategyDependencies;
|
||||||
params = {
|
params = {
|
||||||
index: 'apm-*',
|
|
||||||
start: '2020',
|
start: '2020',
|
||||||
end: '2021',
|
end: '2021',
|
||||||
};
|
};
|
||||||
|
@ -130,7 +142,13 @@ describe('APM Correlations search strategy', () => {
|
||||||
describe('async functionality', () => {
|
describe('async functionality', () => {
|
||||||
describe('when no params are provided', () => {
|
describe('when no params are provided', () => {
|
||||||
it('throws an error', async () => {
|
it('throws an error', async () => {
|
||||||
const searchStrategy = await apmCorrelationsSearchStrategyProvider();
|
const searchStrategy = await apmCorrelationsSearchStrategyProvider(
|
||||||
|
mockGetApmIndicesMock,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockGetApmIndicesMock).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
expect(() => searchStrategy.search({}, {}, mockDeps)).toThrow(
|
expect(() => searchStrategy.search({}, {}, mockDeps)).toThrow(
|
||||||
'Invalid request parameters.'
|
'Invalid request parameters.'
|
||||||
);
|
);
|
||||||
|
@ -139,8 +157,14 @@ describe('APM Correlations search strategy', () => {
|
||||||
|
|
||||||
describe('when no ID is provided', () => {
|
describe('when no ID is provided', () => {
|
||||||
it('performs a client search with params', async () => {
|
it('performs a client search with params', async () => {
|
||||||
const searchStrategy = await apmCorrelationsSearchStrategyProvider();
|
const searchStrategy = await apmCorrelationsSearchStrategyProvider(
|
||||||
|
mockGetApmIndicesMock,
|
||||||
|
false
|
||||||
|
);
|
||||||
await searchStrategy.search({ params }, {}, mockDeps).toPromise();
|
await searchStrategy.search({ params }, {}, mockDeps).toPromise();
|
||||||
|
|
||||||
|
expect(mockGetApmIndicesMock).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
const [[request]] = mockClientSearch.mock.calls;
|
const [[request]] = mockClientSearch.mock.calls;
|
||||||
|
|
||||||
expect(request.index).toEqual('apm-*');
|
expect(request.index).toEqual('apm-*');
|
||||||
|
@ -179,7 +203,10 @@ describe('APM Correlations search strategy', () => {
|
||||||
|
|
||||||
describe('when an ID with params is provided', () => {
|
describe('when an ID with params is provided', () => {
|
||||||
it('retrieves the current request', async () => {
|
it('retrieves the current request', async () => {
|
||||||
const searchStrategy = await apmCorrelationsSearchStrategyProvider();
|
const searchStrategy = await apmCorrelationsSearchStrategyProvider(
|
||||||
|
mockGetApmIndicesMock,
|
||||||
|
false
|
||||||
|
);
|
||||||
const response = await searchStrategy
|
const response = await searchStrategy
|
||||||
.search({ params }, {}, mockDeps)
|
.search({ params }, {}, mockDeps)
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
@ -190,6 +217,7 @@ describe('APM Correlations search strategy', () => {
|
||||||
.search({ id: searchStrategyId, params }, {}, mockDeps)
|
.search({ id: searchStrategyId, params }, {}, mockDeps)
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
||||||
|
expect(mockGetApmIndicesMock).toHaveBeenCalledTimes(1);
|
||||||
expect(response2).toEqual(
|
expect(response2).toEqual(
|
||||||
expect.objectContaining({ id: searchStrategyId })
|
expect.objectContaining({ id: searchStrategyId })
|
||||||
);
|
);
|
||||||
|
@ -201,11 +229,16 @@ describe('APM Correlations search strategy', () => {
|
||||||
mockClientSearch
|
mockClientSearch
|
||||||
.mockReset()
|
.mockReset()
|
||||||
.mockRejectedValueOnce(new Error('client error'));
|
.mockRejectedValueOnce(new Error('client error'));
|
||||||
const searchStrategy = await apmCorrelationsSearchStrategyProvider();
|
const searchStrategy = await apmCorrelationsSearchStrategyProvider(
|
||||||
|
mockGetApmIndicesMock,
|
||||||
|
false
|
||||||
|
);
|
||||||
const response = await searchStrategy
|
const response = await searchStrategy
|
||||||
.search({ params }, {}, mockDeps)
|
.search({ params }, {}, mockDeps)
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
||||||
|
expect(mockGetApmIndicesMock).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
expect(response).toEqual(
|
expect(response).toEqual(
|
||||||
expect.objectContaining({ isRunning: true })
|
expect.objectContaining({ isRunning: true })
|
||||||
);
|
);
|
||||||
|
@ -213,11 +246,15 @@ describe('APM Correlations search strategy', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('triggers the subscription only once', async () => {
|
it('triggers the subscription only once', async () => {
|
||||||
expect.assertions(1);
|
expect.assertions(2);
|
||||||
const searchStrategy = await apmCorrelationsSearchStrategyProvider();
|
const searchStrategy = await apmCorrelationsSearchStrategyProvider(
|
||||||
|
mockGetApmIndicesMock,
|
||||||
|
false
|
||||||
|
);
|
||||||
searchStrategy
|
searchStrategy
|
||||||
.search({ params }, {}, mockDeps)
|
.search({ params }, {}, mockDeps)
|
||||||
.subscribe((response) => {
|
.subscribe((response) => {
|
||||||
|
expect(mockGetApmIndicesMock).toHaveBeenCalledTimes(1);
|
||||||
expect(response).toEqual(
|
expect(response).toEqual(
|
||||||
expect.objectContaining({ loaded: 0, isRunning: true })
|
expect.objectContaining({ loaded: 0, isRunning: true })
|
||||||
);
|
);
|
||||||
|
@ -227,12 +264,16 @@ describe('APM Correlations search strategy', () => {
|
||||||
|
|
||||||
describe('response', () => {
|
describe('response', () => {
|
||||||
it('sends an updated response on consecutive search calls', async () => {
|
it('sends an updated response on consecutive search calls', async () => {
|
||||||
const searchStrategy = await apmCorrelationsSearchStrategyProvider();
|
const searchStrategy = await apmCorrelationsSearchStrategyProvider(
|
||||||
|
mockGetApmIndicesMock,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
const response1 = await searchStrategy
|
const response1 = await searchStrategy
|
||||||
.search({ params }, {}, mockDeps)
|
.search({ params }, {}, mockDeps)
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
||||||
|
expect(mockGetApmIndicesMock).toHaveBeenCalledTimes(1);
|
||||||
expect(typeof response1.id).toEqual('string');
|
expect(typeof response1.id).toEqual('string');
|
||||||
expect(response1).toEqual(
|
expect(response1).toEqual(
|
||||||
expect.objectContaining({ loaded: 0, isRunning: true })
|
expect.objectContaining({ loaded: 0, isRunning: true })
|
||||||
|
@ -244,6 +285,7 @@ describe('APM Correlations search strategy', () => {
|
||||||
.search({ id: response1.id, params }, {}, mockDeps)
|
.search({ id: response1.id, params }, {}, mockDeps)
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
||||||
|
expect(mockGetApmIndicesMock).toHaveBeenCalledTimes(1);
|
||||||
expect(response2.id).toEqual(response1.id);
|
expect(response2.id).toEqual(response1.id);
|
||||||
expect(response2).toEqual(
|
expect(response2).toEqual(
|
||||||
expect.objectContaining({ loaded: 100, isRunning: false })
|
expect.objectContaining({ loaded: 100, isRunning: false })
|
||||||
|
|
|
@ -19,6 +19,8 @@ import type {
|
||||||
SearchServiceValue,
|
SearchServiceValue,
|
||||||
} from '../../../../common/search_strategies/correlations/types';
|
} from '../../../../common/search_strategies/correlations/types';
|
||||||
|
|
||||||
|
import type { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices';
|
||||||
|
|
||||||
import { asyncSearchServiceProvider } from './async_search_service';
|
import { asyncSearchServiceProvider } from './async_search_service';
|
||||||
|
|
||||||
export type PartialSearchRequest = IKibanaSearchRequest<SearchServiceParams>;
|
export type PartialSearchRequest = IKibanaSearchRequest<SearchServiceParams>;
|
||||||
|
@ -26,10 +28,10 @@ export type PartialSearchResponse = IKibanaSearchResponse<{
|
||||||
values: SearchServiceValue[];
|
values: SearchServiceValue[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const apmCorrelationsSearchStrategyProvider = (): ISearchStrategy<
|
export const apmCorrelationsSearchStrategyProvider = (
|
||||||
PartialSearchRequest,
|
getApmIndices: () => Promise<ApmIndicesConfig>,
|
||||||
PartialSearchResponse
|
includeFrozen: boolean
|
||||||
> => {
|
): ISearchStrategy<PartialSearchRequest, PartialSearchResponse> => {
|
||||||
const asyncSearchServiceMap = new Map<
|
const asyncSearchServiceMap = new Map<
|
||||||
string,
|
string,
|
||||||
ReturnType<typeof asyncSearchServiceProvider>
|
ReturnType<typeof asyncSearchServiceProvider>
|
||||||
|
@ -65,7 +67,9 @@ export const apmCorrelationsSearchStrategyProvider = (): ISearchStrategy<
|
||||||
} else {
|
} else {
|
||||||
getAsyncSearchServiceState = asyncSearchServiceProvider(
|
getAsyncSearchServiceState = asyncSearchServiceProvider(
|
||||||
deps.esClient.asCurrentUser,
|
deps.esClient.asCurrentUser,
|
||||||
request.params
|
getApmIndices,
|
||||||
|
request.params,
|
||||||
|
includeFrozen
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +77,7 @@ export const apmCorrelationsSearchStrategyProvider = (): ISearchStrategy<
|
||||||
const id = request.id ?? uuid();
|
const id = request.id ?? uuid();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
ccsWarning,
|
||||||
error,
|
error,
|
||||||
log,
|
log,
|
||||||
isRunning,
|
isRunning,
|
||||||
|
@ -102,6 +107,7 @@ export const apmCorrelationsSearchStrategyProvider = (): ISearchStrategy<
|
||||||
isRunning,
|
isRunning,
|
||||||
isPartial: isRunning,
|
isPartial: isRunning,
|
||||||
rawResponse: {
|
rawResponse: {
|
||||||
|
ccsWarning,
|
||||||
log,
|
log,
|
||||||
took,
|
took,
|
||||||
values,
|
values,
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
PluginInitializerContext,
|
PluginInitializerContext,
|
||||||
} from 'src/core/server';
|
} from 'src/core/server';
|
||||||
import { isEmpty, mapValues, once } from 'lodash';
|
import { isEmpty, mapValues, once } from 'lodash';
|
||||||
|
import { SavedObjectsClient } from '../../../../src/core/server';
|
||||||
import { TECHNICAL_COMPONENT_TEMPLATE_NAME } from '../../rule_registry/common/assets';
|
import { TECHNICAL_COMPONENT_TEMPLATE_NAME } from '../../rule_registry/common/assets';
|
||||||
import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map';
|
import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map';
|
||||||
import { APMConfig, APMXPackConfig, APM_SERVER_FEATURE_ID } from '.';
|
import { APMConfig, APMXPackConfig, APM_SERVER_FEATURE_ID } from '.';
|
||||||
|
@ -248,12 +249,24 @@ export class APMPlugin
|
||||||
});
|
});
|
||||||
|
|
||||||
// search strategies for async partial search results
|
// search strategies for async partial search results
|
||||||
if (plugins.data?.search?.registerSearchStrategy !== undefined) {
|
core.getStartServices().then(([coreStart]) => {
|
||||||
plugins.data.search.registerSearchStrategy(
|
(async () => {
|
||||||
'apmCorrelationsSearchStrategy',
|
const savedObjectsClient = new SavedObjectsClient(
|
||||||
apmCorrelationsSearchStrategyProvider()
|
coreStart.savedObjects.createInternalRepository()
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
plugins.data.search.registerSearchStrategy(
|
||||||
|
'apmCorrelationsSearchStrategy',
|
||||||
|
apmCorrelationsSearchStrategyProvider(
|
||||||
|
boundGetApmIndices,
|
||||||
|
await coreStart.uiSettings
|
||||||
|
.asScopedToClient(savedObjectsClient)
|
||||||
|
.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
config$: mergedConfig$,
|
config$: mergedConfig$,
|
||||||
getApmIndices: boundGetApmIndices,
|
getApmIndices: boundGetApmIndices,
|
||||||
|
|
|
@ -26,7 +26,6 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
||||||
const getRequestBody = () => {
|
const getRequestBody = () => {
|
||||||
const partialSearchRequest: PartialSearchRequest = {
|
const partialSearchRequest: PartialSearchRequest = {
|
||||||
params: {
|
params: {
|
||||||
index: 'apm-*',
|
|
||||||
environment: 'ENVIRONMENT_ALL',
|
environment: 'ENVIRONMENT_ALL',
|
||||||
start: '2020',
|
start: '2020',
|
||||||
end: '2021',
|
end: '2021',
|
||||||
|
@ -141,7 +140,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
||||||
|
|
||||||
registry.when(
|
registry.when(
|
||||||
'Correlations latency_ml with data and opbeans-node args',
|
'Correlations latency_ml with data and opbeans-node args',
|
||||||
{ config: 'trial', archives: ['ml_8.0.0'] },
|
{ config: 'trial', archives: ['8.0.0'] },
|
||||||
() => {
|
() => {
|
||||||
// putting this into a single `it` because the responses depend on each other
|
// putting this into a single `it` because the responses depend on each other
|
||||||
it('queries the search strategy and returns results', async () => {
|
it('queries the search strategy and returns results', async () => {
|
||||||
|
@ -235,30 +234,30 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
||||||
const { rawResponse: finalRawResponse } = followUpResult;
|
const { rawResponse: finalRawResponse } = followUpResult;
|
||||||
|
|
||||||
expect(typeof finalRawResponse?.took).to.be('number');
|
expect(typeof finalRawResponse?.took).to.be('number');
|
||||||
expect(finalRawResponse?.percentileThresholdValue).to.be(1404927.875);
|
expect(finalRawResponse?.percentileThresholdValue).to.be(1309695.875);
|
||||||
expect(finalRawResponse?.overallHistogram.length).to.be(101);
|
expect(finalRawResponse?.overallHistogram.length).to.be(101);
|
||||||
|
|
||||||
expect(finalRawResponse?.values.length).to.eql(
|
expect(finalRawResponse?.values.length).to.eql(
|
||||||
1,
|
13,
|
||||||
`Expected 1 identified correlations, got ${finalRawResponse?.values.length}.`
|
`Expected 13 identified correlations, got ${finalRawResponse?.values.length}.`
|
||||||
);
|
);
|
||||||
expect(finalRawResponse?.log.map((d: string) => d.split(': ')[1])).to.eql([
|
expect(finalRawResponse?.log.map((d: string) => d.split(': ')[1])).to.eql([
|
||||||
'Fetched 95th percentile value of 1404927.875 based on 989 documents.',
|
'Fetched 95th percentile value of 1309695.875 based on 1244 documents.',
|
||||||
'Loaded histogram range steps.',
|
'Loaded histogram range steps.',
|
||||||
'Loaded overall histogram chart data.',
|
'Loaded overall histogram chart data.',
|
||||||
'Loaded percentiles.',
|
'Loaded percentiles.',
|
||||||
'Identified 67 fieldCandidates.',
|
'Identified 69 fieldCandidates.',
|
||||||
'Identified 339 fieldValuePairs.',
|
'Identified 379 fieldValuePairs.',
|
||||||
'Loaded fractions and totalDocCount of 989.',
|
'Loaded fractions and totalDocCount of 1244.',
|
||||||
'Identified 1 significant correlations out of 339 field/value pairs.',
|
'Identified 13 significant correlations out of 379 field/value pairs.',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const correlation = finalRawResponse?.values[0];
|
const correlation = finalRawResponse?.values[0];
|
||||||
expect(typeof correlation).to.be('object');
|
expect(typeof correlation).to.be('object');
|
||||||
expect(correlation?.field).to.be('transaction.result');
|
expect(correlation?.field).to.be('transaction.result');
|
||||||
expect(correlation?.value).to.be('success');
|
expect(correlation?.value).to.be('success');
|
||||||
expect(correlation?.correlation).to.be(0.37418510688551887);
|
expect(correlation?.correlation).to.be(0.6275246559191225);
|
||||||
expect(correlation?.ksTest).to.be(1.1238496968312214e-10);
|
expect(correlation?.ksTest).to.be(4.806503252860024e-13);
|
||||||
expect(correlation?.histogram.length).to.be(101);
|
expect(correlation?.histogram.length).to.be(101);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
15
x-pack/test/functional/apps/apm/correlations/index.ts
Normal file
15
x-pack/test/functional/apps/apm/correlations/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||||
|
describe('correlations', function () {
|
||||||
|
this.tags('skipFirefox');
|
||||||
|
loadTestFile(require.resolve('./latency_correlations'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import expect from '@kbn/expect';
|
||||||
|
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||||
|
|
||||||
|
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
|
const esArchiver = getService('esArchiver');
|
||||||
|
const find = getService('find');
|
||||||
|
const retry = getService('retry');
|
||||||
|
const spacesService = getService('spaces');
|
||||||
|
const PageObjects = getPageObjects(['common', 'error', 'timePicker', 'security']);
|
||||||
|
const testSubjects = getService('testSubjects');
|
||||||
|
const appsMenu = getService('appsMenu');
|
||||||
|
|
||||||
|
const testData = { serviceName: 'opbeans-go' };
|
||||||
|
|
||||||
|
describe('latency correlations', () => {
|
||||||
|
describe('space with no features disabled', () => {
|
||||||
|
before(async () => {
|
||||||
|
await esArchiver.load('x-pack/test/functional/es_archives/infra/8.0.0/metrics_and_apm');
|
||||||
|
await spacesService.create({
|
||||||
|
id: 'custom_space',
|
||||||
|
name: 'custom_space',
|
||||||
|
disabledFeatures: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await spacesService.delete('custom_space');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows apm navlink', async () => {
|
||||||
|
await PageObjects.common.navigateToApp('home', {
|
||||||
|
basePath: '/s/custom_space',
|
||||||
|
});
|
||||||
|
const navLinks = (await appsMenu.readLinks()).map((link) => link.text);
|
||||||
|
expect(navLinks).to.contain('APM');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can navigate to APM app', async () => {
|
||||||
|
await PageObjects.common.navigateToApp('apm');
|
||||||
|
|
||||||
|
await retry.try(async () => {
|
||||||
|
await testSubjects.existOrFail('apmMainContainer', {
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const apmMainContainerText = await testSubjects.getVisibleTextAll('apmMainContainer');
|
||||||
|
const apmMainContainerTextItems = apmMainContainerText[0].split('\n');
|
||||||
|
expect(apmMainContainerTextItems).to.contain('No services found');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the timePicker to return data', async () => {
|
||||||
|
await PageObjects.timePicker.timePickerExists();
|
||||||
|
|
||||||
|
const fromTime = 'Jul 29, 2019 @ 00:00:00.000';
|
||||||
|
const toTime = 'Jul 30, 2019 @ 00:00:00.000';
|
||||||
|
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
|
||||||
|
|
||||||
|
await retry.try(async () => {
|
||||||
|
const apmMainContainerText = await testSubjects.getVisibleTextAll('apmMainContainer');
|
||||||
|
const apmMainContainerTextItems = apmMainContainerText[0].split('\n');
|
||||||
|
|
||||||
|
expect(apmMainContainerTextItems).to.not.contain('No services found');
|
||||||
|
|
||||||
|
expect(apmMainContainerTextItems).to.contain('opbeans-go');
|
||||||
|
expect(apmMainContainerTextItems).to.contain('opbeans-node');
|
||||||
|
expect(apmMainContainerTextItems).to.contain('opbeans-ruby');
|
||||||
|
expect(apmMainContainerTextItems).to.contain('opbeans-python');
|
||||||
|
expect(apmMainContainerTextItems).to.contain('opbeans-dotnet');
|
||||||
|
expect(apmMainContainerTextItems).to.contain('opbeans-java');
|
||||||
|
|
||||||
|
expect(apmMainContainerTextItems).to.contain('development');
|
||||||
|
|
||||||
|
const items = await testSubjects.findAll('apmServiceListAppLink');
|
||||||
|
expect(items.length).to.be(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`navigates to the 'opbeans-go' service overview page`, async function () {
|
||||||
|
await find.clickByDisplayedLinkText(testData.serviceName);
|
||||||
|
|
||||||
|
await retry.try(async () => {
|
||||||
|
const apmMainTemplateHeaderServiceName = await testSubjects.getVisibleTextAll(
|
||||||
|
'apmMainTemplateHeaderServiceName'
|
||||||
|
);
|
||||||
|
expect(apmMainTemplateHeaderServiceName).to.contain('opbeans-go');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the correlations flyout', async function () {
|
||||||
|
await testSubjects.click('apmViewCorrelationsButton');
|
||||||
|
|
||||||
|
await retry.try(async () => {
|
||||||
|
await testSubjects.existOrFail('apmCorrelationsFlyout', {
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const apmCorrelationsFlyoutHeader = await testSubjects.getVisibleText(
|
||||||
|
'apmCorrelationsFlyoutHeader'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(apmCorrelationsFlyoutHeader).to.contain('Correlations BETA');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the correlation results', async function () {
|
||||||
|
await retry.try(async () => {
|
||||||
|
// Assert that the data fully loaded to 100%
|
||||||
|
const apmCorrelationsLatencyCorrelationsProgressTitle = await testSubjects.getVisibleText(
|
||||||
|
'apmCorrelationsLatencyCorrelationsProgressTitle'
|
||||||
|
);
|
||||||
|
expect(apmCorrelationsLatencyCorrelationsProgressTitle).to.be('Progress: 100%');
|
||||||
|
|
||||||
|
// Assert that the Correlations Chart and its header are present
|
||||||
|
const apmCorrelationsLatencyCorrelationsChartTitle = await testSubjects.getVisibleText(
|
||||||
|
'apmCorrelationsLatencyCorrelationsChartTitle'
|
||||||
|
);
|
||||||
|
expect(apmCorrelationsLatencyCorrelationsChartTitle).to.be(
|
||||||
|
`Latency distribution for ${testData.serviceName}`
|
||||||
|
);
|
||||||
|
await testSubjects.existOrFail('apmCorrelationsChart', {
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert that results for the given service didn't find any correlations
|
||||||
|
const apmCorrelationsTable = await testSubjects.getVisibleText('apmCorrelationsTable');
|
||||||
|
expect(apmCorrelationsTable).to.be('No significant correlations found');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -11,5 +11,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
||||||
describe('APM specs', function () {
|
describe('APM specs', function () {
|
||||||
this.tags('ciGroup6');
|
this.tags('ciGroup6');
|
||||||
loadTestFile(require.resolve('./feature_controls'));
|
loadTestFile(require.resolve('./feature_controls'));
|
||||||
|
loadTestFile(require.resolve('./correlations'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue