mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[APM] Displays callout when transaction events are used instead of aggregrated metrics (#108080)
* [APM] Displays callout when transaction events are used instead of aggregrated metrics (#107477) * Apply suggestions from code review Co-authored-by: Søren Louv-Jansen <sorenlouv@gmail.com> * PR feedback, and isolates the logic for getting the fallback strategy * PR feedback Co-authored-by: Søren Louv-Jansen <sorenlouv@gmail.com>
This commit is contained in:
parent
947657118c
commit
ae73cf8416
14 changed files with 333 additions and 144 deletions
|
@ -15,6 +15,8 @@ import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_
|
|||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
|
||||
import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to_transactions_fetcher';
|
||||
import { AggregatedTransactionsCallout } from '../../shared/aggregated_transactions_callout';
|
||||
import { useUpgradeAssistantHref } from '../../shared/Links/kibana';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
import { getTimeRangeComparison } from '../../shared/time_comparison/get_time_range_comparison';
|
||||
|
@ -155,6 +157,7 @@ function useServicesFetcher() {
|
|||
|
||||
export function ServiceInventory() {
|
||||
const { core } = useApmPluginContext();
|
||||
const { fallbackToTransactions } = useFallbackToTransactionsFetcher();
|
||||
const {
|
||||
servicesData,
|
||||
servicesStatus,
|
||||
|
@ -189,6 +192,11 @@ export function ServiceInventory() {
|
|||
<MLCallout onDismiss={() => setUserHasDismissedCallout(true)} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{fallbackToTransactions && (
|
||||
<EuiFlexItem>
|
||||
<AggregatedTransactionsCallout />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<ServiceList
|
||||
isLoading={isLoading}
|
||||
|
|
|
@ -98,143 +98,9 @@ describe('ServiceInventory', () => {
|
|||
|
||||
it('should render services, when list is not empty', async () => {
|
||||
// mock rest requests
|
||||
httpGet.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: [
|
||||
{
|
||||
serviceName: 'My Python Service',
|
||||
agentName: 'python',
|
||||
transactionsPerMinute: 100,
|
||||
errorsPerMinute: 200,
|
||||
avgResponseTime: 300,
|
||||
environments: ['test', 'dev'],
|
||||
healthStatus: ServiceHealthStatus.warning,
|
||||
},
|
||||
{
|
||||
serviceName: 'My Go Service',
|
||||
agentName: 'go',
|
||||
transactionsPerMinute: 400,
|
||||
errorsPerMinute: 500,
|
||||
avgResponseTime: 600,
|
||||
environments: [],
|
||||
severity: ServiceHealthStatus.healthy,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { container, findByText } = render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1));
|
||||
await findByText('My Python Service');
|
||||
|
||||
expect(container.querySelectorAll('.euiTableRow')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should render getting started message, when list is empty and no historical data is found', async () => {
|
||||
httpGet.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: false,
|
||||
items: [],
|
||||
});
|
||||
|
||||
const { findByText } = render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1));
|
||||
|
||||
// wait for elements to be rendered
|
||||
const gettingStartedMessage = await findByText(
|
||||
"Looks like you don't have any APM services installed. Let's add some!"
|
||||
);
|
||||
|
||||
expect(gettingStartedMessage).not.toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should render empty message, when list is empty and historical data is found', async () => {
|
||||
httpGet.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: [],
|
||||
});
|
||||
|
||||
const { findByText } = render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1));
|
||||
const noServicesText = await findByText('No services found');
|
||||
|
||||
expect(noServicesText).not.toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
describe('when legacy data is found', () => {
|
||||
it('renders an upgrade migration notification', async () => {
|
||||
httpGet.mockResolvedValueOnce({
|
||||
hasLegacyData: true,
|
||||
hasHistoricalData: true,
|
||||
items: [],
|
||||
});
|
||||
|
||||
render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1));
|
||||
|
||||
expect(addWarning).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
title: 'Legacy data was detected within the selected time range',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when legacy data is not found', () => {
|
||||
it('does not render an upgrade migration notification', async () => {
|
||||
httpGet.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: [],
|
||||
});
|
||||
|
||||
render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1));
|
||||
|
||||
expect(addWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when ML data is not found', () => {
|
||||
it('does not render the health column', async () => {
|
||||
httpGet.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: [
|
||||
{
|
||||
serviceName: 'My Python Service',
|
||||
agentName: 'python',
|
||||
transactionsPerMinute: 100,
|
||||
errorsPerMinute: 200,
|
||||
avgResponseTime: 300,
|
||||
environments: ['test', 'dev'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { queryByText } = render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1));
|
||||
|
||||
expect(queryByText('Health')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when ML data is found', () => {
|
||||
it('renders the health column', async () => {
|
||||
httpGet.mockResolvedValueOnce({
|
||||
httpGet
|
||||
.mockResolvedValueOnce({ fallbackToTransactions: false })
|
||||
.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: [
|
||||
|
@ -247,13 +113,161 @@ describe('ServiceInventory', () => {
|
|||
environments: ['test', 'dev'],
|
||||
healthStatus: ServiceHealthStatus.warning,
|
||||
},
|
||||
{
|
||||
serviceName: 'My Go Service',
|
||||
agentName: 'go',
|
||||
transactionsPerMinute: 400,
|
||||
errorsPerMinute: 500,
|
||||
avgResponseTime: 600,
|
||||
environments: [],
|
||||
severity: ServiceHealthStatus.healthy,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { container, findByText } = render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2));
|
||||
await findByText('My Python Service');
|
||||
|
||||
expect(container.querySelectorAll('.euiTableRow')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should render getting started message, when list is empty and no historical data is found', async () => {
|
||||
httpGet
|
||||
.mockResolvedValueOnce({ fallbackToTransactions: false })
|
||||
.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: false,
|
||||
items: [],
|
||||
});
|
||||
|
||||
const { findByText } = render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2));
|
||||
|
||||
// wait for elements to be rendered
|
||||
const gettingStartedMessage = await findByText(
|
||||
"Looks like you don't have any APM services installed. Let's add some!"
|
||||
);
|
||||
|
||||
expect(gettingStartedMessage).not.toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should render empty message, when list is empty and historical data is found', async () => {
|
||||
httpGet
|
||||
.mockResolvedValueOnce({ fallbackToTransactions: false })
|
||||
.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: [],
|
||||
});
|
||||
|
||||
const { findByText } = render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2));
|
||||
const noServicesText = await findByText('No services found');
|
||||
|
||||
expect(noServicesText).not.toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
describe('when legacy data is found', () => {
|
||||
it('renders an upgrade migration notification', async () => {
|
||||
httpGet
|
||||
.mockResolvedValueOnce({ fallbackToTransactions: false })
|
||||
.mockResolvedValueOnce({
|
||||
hasLegacyData: true,
|
||||
hasHistoricalData: true,
|
||||
items: [],
|
||||
});
|
||||
|
||||
render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2));
|
||||
|
||||
expect(addWarning).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
title: 'Legacy data was detected within the selected time range',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when legacy data is not found', () => {
|
||||
it('does not render an upgrade migration notification', async () => {
|
||||
httpGet
|
||||
.mockResolvedValueOnce({ fallbackToTransactions: false })
|
||||
.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: [],
|
||||
});
|
||||
|
||||
render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2));
|
||||
|
||||
expect(addWarning).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when ML data is not found', () => {
|
||||
it('does not render the health column', async () => {
|
||||
httpGet
|
||||
.mockResolvedValueOnce({ fallbackToTransactions: false })
|
||||
.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: [
|
||||
{
|
||||
serviceName: 'My Python Service',
|
||||
agentName: 'python',
|
||||
transactionsPerMinute: 100,
|
||||
errorsPerMinute: 200,
|
||||
avgResponseTime: 300,
|
||||
environments: ['test', 'dev'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { queryByText } = render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2));
|
||||
|
||||
expect(queryByText('Health')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when ML data is found', () => {
|
||||
it('renders the health column', async () => {
|
||||
httpGet
|
||||
.mockResolvedValueOnce({ fallbackToTransactions: false })
|
||||
.mockResolvedValueOnce({
|
||||
hasLegacyData: false,
|
||||
hasHistoricalData: true,
|
||||
items: [
|
||||
{
|
||||
serviceName: 'My Python Service',
|
||||
agentName: 'python',
|
||||
transactionsPerMinute: 100,
|
||||
errorsPerMinute: 200,
|
||||
avgResponseTime: 300,
|
||||
environments: ['test', 'dev'],
|
||||
healthStatus: ServiceHealthStatus.warning,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { queryAllByText } = render(<ServiceInventory />, { wrapper });
|
||||
|
||||
// wait for requests to be made
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(1));
|
||||
await waitFor(() => expect(httpGet).toHaveBeenCalledTimes(2));
|
||||
|
||||
expect(queryAllByText('Health').length).toBeGreaterThan(1);
|
||||
});
|
||||
|
|
|
@ -20,6 +20,8 @@ import { ServiceOverviewErrorsTable } from './service_overview_errors_table';
|
|||
import { ServiceOverviewInstancesChartAndTable } from './service_overview_instances_chart_and_table';
|
||||
import { ServiceOverviewThroughputChart } from './service_overview_throughput_chart';
|
||||
import { TransactionsTable } from '../../shared/transactions_table';
|
||||
import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to_transactions_fetcher';
|
||||
import { AggregatedTransactionsCallout } from '../../shared/aggregated_transactions_callout';
|
||||
|
||||
/**
|
||||
* The height a chart should be if it's next to a table with 5 rows and a title.
|
||||
|
@ -28,6 +30,7 @@ import { TransactionsTable } from '../../shared/transactions_table';
|
|||
export const chartHeight = 288;
|
||||
|
||||
export function ServiceOverview() {
|
||||
const { fallbackToTransactions } = useFallbackToTransactionsFetcher();
|
||||
const { agentName, serviceName } = useApmServiceContext();
|
||||
|
||||
// The default EuiFlexGroup breaks at 768, but we want to break at 992, so we
|
||||
|
@ -41,6 +44,11 @@ export function ServiceOverview() {
|
|||
<AnnotationsContextProvider>
|
||||
<ChartPointerEventContextProvider>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{fallbackToTransactions && (
|
||||
<EuiFlexItem>
|
||||
<AggregatedTransactionsCallout />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasBorder={true}>
|
||||
<LatencyChart height={200} />
|
||||
|
|
|
@ -140,6 +140,9 @@ describe('ServiceOverview', () => {
|
|||
'GET /api/apm/services/{serviceName}/annotation/search': {
|
||||
annotations: [],
|
||||
},
|
||||
'GET /api/apm/fallback_to_transactions': {
|
||||
fallbackToTransactions: false,
|
||||
},
|
||||
};
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
|
|
|
@ -5,12 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
|
||||
import { APIReturnType } from '../../../services/rest/createCallApmApi';
|
||||
import { SearchBar } from '../../shared/search_bar';
|
||||
import { TraceList } from './trace_list';
|
||||
import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to_transactions_fetcher';
|
||||
import { AggregatedTransactionsCallout } from '../../shared/aggregated_transactions_callout';
|
||||
|
||||
type TracesAPIResponse = APIReturnType<'GET /api/apm/traces'>;
|
||||
const DEFAULT_RESPONSE: TracesAPIResponse = {
|
||||
|
@ -18,6 +21,7 @@ const DEFAULT_RESPONSE: TracesAPIResponse = {
|
|||
};
|
||||
|
||||
export function TraceOverview() {
|
||||
const { fallbackToTransactions } = useFallbackToTransactionsFetcher();
|
||||
const {
|
||||
urlParams: { environment, kuery, start, end },
|
||||
} = useUrlParams();
|
||||
|
@ -44,6 +48,14 @@ export function TraceOverview() {
|
|||
<>
|
||||
<SearchBar />
|
||||
|
||||
{fallbackToTransactions && (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<AggregatedTransactionsCallout />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
<TraceList
|
||||
items={data.items}
|
||||
isLoading={status === FETCH_STATUS.LOADING}
|
||||
|
|
|
@ -5,13 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
import { EuiPanel, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { Location } from 'history';
|
||||
import React from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
||||
import { IUrlParams } from '../../../context/url_params_context/types';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { useFallbackToTransactionsFetcher } from '../../../hooks/use_fallback_to_transactions_fetcher';
|
||||
import { AggregatedTransactionsCallout } from '../../shared/aggregated_transactions_callout';
|
||||
import { TransactionCharts } from '../../shared/charts/transaction_charts';
|
||||
import { fromQuery, toQuery } from '../../shared/Links/url_helpers';
|
||||
import { TransactionsTable } from '../../shared/transactions_table';
|
||||
|
@ -40,6 +42,7 @@ function getRedirectLocation({
|
|||
}
|
||||
|
||||
export function TransactionOverview() {
|
||||
const { fallbackToTransactions } = useFallbackToTransactionsFetcher();
|
||||
const location = useLocation();
|
||||
const { urlParams } = useUrlParams();
|
||||
const { transactionType, serviceName } = useApmServiceContext();
|
||||
|
@ -55,6 +58,16 @@ export function TransactionOverview() {
|
|||
|
||||
return (
|
||||
<>
|
||||
{fallbackToTransactions && (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<AggregatedTransactionsCallout />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
<TransactionCharts />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiPanel hasBorder={true}>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiCallOut, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export function AggregatedTransactionsCallout() {
|
||||
return (
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
title={
|
||||
<EuiText size="xs">
|
||||
{i18n.translate('xpack.apm.aggregatedTransactions.callout.title', {
|
||||
defaultMessage: `This page is using transaction event data as no metrics events were found in the current time range.`,
|
||||
})}
|
||||
</EuiText>
|
||||
}
|
||||
iconType="iInCircle"
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { useUrlParams } from '../context/url_params_context/use_url_params';
|
||||
import { useFetcher } from './use_fetcher';
|
||||
|
||||
export function useFallbackToTransactionsFetcher() {
|
||||
const {
|
||||
urlParams: { kuery, start, end },
|
||||
} = useUrlParams();
|
||||
const { data = { fallbackToTransactions: false } } = useFetcher(
|
||||
(callApmApi) => {
|
||||
return callApmApi({
|
||||
endpoint: 'GET /api/apm/fallback_to_transactions',
|
||||
params: {
|
||||
query: { kuery, start, end },
|
||||
},
|
||||
});
|
||||
},
|
||||
[kuery, start, end]
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
|
@ -12,7 +12,7 @@ import { calculateAuto } from './calculate_auto';
|
|||
export function getBucketSize({
|
||||
start,
|
||||
end,
|
||||
numBuckets = 100,
|
||||
numBuckets = 50,
|
||||
minBucketSize,
|
||||
}: {
|
||||
start: number;
|
||||
|
|
|
@ -62,7 +62,10 @@ interface SetupRequestParams {
|
|||
|
||||
type InferSetup<TParams extends SetupRequestParams> = Setup &
|
||||
(TParams extends { query: { start: number } } ? { start: number } : {}) &
|
||||
(TParams extends { query: { end: number } } ? { end: number } : {});
|
||||
(TParams extends { query: { end: number } } ? { end: number } : {}) &
|
||||
(TParams extends { query: Partial<SetupTimeRange> }
|
||||
? Partial<SetupTimeRange>
|
||||
: {});
|
||||
|
||||
export async function setupRequest<TParams extends SetupRequestParams>({
|
||||
context,
|
||||
|
|
36
x-pack/plugins/apm/server/routes/fallback_to_transactions.ts
Normal file
36
x-pack/plugins/apm/server/routes/fallback_to_transactions.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
import { getFallbackToTransactions } from '../lib/helpers/aggregated_transactions/get_fallback_to_transactions';
|
||||
import { setupRequest } from '../lib/helpers/setup_request';
|
||||
import { createApmServerRoute } from './create_apm_server_route';
|
||||
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
|
||||
import { kueryRt, rangeRt } from './default_api_types';
|
||||
|
||||
const fallbackToTransactionsRoute = createApmServerRoute({
|
||||
endpoint: 'GET /api/apm/fallback_to_transactions',
|
||||
params: t.partial({
|
||||
query: t.intersection([kueryRt, t.partial(rangeRt.props)]),
|
||||
}),
|
||||
options: { tags: ['access:apm'] },
|
||||
handler: async (resources) => {
|
||||
const setup = await setupRequest(resources);
|
||||
const {
|
||||
params: {
|
||||
query: { kuery },
|
||||
},
|
||||
} = resources;
|
||||
return {
|
||||
fallbackToTransactions: await getFallbackToTransactions({ setup, kuery }),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const fallbackToTransactionsRouteRepository = createApmServerRouteRepository().add(
|
||||
fallbackToTransactionsRoute
|
||||
);
|
|
@ -21,6 +21,7 @@ import { indexPatternRouteRepository } from './index_pattern';
|
|||
import { metricsRouteRepository } from './metrics';
|
||||
import { observabilityOverviewRouteRepository } from './observability_overview';
|
||||
import { rumRouteRepository } from './rum_client';
|
||||
import { fallbackToTransactionsRouteRepository } from './fallback_to_transactions';
|
||||
import { serviceRouteRepository } from './services';
|
||||
import { serviceMapRouteRepository } from './service_map';
|
||||
import { serviceNodeRouteRepository } from './service_nodes';
|
||||
|
@ -54,7 +55,8 @@ const getTypedGlobalApmServerRouteRepository = () => {
|
|||
.merge(customLinkRouteRepository)
|
||||
.merge(sourceMapsRouteRepository)
|
||||
.merge(apmFleetRouteRepository)
|
||||
.merge(backendsRouteRepository);
|
||||
.merge(backendsRouteRepository)
|
||||
.merge(fallbackToTransactionsRouteRepository);
|
||||
|
||||
return repository;
|
||||
};
|
||||
|
|
|
@ -368,7 +368,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
.toMatchInline(`
|
||||
Array [
|
||||
0,
|
||||
15,
|
||||
3,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
@ -397,7 +397,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
.toMatchInline(`
|
||||
Array [
|
||||
0,
|
||||
187.5,
|
||||
37.5,
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue