mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[APM] Fix for no-data state for fallback from aggregated transactions (#109995)
* [APM] Fix for no-data state for fallback from aggregated transactions (#109609) * PR feedback and unit tests * fixes lint error Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d3774519c0
commit
e22c46bcc6
7 changed files with 503 additions and 45 deletions
|
@ -0,0 +1,145 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getIsUsingTransactionEvents with config xpack.apm.searchAggregatedTransactions: always should query for data when kuery is set 1`] = `
|
||||
Object {
|
||||
"apm": Object {
|
||||
"events": Array [
|
||||
"metric",
|
||||
],
|
||||
},
|
||||
"body": Object {
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"exists": Object {
|
||||
"field": "transaction.duration.histogram",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
"format": "epoch_millis",
|
||||
"gte": 1528113600000,
|
||||
"lte": 1528977600000,
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match_phrase": Object {
|
||||
"proccessor.event": "transaction",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"terminateAfter": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`getIsUsingTransactionEvents with config xpack.apm.searchAggregatedTransactions: auto should query for data once if metrics data found 1`] = `
|
||||
Object {
|
||||
"apm": Object {
|
||||
"events": Array [
|
||||
"metric",
|
||||
],
|
||||
},
|
||||
"body": Object {
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"exists": Object {
|
||||
"field": "transaction.duration.histogram",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
"format": "epoch_millis",
|
||||
"gte": 1528113600000,
|
||||
"lte": 1528977600000,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"terminateAfter": 1,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`getIsUsingTransactionEvents with config xpack.apm.searchAggregatedTransactions: auto should query for data twice if metrics data not found 1`] = `
|
||||
Array [
|
||||
Array [
|
||||
"get_has_aggregated_transactions",
|
||||
Object {
|
||||
"apm": Object {
|
||||
"events": Array [
|
||||
"metric",
|
||||
],
|
||||
},
|
||||
"body": Object {
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"exists": Object {
|
||||
"field": "transaction.duration.histogram",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
"format": "epoch_millis",
|
||||
"gte": 1528113600000,
|
||||
"lte": 1528977600000,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"terminateAfter": 1,
|
||||
},
|
||||
],
|
||||
Array [
|
||||
"get_has_transactions",
|
||||
Object {
|
||||
"apm": Object {
|
||||
"events": Array [
|
||||
"transaction",
|
||||
],
|
||||
},
|
||||
"body": Object {
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
"format": "epoch_millis",
|
||||
"gte": 1528113600000,
|
||||
"lte": 1528977600000,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"terminateAfter": 1,
|
||||
},
|
||||
],
|
||||
]
|
||||
`;
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* 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 { getSearchAggregatedTransactions } from '.';
|
||||
import { SearchAggregatedTransactionSetting } from '../../../../common/aggregated_transactions';
|
||||
import { Setup, SetupTimeRange } from '../setup_request';
|
||||
|
||||
export async function getFallbackToTransactions({
|
||||
setup: { config, start, end, apmEventClient },
|
||||
kuery,
|
||||
}: {
|
||||
setup: Setup & Partial<SetupTimeRange>;
|
||||
kuery: string;
|
||||
}): Promise<boolean> {
|
||||
const searchAggregatedTransactions =
|
||||
config['xpack.apm.searchAggregatedTransactions'];
|
||||
const neverSearchAggregatedTransactions =
|
||||
searchAggregatedTransactions === SearchAggregatedTransactionSetting.never;
|
||||
|
||||
if (neverSearchAggregatedTransactions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const searchesAggregatedTransactions = await getSearchAggregatedTransactions({
|
||||
config,
|
||||
start,
|
||||
end,
|
||||
apmEventClient,
|
||||
kuery,
|
||||
});
|
||||
return !searchesAggregatedTransactions;
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
* 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 { getIsUsingTransactionEvents } from './get_is_using_transaction_events';
|
||||
import {
|
||||
SearchParamsMock,
|
||||
inspectSearchParams,
|
||||
} from '../../../utils/test_helpers';
|
||||
import { SearchAggregatedTransactionSetting } from '../../../../common/aggregated_transactions';
|
||||
|
||||
const mockResponseNoHits = {
|
||||
took: 398,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
total: 1,
|
||||
successful: 1,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
},
|
||||
hits: {
|
||||
total: {
|
||||
value: 0,
|
||||
relation: 'gte' as const,
|
||||
max_score: 0,
|
||||
},
|
||||
hits: [],
|
||||
},
|
||||
};
|
||||
|
||||
const mockResponseSomeHits = {
|
||||
took: 398,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
total: 1,
|
||||
successful: 1,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
},
|
||||
hits: {
|
||||
total: {
|
||||
value: 3,
|
||||
relation: 'gte' as const,
|
||||
},
|
||||
hits: [],
|
||||
},
|
||||
};
|
||||
|
||||
describe('getIsUsingTransactionEvents', () => {
|
||||
let mock: SearchParamsMock;
|
||||
|
||||
afterEach(() => {
|
||||
mock.teardown();
|
||||
});
|
||||
|
||||
describe('with config xpack.apm.searchAggregatedTransactions: never', () => {
|
||||
const config = {
|
||||
'xpack.apm.searchAggregatedTransactions':
|
||||
SearchAggregatedTransactionSetting.never,
|
||||
};
|
||||
|
||||
it('should be false', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) => getIsUsingTransactionEvents({ setup, kuery: '' }),
|
||||
{ config }
|
||||
);
|
||||
expect(mock.response).toBe(false);
|
||||
});
|
||||
|
||||
it('should not query for data', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) => getIsUsingTransactionEvents({ setup, kuery: '' }),
|
||||
{ config }
|
||||
);
|
||||
expect(mock.spy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with config xpack.apm.searchAggregatedTransactions: always', () => {
|
||||
const config = {
|
||||
'xpack.apm.searchAggregatedTransactions':
|
||||
SearchAggregatedTransactionSetting.always,
|
||||
};
|
||||
it('should be false when kuery is empty', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) => getIsUsingTransactionEvents({ setup, kuery: '' }),
|
||||
{ config }
|
||||
);
|
||||
expect(mock.response).toBe(false);
|
||||
});
|
||||
|
||||
it('should be false when kuery is set and metrics data found', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) =>
|
||||
getIsUsingTransactionEvents({
|
||||
setup,
|
||||
kuery: 'proccessor.event: "transaction"',
|
||||
}),
|
||||
{
|
||||
config,
|
||||
mockResponse: (request) => {
|
||||
if (request === 'get_has_aggregated_transactions') {
|
||||
return mockResponseSomeHits;
|
||||
}
|
||||
if (request === 'get_has_transactions') {
|
||||
return mockResponseNoHits;
|
||||
}
|
||||
return mockResponseNoHits;
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(mock.spy).toHaveBeenCalledTimes(1);
|
||||
expect(mock.response).toBe(false);
|
||||
});
|
||||
|
||||
it('should be true when kuery is set and metrics data are not found', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) =>
|
||||
getIsUsingTransactionEvents({
|
||||
setup,
|
||||
kuery: 'proccessor.event: "transaction"',
|
||||
}),
|
||||
{
|
||||
config,
|
||||
mockResponse: (request) => {
|
||||
if (request === 'get_has_aggregated_transactions') {
|
||||
return mockResponseNoHits;
|
||||
}
|
||||
if (request === 'get_has_transactions') {
|
||||
return mockResponseSomeHits;
|
||||
}
|
||||
return mockResponseNoHits;
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(mock.spy).toHaveBeenCalledTimes(2);
|
||||
expect(mock.response).toBe(true);
|
||||
});
|
||||
|
||||
it('should not query for data when kuery is empty', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) => getIsUsingTransactionEvents({ setup, kuery: '' }),
|
||||
{ config }
|
||||
);
|
||||
expect(mock.spy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should query for data when kuery is set', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) =>
|
||||
getIsUsingTransactionEvents({
|
||||
setup,
|
||||
kuery: 'proccessor.event: "transaction"',
|
||||
}),
|
||||
{ config }
|
||||
);
|
||||
expect(mock.spy).toHaveBeenCalledTimes(1);
|
||||
expect(mock.params).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with config xpack.apm.searchAggregatedTransactions: auto', () => {
|
||||
const config = {
|
||||
'xpack.apm.searchAggregatedTransactions':
|
||||
SearchAggregatedTransactionSetting.auto,
|
||||
};
|
||||
|
||||
it('should query for data once if metrics data found', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) => getIsUsingTransactionEvents({ setup, kuery: '' }),
|
||||
{
|
||||
config,
|
||||
mockResponse: (request) => {
|
||||
if (request === 'get_has_aggregated_transactions') {
|
||||
return mockResponseSomeHits;
|
||||
}
|
||||
if (request === 'get_has_transactions') {
|
||||
return mockResponseNoHits;
|
||||
}
|
||||
return mockResponseNoHits;
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(mock.spy).toHaveBeenCalledTimes(1);
|
||||
expect(mock.params).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should query for data twice if metrics data not found', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) => getIsUsingTransactionEvents({ setup, kuery: '' }),
|
||||
{
|
||||
config,
|
||||
mockResponse: (request) => {
|
||||
if (request === 'get_has_aggregated_transactions') {
|
||||
return mockResponseNoHits;
|
||||
}
|
||||
if (request === 'get_has_transactions') {
|
||||
return mockResponseSomeHits;
|
||||
}
|
||||
return mockResponseNoHits;
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(mock.spy).toHaveBeenCalledTimes(2);
|
||||
expect(mock.spy.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should be false if metrics data are found', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) => getIsUsingTransactionEvents({ setup, kuery: '' }),
|
||||
{
|
||||
config,
|
||||
mockResponse: (request) => {
|
||||
if (request === 'get_has_aggregated_transactions') {
|
||||
return mockResponseSomeHits;
|
||||
}
|
||||
if (request === 'get_has_transactions') {
|
||||
return mockResponseNoHits;
|
||||
}
|
||||
return mockResponseNoHits;
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(mock.response).toBe(false);
|
||||
});
|
||||
|
||||
it('should be true if no metrics data are found', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) => getIsUsingTransactionEvents({ setup, kuery: '' }),
|
||||
{
|
||||
config,
|
||||
mockResponse: (request) => {
|
||||
if (request === 'get_has_aggregated_transactions') {
|
||||
return mockResponseNoHits;
|
||||
}
|
||||
if (request === 'get_has_transactions') {
|
||||
return mockResponseSomeHits;
|
||||
}
|
||||
return mockResponseNoHits;
|
||||
},
|
||||
}
|
||||
);
|
||||
expect(mock.response).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false if no metrics or transactions data are found', async () => {
|
||||
mock = await inspectSearchParams(
|
||||
(setup) => getIsUsingTransactionEvents({ setup, kuery: '' }),
|
||||
{ config, mockResponse: () => mockResponseNoHits }
|
||||
);
|
||||
expect(mock.response).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 { getSearchAggregatedTransactions } from '.';
|
||||
import { SearchAggregatedTransactionSetting } from '../../../../common/aggregated_transactions';
|
||||
import { Setup, SetupTimeRange } from '../setup_request';
|
||||
import { kqlQuery, rangeQuery } from '../../../../../observability/server';
|
||||
import { ProcessorEvent } from '../../../../common/processor_event';
|
||||
import { APMEventClient } from '../create_es_client/create_apm_event_client';
|
||||
|
||||
export async function getIsUsingTransactionEvents({
|
||||
setup: { config, start, end, apmEventClient },
|
||||
kuery,
|
||||
}: {
|
||||
setup: Setup & Partial<SetupTimeRange>;
|
||||
kuery: string;
|
||||
}): Promise<boolean> {
|
||||
const searchAggregatedTransactions =
|
||||
config['xpack.apm.searchAggregatedTransactions'];
|
||||
|
||||
if (
|
||||
searchAggregatedTransactions === SearchAggregatedTransactionSetting.never
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!kuery &&
|
||||
searchAggregatedTransactions === SearchAggregatedTransactionSetting.always
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const searchesAggregatedTransactions = await getSearchAggregatedTransactions({
|
||||
config,
|
||||
start,
|
||||
end,
|
||||
apmEventClient,
|
||||
kuery,
|
||||
});
|
||||
|
||||
if (!searchesAggregatedTransactions) {
|
||||
// if no aggregrated transactions, check if any transactions at all
|
||||
return await getHasTransactions({
|
||||
start,
|
||||
end,
|
||||
apmEventClient,
|
||||
kuery,
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function getHasTransactions({
|
||||
start,
|
||||
end,
|
||||
apmEventClient,
|
||||
kuery,
|
||||
}: {
|
||||
start?: number;
|
||||
end?: number;
|
||||
apmEventClient: APMEventClient;
|
||||
kuery: string;
|
||||
}) {
|
||||
const response = await apmEventClient.search('get_has_transactions', {
|
||||
apm: {
|
||||
events: [ProcessorEvent.transaction],
|
||||
},
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
...(start && end ? rangeQuery(start, end) : []),
|
||||
...kqlQuery(kuery),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
terminateAfter: 1,
|
||||
});
|
||||
|
||||
return response.hits.total.value > 0;
|
||||
}
|
|
@ -47,11 +47,7 @@ export async function getHasAggregatedTransactions({
|
|||
}
|
||||
);
|
||||
|
||||
if (response.hits.total.value > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return response.hits.total.value > 0;
|
||||
}
|
||||
|
||||
export async function getSearchAggregatedTransactions({
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { getFallbackToTransactions } from '../lib/helpers/aggregated_transactions/get_fallback_to_transactions';
|
||||
import { getIsUsingTransactionEvents } from '../lib/helpers/aggregated_transactions/get_is_using_transaction_events';
|
||||
import { setupRequest } from '../lib/helpers/setup_request';
|
||||
import { createApmServerRoute } from './create_apm_server_route';
|
||||
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||
|
@ -26,7 +26,10 @@ const fallbackToTransactionsRoute = createApmServerRoute({
|
|||
},
|
||||
} = resources;
|
||||
return {
|
||||
fallbackToTransactions: await getFallbackToTransactions({ setup, kuery }),
|
||||
fallbackToTransactions: await getIsUsingTransactionEvents({
|
||||
setup,
|
||||
kuery,
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ interface Options {
|
|||
request: ESSearchRequest
|
||||
) => ESSearchResponse<unknown, ESSearchRequest>;
|
||||
uiFilters?: Record<string, string>;
|
||||
config?: Partial<APMConfig>;
|
||||
}
|
||||
|
||||
interface MockSetup {
|
||||
|
@ -70,7 +71,12 @@ export async function inspectSearchParams(
|
|||
config: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_, key) => {
|
||||
get: (_, key: keyof APMConfig) => {
|
||||
const { config } = options;
|
||||
if (config?.[key]) {
|
||||
return config?.[key];
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
default:
|
||||
return 'myIndex';
|
||||
|
@ -110,7 +116,7 @@ export async function inspectSearchParams(
|
|||
}
|
||||
|
||||
return {
|
||||
params: spy.mock.calls[0][1],
|
||||
params: spy.mock.calls[0]?.[1],
|
||||
response,
|
||||
error,
|
||||
spy,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue