[APM] Break down transaction table api removing the sparklines (#88946)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Dario Gieselaar <dario.gieselaar@elastic.co>
This commit is contained in:
parent
6406e99642
commit
2fcf2a91cc
|
@ -80,21 +80,21 @@ describe('ServiceOverview', () => {
|
|||
status: FETCH_STATUS.SUCCESS,
|
||||
});
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
const calls = {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'GET /api/apm/services/{serviceName}/error_groups': {
|
||||
error_groups: [],
|
||||
total_error_groups: 0,
|
||||
},
|
||||
'GET /api/apm/services/{serviceName}/transactions/groups/overview': {
|
||||
'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics': {
|
||||
transactionGroups: [],
|
||||
totalTransactionGroups: 0,
|
||||
isAggregationAccurate: true,
|
||||
},
|
||||
'GET /api/apm/services/{serviceName}/dependencies': [],
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'GET /api/apm/services/{serviceName}/service_overview_instances': [],
|
||||
};
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
jest
|
||||
.spyOn(callApmApiModule, 'createCallApmApi')
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* 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 { EuiBasicTableColumn } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { ValuesType } from 'utility-types';
|
||||
import {
|
||||
asMillisecondDuration,
|
||||
asPercent,
|
||||
asTransactionRate,
|
||||
} from '../../../../../common/utils/formatters';
|
||||
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
|
||||
import { px, unit } from '../../../../style/variables';
|
||||
import { SparkPlot } from '../../../shared/charts/spark_plot';
|
||||
import { ImpactBar } from '../../../shared/ImpactBar';
|
||||
import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link';
|
||||
import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip';
|
||||
|
||||
type TransactionGroupPrimaryStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics'>;
|
||||
|
||||
type ServiceTransactionGroupItem = ValuesType<
|
||||
TransactionGroupPrimaryStatistics['transactionGroups']
|
||||
>;
|
||||
type TransactionGroupComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics'>;
|
||||
|
||||
function getLatencyAggregationTypeLabel(latencyAggregationType?: string) {
|
||||
switch (latencyAggregationType) {
|
||||
case 'avg':
|
||||
return i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnLatency.avg',
|
||||
{ defaultMessage: 'Latency (avg.)' }
|
||||
);
|
||||
|
||||
case 'p95':
|
||||
return i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnLatency.p95',
|
||||
{ defaultMessage: 'Latency (95th)' }
|
||||
);
|
||||
|
||||
case 'p99':
|
||||
return i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnLatency.p99',
|
||||
{ defaultMessage: 'Latency (99th)' }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function getColumns({
|
||||
serviceName,
|
||||
latencyAggregationType,
|
||||
transactionGroupComparisonStatistics,
|
||||
}: {
|
||||
serviceName: string;
|
||||
latencyAggregationType?: string;
|
||||
transactionGroupComparisonStatistics?: TransactionGroupComparisonStatistics;
|
||||
}): Array<EuiBasicTableColumn<ServiceTransactionGroupItem>> {
|
||||
return [
|
||||
{
|
||||
field: 'name',
|
||||
sortable: true,
|
||||
name: i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnName',
|
||||
{ defaultMessage: 'Name' }
|
||||
),
|
||||
render: (_, { name, transactionType: type }) => {
|
||||
return (
|
||||
<TruncateWithTooltip
|
||||
text={name}
|
||||
content={
|
||||
<TransactionDetailLink
|
||||
serviceName={serviceName}
|
||||
transactionName={name}
|
||||
transactionType={type}
|
||||
latencyAggregationType={latencyAggregationType}
|
||||
>
|
||||
{name}
|
||||
</TransactionDetailLink>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'latency',
|
||||
sortable: true,
|
||||
name: getLatencyAggregationTypeLabel(latencyAggregationType),
|
||||
width: px(unit * 10),
|
||||
render: (_, { latency, name }) => {
|
||||
const timeseries =
|
||||
transactionGroupComparisonStatistics?.[name]?.latency;
|
||||
return (
|
||||
<SparkPlot
|
||||
color="euiColorVis1"
|
||||
compact
|
||||
series={timeseries}
|
||||
valueLabel={asMillisecondDuration(latency)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'throughput',
|
||||
sortable: true,
|
||||
name: i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnThroughput',
|
||||
{ defaultMessage: 'Throughput' }
|
||||
),
|
||||
width: px(unit * 10),
|
||||
render: (_, { throughput, name }) => {
|
||||
const timeseries =
|
||||
transactionGroupComparisonStatistics?.[name]?.throughput;
|
||||
return (
|
||||
<SparkPlot
|
||||
color="euiColorVis0"
|
||||
compact
|
||||
series={timeseries}
|
||||
valueLabel={asTransactionRate(throughput)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'errorRate',
|
||||
sortable: true,
|
||||
name: i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnErrorRate',
|
||||
{ defaultMessage: 'Error rate' }
|
||||
),
|
||||
width: px(unit * 8),
|
||||
render: (_, { errorRate, name }) => {
|
||||
const timeseries =
|
||||
transactionGroupComparisonStatistics?.[name]?.errorRate;
|
||||
return (
|
||||
<SparkPlot
|
||||
color="euiColorVis7"
|
||||
compact
|
||||
series={timeseries}
|
||||
valueLabel={asPercent(errorRate, 1)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'impact',
|
||||
sortable: true,
|
||||
name: i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnImpact',
|
||||
{ defaultMessage: 'Impact' }
|
||||
),
|
||||
width: px(unit * 5),
|
||||
render: (_, { name }) => {
|
||||
const impact =
|
||||
transactionGroupComparisonStatistics?.[name]?.impact ?? 0;
|
||||
return <ImpactBar value={impact} size="m" />;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
|
@ -7,86 +7,41 @@
|
|||
|
||||
import {
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { orderBy } from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import { ValuesType } from 'utility-types';
|
||||
import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types';
|
||||
import {
|
||||
asMillisecondDuration,
|
||||
asPercent,
|
||||
asTransactionRate,
|
||||
} from '../../../../../common/utils/formatters';
|
||||
import uuid from 'uuid';
|
||||
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
|
||||
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 { px, unit } from '../../../../style/variables';
|
||||
import { SparkPlot } from '../../../shared/charts/spark_plot';
|
||||
import { ImpactBar } from '../../../shared/ImpactBar';
|
||||
import { TransactionDetailLink } from '../../../shared/Links/apm/transaction_detail_link';
|
||||
import { TransactionOverviewLink } from '../../../shared/Links/apm/transaction_overview_link';
|
||||
import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper';
|
||||
import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip';
|
||||
import { ServiceOverviewTableContainer } from '../service_overview_table_container';
|
||||
|
||||
type ServiceTransactionGroupItem = ValuesType<
|
||||
APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/overview'>['transactionGroups']
|
||||
>;
|
||||
import { getColumns } from './get_columns';
|
||||
|
||||
interface Props {
|
||||
serviceName: string;
|
||||
}
|
||||
|
||||
const INITIAL_STATE = {
|
||||
transactionGroups: [],
|
||||
isAggregationAccurate: true,
|
||||
requestId: '',
|
||||
};
|
||||
|
||||
type SortField = 'name' | 'latency' | 'throughput' | 'errorRate' | 'impact';
|
||||
type SortDirection = 'asc' | 'desc';
|
||||
|
||||
const PAGE_SIZE = 5;
|
||||
const DEFAULT_SORT = {
|
||||
direction: 'desc' as const,
|
||||
field: 'impact' as const,
|
||||
};
|
||||
|
||||
function getLatencyAggregationTypeLabel(latencyAggregationType?: string) {
|
||||
switch (latencyAggregationType) {
|
||||
case 'avg':
|
||||
return i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnLatency.avg',
|
||||
{
|
||||
defaultMessage: 'Latency (avg.)',
|
||||
}
|
||||
);
|
||||
|
||||
case 'p95':
|
||||
return i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnLatency.p95',
|
||||
{
|
||||
defaultMessage: 'Latency (95th)',
|
||||
}
|
||||
);
|
||||
|
||||
case 'p99':
|
||||
return i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnLatency.p99',
|
||||
{
|
||||
defaultMessage: 'Latency (99th)',
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function ServiceOverviewTransactionsTable(props: Props) {
|
||||
const { serviceName } = props;
|
||||
const { transactionType } = useApmServiceContext();
|
||||
const {
|
||||
uiFilters,
|
||||
urlParams: { start, end, latencyAggregationType },
|
||||
} = useUrlParams();
|
||||
|
||||
export function ServiceOverviewTransactionsTable({ serviceName }: Props) {
|
||||
const [tableOptions, setTableOptions] = useState<{
|
||||
pageIndex: number;
|
||||
sort: {
|
||||
|
@ -98,51 +53,36 @@ export function ServiceOverviewTransactionsTable(props: Props) {
|
|||
sort: DEFAULT_SORT,
|
||||
});
|
||||
|
||||
const { pageIndex, sort } = tableOptions;
|
||||
|
||||
const { transactionType } = useApmServiceContext();
|
||||
const {
|
||||
data = {
|
||||
totalItemCount: 0,
|
||||
items: [],
|
||||
tableOptions: {
|
||||
pageIndex: 0,
|
||||
sort: DEFAULT_SORT,
|
||||
},
|
||||
},
|
||||
status,
|
||||
} = useFetcher(
|
||||
uiFilters,
|
||||
urlParams: { start, end, latencyAggregationType },
|
||||
} = useUrlParams();
|
||||
|
||||
const { data = INITIAL_STATE, status } = useFetcher(
|
||||
(callApmApi) => {
|
||||
if (!start || !end || !latencyAggregationType || !transactionType) {
|
||||
return;
|
||||
}
|
||||
|
||||
return callApmApi({
|
||||
endpoint:
|
||||
'GET /api/apm/services/{serviceName}/transactions/groups/overview',
|
||||
'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: JSON.stringify(uiFilters),
|
||||
size: PAGE_SIZE,
|
||||
numBuckets: 20,
|
||||
pageIndex: tableOptions.pageIndex,
|
||||
sortField: tableOptions.sort.field,
|
||||
sortDirection: tableOptions.sort.direction,
|
||||
transactionType,
|
||||
latencyAggregationType: latencyAggregationType as LatencyAggregationType,
|
||||
latencyAggregationType,
|
||||
},
|
||||
},
|
||||
}).then((response) => {
|
||||
return {
|
||||
items: response.transactionGroups,
|
||||
totalItemCount: response.totalTransactionGroups,
|
||||
tableOptions: {
|
||||
pageIndex: tableOptions.pageIndex,
|
||||
sort: {
|
||||
field: tableOptions.sort.field,
|
||||
direction: tableOptions.sort.direction,
|
||||
},
|
||||
},
|
||||
requestId: uuid(),
|
||||
...response,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
@ -151,114 +91,81 @@ export function ServiceOverviewTransactionsTable(props: Props) {
|
|||
start,
|
||||
end,
|
||||
uiFilters,
|
||||
tableOptions.pageIndex,
|
||||
tableOptions.sort.field,
|
||||
tableOptions.sort.direction,
|
||||
transactionType,
|
||||
latencyAggregationType,
|
||||
]
|
||||
);
|
||||
|
||||
const {
|
||||
items,
|
||||
totalItemCount,
|
||||
tableOptions: { pageIndex, sort },
|
||||
} = data;
|
||||
const { transactionGroups, requestId } = data;
|
||||
const currentPageTransactionGroups = orderBy(
|
||||
transactionGroups,
|
||||
sort.field,
|
||||
sort.direction
|
||||
).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE);
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<ServiceTransactionGroupItem>> = [
|
||||
{
|
||||
field: 'name',
|
||||
name: i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnName',
|
||||
{
|
||||
defaultMessage: 'Name',
|
||||
}
|
||||
),
|
||||
render: (_, { name, transactionType: type }) => {
|
||||
return (
|
||||
<TruncateWithTooltip
|
||||
text={name}
|
||||
content={
|
||||
<TransactionDetailLink
|
||||
serviceName={serviceName}
|
||||
transactionName={name}
|
||||
transactionType={type}
|
||||
latencyAggregationType={latencyAggregationType}
|
||||
>
|
||||
{name}
|
||||
</TransactionDetailLink>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
const transactionNames = JSON.stringify(
|
||||
currentPageTransactionGroups.map(({ name }) => name).sort()
|
||||
);
|
||||
|
||||
const {
|
||||
data: transactionGroupComparisonStatistics,
|
||||
status: transactionGroupComparisonStatisticsStatus,
|
||||
} = useFetcher(
|
||||
(callApmApi) => {
|
||||
if (
|
||||
currentPageTransactionGroups.length &&
|
||||
start &&
|
||||
end &&
|
||||
transactionType &&
|
||||
latencyAggregationType
|
||||
) {
|
||||
return callApmApi({
|
||||
endpoint:
|
||||
'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: JSON.stringify(uiFilters),
|
||||
numBuckets: 20,
|
||||
transactionType,
|
||||
latencyAggregationType,
|
||||
transactionNames,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'latency',
|
||||
name: getLatencyAggregationTypeLabel(latencyAggregationType),
|
||||
width: px(unit * 10),
|
||||
render: (_, { latency }) => {
|
||||
return (
|
||||
<SparkPlot
|
||||
color="euiColorVis1"
|
||||
compact
|
||||
series={latency.timeseries ?? undefined}
|
||||
valueLabel={asMillisecondDuration(latency.value)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
// only fetches statistics when requestId changes or transaction names changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[requestId, transactionNames],
|
||||
{ preservePreviousData: false }
|
||||
);
|
||||
|
||||
const columns = getColumns({
|
||||
serviceName,
|
||||
latencyAggregationType,
|
||||
transactionGroupComparisonStatistics,
|
||||
});
|
||||
|
||||
const isLoading =
|
||||
status === FETCH_STATUS.LOADING ||
|
||||
transactionGroupComparisonStatisticsStatus === FETCH_STATUS.LOADING;
|
||||
|
||||
const pagination = {
|
||||
pageIndex,
|
||||
pageSize: PAGE_SIZE,
|
||||
totalItemCount: transactionGroups.length,
|
||||
hidePerPageOptions: true,
|
||||
};
|
||||
|
||||
const sorting = {
|
||||
sort: {
|
||||
field: sort.field,
|
||||
direction: sort.direction,
|
||||
},
|
||||
{
|
||||
field: 'throughput',
|
||||
name: i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnThroughput',
|
||||
{ defaultMessage: 'Throughput' }
|
||||
),
|
||||
width: px(unit * 10),
|
||||
render: (_, { throughput }) => {
|
||||
return (
|
||||
<SparkPlot
|
||||
color="euiColorVis0"
|
||||
compact
|
||||
series={throughput.timeseries ?? undefined}
|
||||
valueLabel={asTransactionRate(throughput.value)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'errorRate',
|
||||
name: i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnErrorRate',
|
||||
{
|
||||
defaultMessage: 'Error rate',
|
||||
}
|
||||
),
|
||||
width: px(unit * 8),
|
||||
render: (_, { errorRate }) => {
|
||||
return (
|
||||
<SparkPlot
|
||||
color="euiColorVis7"
|
||||
compact
|
||||
series={errorRate.timeseries ?? undefined}
|
||||
valueLabel={asPercent(errorRate.value, 1)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'impact',
|
||||
name: i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableColumnImpact',
|
||||
{
|
||||
defaultMessage: 'Impact',
|
||||
}
|
||||
),
|
||||
width: px(unit * 5),
|
||||
render: (_, { impact }) => {
|
||||
return <ImpactBar value={impact ?? 0} size="m" />;
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
|
@ -295,21 +202,14 @@ export function ServiceOverviewTransactionsTable(props: Props) {
|
|||
<EuiFlexItem>
|
||||
<TableFetchWrapper status={status}>
|
||||
<ServiceOverviewTableContainer
|
||||
isEmptyAndLoading={
|
||||
items.length === 0 && status === FETCH_STATUS.LOADING
|
||||
}
|
||||
isEmptyAndLoading={transactionGroups.length === 0 && isLoading}
|
||||
>
|
||||
<EuiBasicTable
|
||||
loading={isLoading}
|
||||
items={currentPageTransactionGroups}
|
||||
columns={columns}
|
||||
items={items}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize: PAGE_SIZE,
|
||||
totalItemCount,
|
||||
pageSizeOptions: [PAGE_SIZE],
|
||||
hidePerPageOptions: true,
|
||||
}}
|
||||
loading={status === FETCH_STATUS.LOADING}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
onChange={(newTableOptions: {
|
||||
page?: {
|
||||
index: number;
|
||||
|
@ -326,13 +226,6 @@ export function ServiceOverviewTransactionsTable(props: Props) {
|
|||
: DEFAULT_SORT,
|
||||
});
|
||||
}}
|
||||
sorting={{
|
||||
enableAllColumns: true,
|
||||
sort: {
|
||||
direction: sort.direction,
|
||||
field: sort.field,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ServiceOverviewTableContainer>
|
||||
</TableFetchWrapper>
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
Settings,
|
||||
} from '@elastic/charts';
|
||||
import { merge } from 'lodash';
|
||||
import { Coordinate } from '../../../../../typings/timeseries';
|
||||
import { useChartTheme } from '../../../../../../observability/public';
|
||||
import { px, unit } from '../../../../style/variables';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
|
@ -39,7 +40,7 @@ export function SparkPlot({
|
|||
compact,
|
||||
}: {
|
||||
color: Color;
|
||||
series?: Array<{ x: number; y: number | null }> | null;
|
||||
series?: Coordinate[] | null;
|
||||
valueLabel: React.ReactNode;
|
||||
compact?: boolean;
|
||||
}) {
|
||||
|
@ -58,18 +59,18 @@ export function SparkPlot({
|
|||
|
||||
const colorValue = theme.eui[color];
|
||||
|
||||
const chartSize = {
|
||||
height: px(24),
|
||||
width: compact ? px(unit * 3) : px(unit * 4),
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="m" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
{!series || series.every((point) => point.y === null) ? (
|
||||
<EuiIcon type="visLine" color="subdued" />
|
||||
<EuiIcon type="visLine" color="subdued" style={chartSize} />
|
||||
) : (
|
||||
<Chart
|
||||
size={{
|
||||
height: px(24),
|
||||
width: compact ? px(unit * 3) : px(unit * 4),
|
||||
}}
|
||||
>
|
||||
<Chart size={chartSize}>
|
||||
<Settings
|
||||
theme={sparkplotChartTheme}
|
||||
showLegend={false}
|
||||
|
|
|
@ -80,7 +80,7 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) {
|
|||
transactionType,
|
||||
searchTerm: toString(searchTerm),
|
||||
percentile: toNumber(percentile),
|
||||
latencyAggregationType,
|
||||
latencyAggregationType: latencyAggregationType as LatencyAggregationType,
|
||||
comparisonEnabled: comparisonEnabled
|
||||
? toBoolean(comparisonEnabled)
|
||||
: undefined,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LatencyAggregationType } from '../../../common/latency_aggregation_types';
|
||||
import { LocalUIFilterName } from '../../../common/ui_filter';
|
||||
|
||||
export type IUrlParams = {
|
||||
|
@ -29,7 +30,7 @@ export type IUrlParams = {
|
|||
pageSize?: number;
|
||||
searchTerm?: string;
|
||||
percentile?: number;
|
||||
latencyAggregationType?: string;
|
||||
latencyAggregationType?: LatencyAggregationType;
|
||||
comparisonEnabled?: boolean;
|
||||
comparisonType?: string;
|
||||
} & Partial<Record<LocalUIFilterName, string>>;
|
||||
|
|
|
@ -12,7 +12,6 @@ import { useUrlParams } from '../context/url_params_context/use_url_params';
|
|||
import { useApmServiceContext } from '../context/apm_service/use_apm_service_context';
|
||||
import { getLatencyChartSelector } from '../selectors/latency_chart_selectors';
|
||||
import { useTheme } from './use_theme';
|
||||
import { LatencyAggregationType } from '../../common/latency_aggregation_types';
|
||||
|
||||
export function useTransactionLatencyChartsFetcher() {
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
|
@ -43,7 +42,7 @@ export function useTransactionLatencyChartsFetcher() {
|
|||
transactionType,
|
||||
transactionName,
|
||||
uiFilters: JSON.stringify(uiFilters),
|
||||
latencyAggregationType: latencyAggregationType as LatencyAggregationType,
|
||||
latencyAggregationType,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* 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 { keyBy } from 'lodash';
|
||||
import {
|
||||
EVENT_OUTCOME,
|
||||
SERVICE_NAME,
|
||||
TRANSACTION_NAME,
|
||||
TRANSACTION_TYPE,
|
||||
} from '../../../common/elasticsearch_fieldnames';
|
||||
import { EventOutcome } from '../../../common/event_outcome';
|
||||
import { LatencyAggregationType } from '../../../common/latency_aggregation_types';
|
||||
import { rangeFilter } from '../../../common/utils/range_filter';
|
||||
import { Coordinate } from '../../../typings/timeseries';
|
||||
import {
|
||||
getDocumentTypeFilterForAggregatedTransactions,
|
||||
getProcessorEventForAggregatedTransactions,
|
||||
getTransactionDurationFieldForAggregatedTransactions,
|
||||
} from '../helpers/aggregated_transactions';
|
||||
import { getBucketSize } from '../helpers/get_bucket_size';
|
||||
import {
|
||||
getLatencyAggregation,
|
||||
getLatencyValue,
|
||||
} from '../helpers/latency_aggregation_type';
|
||||
import { Setup, SetupTimeRange } from '../helpers/setup_request';
|
||||
import { calculateTransactionErrorPercentage } from '../helpers/transaction_error_rate';
|
||||
|
||||
export async function getServiceTransactionGroupComparisonStatistics({
|
||||
serviceName,
|
||||
transactionNames,
|
||||
setup,
|
||||
numBuckets,
|
||||
searchAggregatedTransactions,
|
||||
transactionType,
|
||||
latencyAggregationType,
|
||||
}: {
|
||||
serviceName: string;
|
||||
transactionNames: string[];
|
||||
setup: Setup & SetupTimeRange;
|
||||
numBuckets: number;
|
||||
searchAggregatedTransactions: boolean;
|
||||
transactionType: string;
|
||||
latencyAggregationType: LatencyAggregationType;
|
||||
}): Promise<
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
latency: Coordinate[];
|
||||
throughput: Coordinate[];
|
||||
errorRate: Coordinate[];
|
||||
impact: number;
|
||||
}
|
||||
>
|
||||
> {
|
||||
const { apmEventClient, start, end, esFilter } = setup;
|
||||
const { intervalString } = getBucketSize({ start, end, numBuckets });
|
||||
|
||||
const field = getTransactionDurationFieldForAggregatedTransactions(
|
||||
searchAggregatedTransactions
|
||||
);
|
||||
|
||||
const response = await apmEventClient.search({
|
||||
apm: {
|
||||
events: [
|
||||
getProcessorEventForAggregatedTransactions(
|
||||
searchAggregatedTransactions
|
||||
),
|
||||
],
|
||||
},
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [TRANSACTION_TYPE]: transactionType } },
|
||||
{ range: rangeFilter(start, end) },
|
||||
...getDocumentTypeFilterForAggregatedTransactions(
|
||||
searchAggregatedTransactions
|
||||
),
|
||||
...esFilter,
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
total_duration: { sum: { field } },
|
||||
transaction_groups: {
|
||||
terms: {
|
||||
field: TRANSACTION_NAME,
|
||||
include: transactionNames,
|
||||
size: transactionNames.length,
|
||||
},
|
||||
aggs: {
|
||||
transaction_group_total_duration: {
|
||||
sum: { field },
|
||||
},
|
||||
timeseries: {
|
||||
date_histogram: {
|
||||
field: '@timestamp',
|
||||
fixed_interval: intervalString,
|
||||
min_doc_count: 0,
|
||||
extended_bounds: {
|
||||
min: start,
|
||||
max: end,
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
throughput_rate: {
|
||||
rate: {
|
||||
unit: 'minute',
|
||||
},
|
||||
},
|
||||
...getLatencyAggregation(latencyAggregationType, field),
|
||||
[EVENT_OUTCOME]: {
|
||||
terms: {
|
||||
field: EVENT_OUTCOME,
|
||||
include: [EventOutcome.failure, EventOutcome.success],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const buckets = response.aggregations?.transaction_groups.buckets ?? [];
|
||||
|
||||
const totalDuration = response.aggregations?.total_duration.value;
|
||||
return keyBy(
|
||||
buckets.map((bucket) => {
|
||||
const transactionName = bucket.key;
|
||||
const latency = bucket.timeseries.buckets.map((timeseriesBucket) => ({
|
||||
x: timeseriesBucket.key,
|
||||
y: getLatencyValue({
|
||||
latencyAggregationType,
|
||||
aggregation: timeseriesBucket.latency,
|
||||
}),
|
||||
}));
|
||||
const throughput = bucket.timeseries.buckets.map((timeseriesBucket) => ({
|
||||
x: timeseriesBucket.key,
|
||||
y: timeseriesBucket.throughput_rate.value,
|
||||
}));
|
||||
const errorRate = bucket.timeseries.buckets.map((timeseriesBucket) => ({
|
||||
x: timeseriesBucket.key,
|
||||
y: calculateTransactionErrorPercentage(timeseriesBucket[EVENT_OUTCOME]),
|
||||
}));
|
||||
const transactionGroupTotalDuration =
|
||||
bucket.transaction_group_total_duration.value || 0;
|
||||
return {
|
||||
transactionName,
|
||||
latency,
|
||||
throughput,
|
||||
errorRate,
|
||||
impact: totalDuration
|
||||
? (transactionGroupTotalDuration * 100) / totalDuration
|
||||
: 0,
|
||||
};
|
||||
}),
|
||||
'transactionName'
|
||||
);
|
||||
}
|
|
@ -5,29 +5,27 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { orderBy } from 'lodash';
|
||||
import { ValuesType } from 'utility-types';
|
||||
import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
|
||||
import { PromiseReturnType } from '../../../../../observability/typings/common';
|
||||
import { EventOutcome } from '../../../../common/event_outcome';
|
||||
import { ESFilter } from '../../../../../../typings/elasticsearch';
|
||||
import { rangeFilter } from '../../../../common/utils/range_filter';
|
||||
import {
|
||||
EVENT_OUTCOME,
|
||||
SERVICE_NAME,
|
||||
TRANSACTION_NAME,
|
||||
TRANSACTION_TYPE,
|
||||
} from '../../../../common/elasticsearch_fieldnames';
|
||||
} from '../../../common/elasticsearch_fieldnames';
|
||||
import { EventOutcome } from '../../../common/event_outcome';
|
||||
import { LatencyAggregationType } from '../../../common/latency_aggregation_types';
|
||||
import { rangeFilter } from '../../../common/utils/range_filter';
|
||||
import {
|
||||
getDocumentTypeFilterForAggregatedTransactions,
|
||||
getProcessorEventForAggregatedTransactions,
|
||||
getTransactionDurationFieldForAggregatedTransactions,
|
||||
} from '../../helpers/aggregated_transactions';
|
||||
import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client';
|
||||
} from '../helpers/aggregated_transactions';
|
||||
import { calculateThroughput } from '../helpers/calculate_throughput';
|
||||
import {
|
||||
getLatencyAggregation,
|
||||
getLatencyValue,
|
||||
} from '../../helpers/latency_aggregation_type';
|
||||
import { calculateThroughput } from '../../helpers/calculate_throughput';
|
||||
} from '../helpers/latency_aggregation_type';
|
||||
import { Setup, SetupTimeRange } from '../helpers/setup_request';
|
||||
import { calculateTransactionErrorPercentage } from '../helpers/transaction_error_rate';
|
||||
|
||||
export type ServiceOverviewTransactionGroupSortField =
|
||||
| 'name'
|
||||
|
@ -36,37 +34,21 @@ export type ServiceOverviewTransactionGroupSortField =
|
|||
| 'errorRate'
|
||||
| 'impact';
|
||||
|
||||
export type TransactionGroupWithoutTimeseriesData = ValuesType<
|
||||
PromiseReturnType<typeof getTransactionGroupsForPage>['transactionGroups']
|
||||
>;
|
||||
|
||||
export async function getTransactionGroupsForPage({
|
||||
apmEventClient,
|
||||
searchAggregatedTransactions,
|
||||
export async function getServiceTransactionGroups({
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
esFilter,
|
||||
sortField,
|
||||
sortDirection,
|
||||
pageIndex,
|
||||
size,
|
||||
setup,
|
||||
searchAggregatedTransactions,
|
||||
transactionType,
|
||||
latencyAggregationType,
|
||||
}: {
|
||||
apmEventClient: APMEventClient;
|
||||
searchAggregatedTransactions: boolean;
|
||||
serviceName: string;
|
||||
start: number;
|
||||
end: number;
|
||||
esFilter: ESFilter[];
|
||||
sortField: ServiceOverviewTransactionGroupSortField;
|
||||
sortDirection: 'asc' | 'desc';
|
||||
pageIndex: number;
|
||||
size: number;
|
||||
setup: Setup & SetupTimeRange;
|
||||
searchAggregatedTransactions: boolean;
|
||||
transactionType: string;
|
||||
latencyAggregationType: LatencyAggregationType;
|
||||
}) {
|
||||
const { apmEventClient, start, end, esFilter } = setup;
|
||||
|
||||
const field = getTransactionDurationFieldForAggregatedTransactions(
|
||||
searchAggregatedTransactions
|
||||
);
|
||||
|
@ -87,11 +69,15 @@ export async function getTransactionGroupsForPage({
|
|||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [TRANSACTION_TYPE]: transactionType } },
|
||||
{ range: rangeFilter(start, end) },
|
||||
...getDocumentTypeFilterForAggregatedTransactions(
|
||||
searchAggregatedTransactions
|
||||
),
|
||||
...esFilter,
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
total_duration: { sum: { field } },
|
||||
transaction_groups: {
|
||||
terms: {
|
||||
field: TRANSACTION_NAME,
|
||||
|
@ -99,9 +85,15 @@ export async function getTransactionGroupsForPage({
|
|||
order: { _count: 'desc' },
|
||||
},
|
||||
aggs: {
|
||||
transaction_group_total_duration: {
|
||||
sum: { field },
|
||||
},
|
||||
...getLatencyAggregation(latencyAggregationType, field),
|
||||
[EVENT_OUTCOME]: {
|
||||
filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } },
|
||||
terms: {
|
||||
field: EVENT_OUTCOME,
|
||||
include: [EventOutcome.failure, EventOutcome.success],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -109,12 +101,16 @@ export async function getTransactionGroupsForPage({
|
|||
},
|
||||
});
|
||||
|
||||
const totalDuration = response.aggregations?.total_duration.value;
|
||||
|
||||
const transactionGroups =
|
||||
response.aggregations?.transaction_groups.buckets.map((bucket) => {
|
||||
const errorRate =
|
||||
bucket.doc_count > 0
|
||||
? bucket[EVENT_OUTCOME].doc_count / bucket.doc_count
|
||||
: null;
|
||||
const errorRate = calculateTransactionErrorPercentage(
|
||||
bucket[EVENT_OUTCOME]
|
||||
);
|
||||
|
||||
const transactionGroupTotalDuration =
|
||||
bucket.transaction_group_total_duration.value || 0;
|
||||
|
||||
return {
|
||||
name: bucket.key as string,
|
||||
|
@ -128,36 +124,17 @@ export async function getTransactionGroupsForPage({
|
|||
value: bucket.doc_count,
|
||||
}),
|
||||
errorRate,
|
||||
impact: totalDuration
|
||||
? (transactionGroupTotalDuration * 100) / totalDuration
|
||||
: 0,
|
||||
};
|
||||
}) ?? [];
|
||||
|
||||
const totalDurationValues = transactionGroups.map(
|
||||
(group) => (group.latency ?? 0) * group.throughput
|
||||
);
|
||||
|
||||
const minTotalDuration = Math.min(...totalDurationValues);
|
||||
const maxTotalDuration = Math.max(...totalDurationValues);
|
||||
|
||||
const transactionGroupsWithImpact = transactionGroups.map((group) => ({
|
||||
...group,
|
||||
impact:
|
||||
(((group.latency ?? 0) * group.throughput - minTotalDuration) /
|
||||
(maxTotalDuration - minTotalDuration)) *
|
||||
100,
|
||||
}));
|
||||
|
||||
// Sort transaction groups first, and only get timeseries for data in view.
|
||||
// This is to limit the possibility of creating too many buckets.
|
||||
|
||||
const sortedAndSlicedTransactionGroups = orderBy(
|
||||
transactionGroupsWithImpact,
|
||||
sortField,
|
||||
[sortDirection]
|
||||
).slice(pageIndex * size, pageIndex * size + size);
|
||||
|
||||
return {
|
||||
transactionGroups: sortedAndSlicedTransactionGroups,
|
||||
totalTransactionGroups: transactionGroups.length,
|
||||
transactionGroups: transactionGroups.map((transactionGroup) => ({
|
||||
...transactionGroup,
|
||||
transactionType,
|
||||
})),
|
||||
isAggregationAccurate:
|
||||
(response.aggregations?.transaction_groups.sum_other_doc_count ?? 0) ===
|
||||
0,
|
|
@ -1,119 +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 { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
|
||||
import { PromiseReturnType } from '../../../../../observability/typings/common';
|
||||
import { EventOutcome } from '../../../../common/event_outcome';
|
||||
import { rangeFilter } from '../../../../common/utils/range_filter';
|
||||
import {
|
||||
EVENT_OUTCOME,
|
||||
SERVICE_NAME,
|
||||
TRANSACTION_NAME,
|
||||
TRANSACTION_TYPE,
|
||||
} from '../../../../common/elasticsearch_fieldnames';
|
||||
|
||||
import { ESFilter } from '../../../../../../typings/elasticsearch';
|
||||
import {
|
||||
getDocumentTypeFilterForAggregatedTransactions,
|
||||
getProcessorEventForAggregatedTransactions,
|
||||
getTransactionDurationFieldForAggregatedTransactions,
|
||||
} from '../../helpers/aggregated_transactions';
|
||||
import { APMEventClient } from '../../helpers/create_es_client/create_apm_event_client';
|
||||
import { getBucketSize } from '../../helpers/get_bucket_size';
|
||||
import { getLatencyAggregation } from '../../helpers/latency_aggregation_type';
|
||||
|
||||
export type TransactionGroupTimeseriesData = PromiseReturnType<
|
||||
typeof getTimeseriesDataForTransactionGroups
|
||||
>;
|
||||
|
||||
export async function getTimeseriesDataForTransactionGroups({
|
||||
apmEventClient,
|
||||
start,
|
||||
end,
|
||||
serviceName,
|
||||
transactionNames,
|
||||
esFilter,
|
||||
searchAggregatedTransactions,
|
||||
size,
|
||||
numBuckets,
|
||||
transactionType,
|
||||
latencyAggregationType,
|
||||
}: {
|
||||
apmEventClient: APMEventClient;
|
||||
start: number;
|
||||
end: number;
|
||||
serviceName: string;
|
||||
transactionNames: string[];
|
||||
esFilter: ESFilter[];
|
||||
searchAggregatedTransactions: boolean;
|
||||
size: number;
|
||||
numBuckets: number;
|
||||
transactionType: string;
|
||||
latencyAggregationType: LatencyAggregationType;
|
||||
}) {
|
||||
const { intervalString } = getBucketSize({ start, end, numBuckets });
|
||||
|
||||
const field = getTransactionDurationFieldForAggregatedTransactions(
|
||||
searchAggregatedTransactions
|
||||
);
|
||||
|
||||
const timeseriesResponse = await apmEventClient.search({
|
||||
apm: {
|
||||
events: [
|
||||
getProcessorEventForAggregatedTransactions(
|
||||
searchAggregatedTransactions
|
||||
),
|
||||
],
|
||||
},
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ terms: { [TRANSACTION_NAME]: transactionNames } },
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [TRANSACTION_TYPE]: transactionType } },
|
||||
{ range: rangeFilter(start, end) },
|
||||
...getDocumentTypeFilterForAggregatedTransactions(
|
||||
searchAggregatedTransactions
|
||||
),
|
||||
...esFilter,
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
transaction_groups: {
|
||||
terms: {
|
||||
field: TRANSACTION_NAME,
|
||||
size,
|
||||
},
|
||||
aggs: {
|
||||
timeseries: {
|
||||
date_histogram: {
|
||||
field: '@timestamp',
|
||||
fixed_interval: intervalString,
|
||||
min_doc_count: 0,
|
||||
extended_bounds: {
|
||||
min: start,
|
||||
max: end,
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
...getLatencyAggregation(latencyAggregationType, field),
|
||||
[EVENT_OUTCOME]: {
|
||||
filter: { term: { [EVENT_OUTCOME]: EventOutcome.failure } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return timeseriesResponse.aggregations?.transaction_groups.buckets ?? [];
|
||||
}
|
|
@ -1,89 +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 { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
|
||||
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
|
||||
import { getTimeseriesDataForTransactionGroups } from './get_timeseries_data_for_transaction_groups';
|
||||
import {
|
||||
getTransactionGroupsForPage,
|
||||
ServiceOverviewTransactionGroupSortField,
|
||||
} from './get_transaction_groups_for_page';
|
||||
import { mergeTransactionGroupData } from './merge_transaction_group_data';
|
||||
|
||||
export async function getServiceTransactionGroups({
|
||||
serviceName,
|
||||
setup,
|
||||
size,
|
||||
numBuckets,
|
||||
pageIndex,
|
||||
sortDirection,
|
||||
sortField,
|
||||
searchAggregatedTransactions,
|
||||
transactionType,
|
||||
latencyAggregationType,
|
||||
}: {
|
||||
serviceName: string;
|
||||
setup: Setup & SetupTimeRange;
|
||||
size: number;
|
||||
pageIndex: number;
|
||||
numBuckets: number;
|
||||
sortDirection: 'asc' | 'desc';
|
||||
sortField: ServiceOverviewTransactionGroupSortField;
|
||||
searchAggregatedTransactions: boolean;
|
||||
transactionType: string;
|
||||
latencyAggregationType: LatencyAggregationType;
|
||||
}) {
|
||||
const { apmEventClient, start, end, esFilter } = setup;
|
||||
|
||||
const {
|
||||
transactionGroups,
|
||||
totalTransactionGroups,
|
||||
isAggregationAccurate,
|
||||
} = await getTransactionGroupsForPage({
|
||||
apmEventClient,
|
||||
start,
|
||||
end,
|
||||
serviceName,
|
||||
esFilter,
|
||||
pageIndex,
|
||||
sortField,
|
||||
sortDirection,
|
||||
size,
|
||||
searchAggregatedTransactions,
|
||||
transactionType,
|
||||
latencyAggregationType,
|
||||
});
|
||||
|
||||
const transactionNames = transactionGroups.map((group) => group.name);
|
||||
|
||||
const timeseriesData = await getTimeseriesDataForTransactionGroups({
|
||||
apmEventClient,
|
||||
start,
|
||||
end,
|
||||
esFilter,
|
||||
numBuckets,
|
||||
searchAggregatedTransactions,
|
||||
serviceName,
|
||||
size,
|
||||
transactionNames,
|
||||
transactionType,
|
||||
latencyAggregationType,
|
||||
});
|
||||
|
||||
return {
|
||||
transactionGroups: mergeTransactionGroupData({
|
||||
transactionGroups,
|
||||
timeseriesData,
|
||||
start,
|
||||
end,
|
||||
latencyAggregationType,
|
||||
transactionType,
|
||||
}),
|
||||
totalTransactionGroups,
|
||||
isAggregationAccurate,
|
||||
};
|
||||
}
|
|
@ -1,90 +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 { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames';
|
||||
import { LatencyAggregationType } from '../../../../common/latency_aggregation_types';
|
||||
import { calculateThroughput } from '../../helpers/calculate_throughput';
|
||||
import { getLatencyValue } from '../../helpers/latency_aggregation_type';
|
||||
import { TransactionGroupTimeseriesData } from './get_timeseries_data_for_transaction_groups';
|
||||
import { TransactionGroupWithoutTimeseriesData } from './get_transaction_groups_for_page';
|
||||
|
||||
export function mergeTransactionGroupData({
|
||||
start,
|
||||
end,
|
||||
transactionGroups,
|
||||
timeseriesData,
|
||||
latencyAggregationType,
|
||||
transactionType,
|
||||
}: {
|
||||
start: number;
|
||||
end: number;
|
||||
transactionGroups: TransactionGroupWithoutTimeseriesData[];
|
||||
timeseriesData: TransactionGroupTimeseriesData;
|
||||
latencyAggregationType: LatencyAggregationType;
|
||||
transactionType: string;
|
||||
}) {
|
||||
return transactionGroups.map((transactionGroup) => {
|
||||
const groupBucket = timeseriesData.find(
|
||||
({ key }) => key === transactionGroup.name
|
||||
);
|
||||
|
||||
const timeseriesBuckets = groupBucket?.timeseries.buckets ?? [];
|
||||
|
||||
return timeseriesBuckets.reduce(
|
||||
(acc, point) => {
|
||||
return {
|
||||
...acc,
|
||||
latency: {
|
||||
...acc.latency,
|
||||
timeseries: acc.latency.timeseries.concat({
|
||||
x: point.key,
|
||||
y: getLatencyValue({
|
||||
latencyAggregationType,
|
||||
aggregation: point.latency,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
throughput: {
|
||||
...acc.throughput,
|
||||
timeseries: acc.throughput.timeseries.concat({
|
||||
x: point.key,
|
||||
y: calculateThroughput({
|
||||
start,
|
||||
end,
|
||||
value: point.doc_count,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
errorRate: {
|
||||
...acc.errorRate,
|
||||
timeseries: acc.errorRate.timeseries.concat({
|
||||
x: point.key,
|
||||
y: point[EVENT_OUTCOME].doc_count / point.doc_count,
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
{
|
||||
name: transactionGroup.name,
|
||||
transactionType,
|
||||
latency: {
|
||||
value: transactionGroup.latency,
|
||||
timeseries: [] as Array<{ x: number; y: number | null }>,
|
||||
},
|
||||
throughput: {
|
||||
value: transactionGroup.throughput,
|
||||
timeseries: [] as Array<{ x: number; y: number }>,
|
||||
},
|
||||
errorRate: {
|
||||
value: transactionGroup.errorRate,
|
||||
timeseries: [] as Array<{ x: number; y: number | null }>,
|
||||
},
|
||||
impact: transactionGroup.impact,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
|
@ -61,9 +61,10 @@ import {
|
|||
transactionChartsDistributionRoute,
|
||||
transactionChartsErrorRateRoute,
|
||||
transactionGroupsRoute,
|
||||
transactionGroupsOverviewRoute,
|
||||
transactionGroupsPrimaryStatisticsRoute,
|
||||
transactionLatencyChatsRoute,
|
||||
transactionThroughputChatsRoute,
|
||||
transactionGroupsComparisonStatisticsRoute,
|
||||
} from './transactions';
|
||||
import {
|
||||
rumOverviewLocalFiltersRoute,
|
||||
|
@ -164,9 +165,10 @@ const createApmApi = () => {
|
|||
.add(transactionChartsDistributionRoute)
|
||||
.add(transactionChartsErrorRateRoute)
|
||||
.add(transactionGroupsRoute)
|
||||
.add(transactionGroupsOverviewRoute)
|
||||
.add(transactionGroupsPrimaryStatisticsRoute)
|
||||
.add(transactionLatencyChatsRoute)
|
||||
.add(transactionThroughputChatsRoute)
|
||||
.add(transactionGroupsComparisonStatisticsRoute)
|
||||
|
||||
// UI filters
|
||||
.add(uiFiltersEnvironmentsRoute)
|
||||
|
|
|
@ -7,27 +7,29 @@
|
|||
|
||||
import Boom from '@hapi/boom';
|
||||
import * as t from 'io-ts';
|
||||
import { createRoute } from './create_route';
|
||||
import { rangeRt, uiFiltersRt } from './default_api_types';
|
||||
import { toNumberRt } from '../../common/runtime_types/to_number_rt';
|
||||
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
||||
import { setupRequest } from '../lib/helpers/setup_request';
|
||||
import { getServiceTransactionGroups } from '../lib/services/get_service_transaction_groups';
|
||||
import { getTransactionBreakdown } from '../lib/transactions/breakdown';
|
||||
import { getAnomalySeries } from '../lib/transactions/get_anomaly_data';
|
||||
import { getTransactionDistribution } from '../lib/transactions/distribution';
|
||||
import { getTransactionGroupList } from '../lib/transaction_groups';
|
||||
import { getErrorRate } from '../lib/transaction_groups/get_error_rate';
|
||||
import { getLatencyTimeseries } from '../lib/transactions/get_latency_charts';
|
||||
import { getThroughputCharts } from '../lib/transactions/get_throughput_charts';
|
||||
import {
|
||||
LatencyAggregationType,
|
||||
latencyAggregationTypeRt,
|
||||
} from '../../common/latency_aggregation_types';
|
||||
import { jsonRt } from '../../common/runtime_types/json_rt';
|
||||
import { toNumberRt } from '../../common/runtime_types/to_number_rt';
|
||||
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
|
||||
import { setupRequest } from '../lib/helpers/setup_request';
|
||||
import { getServiceTransactionGroups } from '../lib/services/get_service_transaction_groups';
|
||||
import { getServiceTransactionGroupComparisonStatistics } from '../lib/services/get_service_transaction_group_comparison_statistics';
|
||||
import { getTransactionBreakdown } from '../lib/transactions/breakdown';
|
||||
import { getTransactionDistribution } from '../lib/transactions/distribution';
|
||||
import { getAnomalySeries } from '../lib/transactions/get_anomaly_data';
|
||||
import { getLatencyTimeseries } from '../lib/transactions/get_latency_charts';
|
||||
import { getThroughputCharts } from '../lib/transactions/get_throughput_charts';
|
||||
import { getTransactionGroupList } from '../lib/transaction_groups';
|
||||
import { getErrorRate } from '../lib/transaction_groups/get_error_rate';
|
||||
import { createRoute } from './create_route';
|
||||
import { rangeRt, uiFiltersRt } from './default_api_types';
|
||||
|
||||
/**
|
||||
* Returns a list of transactions grouped by name
|
||||
* //TODO: delete this once we moved away from the old table in the transaction overview page. It should be replaced by /transactions/groups/overview/
|
||||
* //TODO: delete this once we moved away from the old table in the transaction overview page. It should be replaced by /transactions/groups/primary_statistics/
|
||||
*/
|
||||
export const transactionGroupsRoute = createRoute({
|
||||
endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups',
|
||||
|
@ -63,25 +65,56 @@ export const transactionGroupsRoute = createRoute({
|
|||
},
|
||||
});
|
||||
|
||||
export const transactionGroupsOverviewRoute = createRoute({
|
||||
endpoint: 'GET /api/apm/services/{serviceName}/transactions/groups/overview',
|
||||
export const transactionGroupsPrimaryStatisticsRoute = createRoute({
|
||||
endpoint:
|
||||
'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics',
|
||||
params: t.type({
|
||||
path: t.type({ serviceName: t.string }),
|
||||
query: t.intersection([
|
||||
rangeRt,
|
||||
uiFiltersRt,
|
||||
t.type({
|
||||
size: toNumberRt,
|
||||
transactionType: t.string,
|
||||
latencyAggregationType: latencyAggregationTypeRt,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
options: {
|
||||
tags: ['access:apm'],
|
||||
},
|
||||
handler: async ({ context, request }) => {
|
||||
const setup = await setupRequest(context, request);
|
||||
|
||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||
setup
|
||||
);
|
||||
|
||||
const {
|
||||
path: { serviceName },
|
||||
query: { latencyAggregationType, transactionType },
|
||||
} = context.params;
|
||||
|
||||
return getServiceTransactionGroups({
|
||||
setup,
|
||||
serviceName,
|
||||
searchAggregatedTransactions,
|
||||
transactionType,
|
||||
latencyAggregationType: latencyAggregationType as LatencyAggregationType,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const transactionGroupsComparisonStatisticsRoute = createRoute({
|
||||
endpoint:
|
||||
'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics',
|
||||
params: t.type({
|
||||
path: t.type({ serviceName: t.string }),
|
||||
query: t.intersection([
|
||||
rangeRt,
|
||||
uiFiltersRt,
|
||||
t.type({
|
||||
transactionNames: jsonRt,
|
||||
numBuckets: toNumberRt,
|
||||
pageIndex: toNumberRt,
|
||||
sortDirection: t.union([t.literal('asc'), t.literal('desc')]),
|
||||
sortField: t.union([
|
||||
t.literal('name'),
|
||||
t.literal('latency'),
|
||||
t.literal('throughput'),
|
||||
t.literal('errorRate'),
|
||||
t.literal('impact'),
|
||||
]),
|
||||
transactionType: t.string,
|
||||
latencyAggregationType: latencyAggregationTypeRt,
|
||||
}),
|
||||
|
@ -100,24 +133,18 @@ export const transactionGroupsOverviewRoute = createRoute({
|
|||
const {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
transactionNames,
|
||||
latencyAggregationType,
|
||||
numBuckets,
|
||||
pageIndex,
|
||||
size,
|
||||
sortDirection,
|
||||
sortField,
|
||||
transactionType,
|
||||
},
|
||||
} = context.params;
|
||||
|
||||
return getServiceTransactionGroups({
|
||||
return getServiceTransactionGroupComparisonStatistics({
|
||||
setup,
|
||||
serviceName,
|
||||
pageIndex,
|
||||
transactionNames,
|
||||
searchAggregatedTransactions,
|
||||
size,
|
||||
sortDirection,
|
||||
sortField,
|
||||
transactionType,
|
||||
numBuckets,
|
||||
latencyAggregationType: latencyAggregationType as LatencyAggregationType,
|
||||
|
|
|
@ -62,7 +62,8 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte
|
|||
loadTestFile(require.resolve('./transactions/latency'));
|
||||
loadTestFile(require.resolve('./transactions/throughput'));
|
||||
loadTestFile(require.resolve('./transactions/top_transaction_groups'));
|
||||
loadTestFile(require.resolve('./transactions/transactions_groups_overview'));
|
||||
loadTestFile(require.resolve('./transactions/transactions_groups_primary_statistics'));
|
||||
loadTestFile(require.resolve('./transactions/transactions_groups_comparison_statistics'));
|
||||
|
||||
loadTestFile(require.resolve('./feature_controls'));
|
||||
|
||||
|
|
|
@ -6,20 +6,17 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { last, omit, pick, sortBy } from 'lodash';
|
||||
import url from 'url';
|
||||
import { sortBy, pick, last, omit } from 'lodash';
|
||||
import { ValuesType } from 'utility-types';
|
||||
import { registry } from '../../../common/registry';
|
||||
import { Maybe } from '../../../../../plugins/apm/typings/common';
|
||||
import { isFiniteNumber } from '../../../../../plugins/apm/common/utils/is_finite_number';
|
||||
import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi';
|
||||
import { roundNumber } from '../../../utils';
|
||||
import { ENVIRONMENT_ALL } from '../../../../../plugins/apm/common/environment_filter_values';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi';
|
||||
import archives from '../../../common/fixtures/es_archiver/archives_metadata';
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import { registry } from '../../../common/registry';
|
||||
import { apmDependenciesMapping, createServiceDependencyDocs } from './es_utils';
|
||||
|
||||
const round = (num: Maybe<number>): string => (isFiniteNumber(num) ? num.toPrecision(4) : '');
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const es = getService('es');
|
||||
|
@ -235,9 +232,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
expect(opbeansNode !== undefined).to.be(true);
|
||||
|
||||
const values = {
|
||||
latency: round(opbeansNode?.latency.value),
|
||||
throughput: round(opbeansNode?.throughput.value),
|
||||
errorRate: round(opbeansNode?.errorRate.value),
|
||||
latency: roundNumber(opbeansNode?.latency.value),
|
||||
throughput: roundNumber(opbeansNode?.throughput.value),
|
||||
errorRate: roundNumber(opbeansNode?.errorRate.value),
|
||||
...pick(opbeansNode, 'serviceName', 'type', 'agentName', 'environment', 'impact'),
|
||||
};
|
||||
|
||||
|
@ -250,16 +247,16 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
environment: '',
|
||||
serviceName: 'opbeans-node',
|
||||
type: 'service',
|
||||
errorRate: round(errors / count),
|
||||
latency: round(sum / count),
|
||||
throughput: round(count / ((endTime - startTime) / 1000 / 60)),
|
||||
errorRate: roundNumber(errors / count),
|
||||
latency: roundNumber(sum / count),
|
||||
throughput: roundNumber(count / ((endTime - startTime) / 1000 / 60)),
|
||||
impact: 100,
|
||||
});
|
||||
|
||||
const firstValue = round(opbeansNode?.latency.timeseries[0].y);
|
||||
const lastValue = round(last(opbeansNode?.latency.timeseries)?.y);
|
||||
const firstValue = roundNumber(opbeansNode?.latency.timeseries[0].y);
|
||||
const lastValue = roundNumber(last(opbeansNode?.latency.timeseries)?.y);
|
||||
|
||||
expect(firstValue).to.be(round(20 / 3));
|
||||
expect(firstValue).to.be(roundNumber(20 / 3));
|
||||
expect(lastValue).to.be('1.000');
|
||||
});
|
||||
|
||||
|
@ -271,9 +268,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
expect(postgres !== undefined).to.be(true);
|
||||
|
||||
const values = {
|
||||
latency: round(postgres?.latency.value),
|
||||
throughput: round(postgres?.throughput.value),
|
||||
errorRate: round(postgres?.errorRate.value),
|
||||
latency: roundNumber(postgres?.latency.value),
|
||||
throughput: roundNumber(postgres?.throughput.value),
|
||||
errorRate: roundNumber(postgres?.errorRate.value),
|
||||
...pick(postgres, 'spanType', 'spanSubtype', 'name', 'impact', 'type'),
|
||||
};
|
||||
|
||||
|
@ -286,9 +283,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
spanSubtype: 'http',
|
||||
name: 'postgres',
|
||||
type: 'external',
|
||||
errorRate: round(errors / count),
|
||||
latency: round(sum / count),
|
||||
throughput: round(count / ((endTime - startTime) / 1000 / 60)),
|
||||
errorRate: roundNumber(errors / count),
|
||||
latency: roundNumber(sum / count),
|
||||
throughput: roundNumber(count / ((endTime - startTime) / 1000 / 60)),
|
||||
impact: 0,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,517 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`APM API tests basic apm_8.0.0 Transaction groups comparison statistics when data is loaded returns the correct data 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"x": 1607435820000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607435880000,
|
||||
"y": 69429,
|
||||
},
|
||||
Object {
|
||||
"x": 1607435940000,
|
||||
"y": 8071285,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436000000,
|
||||
"y": 31949,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436060000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436120000,
|
||||
"y": 47755,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436180000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436240000,
|
||||
"y": 35403,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436300000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436360000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436420000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436480000,
|
||||
"y": 48137,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436540000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436600000,
|
||||
"y": 35457,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436660000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436720000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436780000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436840000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436900000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436960000,
|
||||
"y": 30501,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437020000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437080000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437140000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437200000,
|
||||
"y": 46937.5,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437260000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437320000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437380000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437440000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437500000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437560000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437620000,
|
||||
"y": null,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`APM API tests basic apm_8.0.0 Transaction groups comparison statistics when data is loaded returns the correct data 2`] = `
|
||||
Array [
|
||||
Object {
|
||||
"x": 1607435820000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607435880000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1607435940000,
|
||||
"y": 2,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436000000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436060000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436120000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436180000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436240000,
|
||||
"y": 4,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436300000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436360000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436420000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436480000,
|
||||
"y": 2,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436540000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436600000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436660000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436720000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436780000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436840000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436900000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436960000,
|
||||
"y": 2,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437020000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437080000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437140000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437200000,
|
||||
"y": 2,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437260000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437320000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437380000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437440000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437500000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437560000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437620000,
|
||||
"y": 0,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`APM API tests basic apm_8.0.0 Transaction groups comparison statistics when data is loaded returns the correct data 3`] = `
|
||||
Array [
|
||||
Object {
|
||||
"x": 1607435820000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607435880000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607435940000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436000000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436060000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436120000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436180000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436240000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436300000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436360000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436420000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436480000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436540000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436600000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436660000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436720000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436780000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436840000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436900000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436960000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437020000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437080000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437140000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437200000,
|
||||
"y": 0.5,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437260000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437320000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437380000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437440000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437500000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437560000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437620000,
|
||||
"y": null,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`APM API tests basic apm_8.0.0 Transaction groups comparison statistics when data is loaded returns the correct for latency aggregation 99th percentile 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"x": 1607435820000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607435880000,
|
||||
"y": 69429,
|
||||
},
|
||||
Object {
|
||||
"x": 1607435940000,
|
||||
"y": 8198285,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436000000,
|
||||
"y": 31949,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436060000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436120000,
|
||||
"y": 47755,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436180000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436240000,
|
||||
"y": 73411,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436300000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436360000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436420000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436480000,
|
||||
"y": 55116,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436540000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436600000,
|
||||
"y": 35457,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436660000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436720000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436780000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436840000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436900000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436960000,
|
||||
"y": 46040,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437020000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437080000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437140000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437200000,
|
||||
"y": 82486,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437260000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437320000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437380000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437440000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437500000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437560000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437620000,
|
||||
"y": null,
|
||||
},
|
||||
]
|
||||
`;
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import url from 'url';
|
||||
import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi';
|
||||
import archives from '../../common/fixtures/es_archiver/archives_metadata';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import { registry } from '../../common/registry';
|
||||
import { removeEmptyCoordinates, roundNumber } from '../../utils';
|
||||
|
||||
type TransactionsGroupsComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/comparison_statistics'>;
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
const archiveName = 'apm_8.0.0';
|
||||
const { start, end } = archives[archiveName];
|
||||
const transactionNames = ['DispatcherServlet#doGet', 'APIRestController#customers'];
|
||||
|
||||
registry.when(
|
||||
'Transaction groups comparison statistics when data is not loaded',
|
||||
{ config: 'basic', archives: [] },
|
||||
() => {
|
||||
it('handles the empty state', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/comparison_statistics`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
numBuckets: 20,
|
||||
latencyAggregationType: 'avg',
|
||||
transactionType: 'request',
|
||||
transactionNames: JSON.stringify(transactionNames),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body).to.empty();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
registry.when(
|
||||
'Transaction groups comparison statistics when data is loaded',
|
||||
{ config: 'basic', archives: [archiveName] },
|
||||
() => {
|
||||
it('returns the correct data', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/comparison_statistics`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
numBuckets: 20,
|
||||
transactionType: 'request',
|
||||
latencyAggregationType: 'avg',
|
||||
transactionNames: JSON.stringify(transactionNames),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
|
||||
const transactionsGroupsComparisonStatistics = response.body as TransactionsGroupsComparisonStatistics;
|
||||
|
||||
expect(Object.keys(transactionsGroupsComparisonStatistics).length).to.be.eql(
|
||||
transactionNames.length
|
||||
);
|
||||
|
||||
transactionNames.map((transactionName) => {
|
||||
expect(transactionsGroupsComparisonStatistics[transactionName]).not.to.be.empty();
|
||||
});
|
||||
|
||||
const { latency, throughput, errorRate, impact } = transactionsGroupsComparisonStatistics[
|
||||
transactionNames[0]
|
||||
];
|
||||
|
||||
expect(removeEmptyCoordinates(latency).length).to.be.greaterThan(0);
|
||||
expectSnapshot(latency).toMatch();
|
||||
|
||||
expect(removeEmptyCoordinates(throughput).length).to.be.greaterThan(0);
|
||||
expectSnapshot(throughput).toMatch();
|
||||
|
||||
expect(removeEmptyCoordinates(errorRate).length).to.be.greaterThan(0);
|
||||
expectSnapshot(errorRate).toMatch();
|
||||
|
||||
expectSnapshot(roundNumber(impact)).toMatchInline(`"93.93"`);
|
||||
});
|
||||
|
||||
it('returns the correct for latency aggregation 99th percentile', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/comparison_statistics`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
numBuckets: 20,
|
||||
transactionType: 'request',
|
||||
latencyAggregationType: 'p99',
|
||||
transactionNames: JSON.stringify(transactionNames),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
|
||||
const transactionsGroupsComparisonStatistics = response.body as TransactionsGroupsComparisonStatistics;
|
||||
|
||||
expect(Object.keys(transactionsGroupsComparisonStatistics).length).to.be.eql(
|
||||
transactionNames.length
|
||||
);
|
||||
|
||||
transactionNames.map((transactionName) => {
|
||||
expect(transactionsGroupsComparisonStatistics[transactionName]).not.to.be.empty();
|
||||
});
|
||||
|
||||
const { latency, throughput, errorRate } = transactionsGroupsComparisonStatistics[
|
||||
transactionNames[0]
|
||||
];
|
||||
expect(removeEmptyCoordinates(latency).length).to.be.greaterThan(0);
|
||||
expectSnapshot(latency).toMatch();
|
||||
|
||||
expect(removeEmptyCoordinates(throughput).length).to.be.greaterThan(0);
|
||||
expect(removeEmptyCoordinates(errorRate).length).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('returns empty when transaction name is not found', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/comparison_statistics`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
numBuckets: 20,
|
||||
transactionType: 'request',
|
||||
latencyAggregationType: 'avg',
|
||||
transactionNames: JSON.stringify(['foo']),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body).to.empty();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,273 +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 expect from '@kbn/expect';
|
||||
import { pick, uniqBy, sortBy } from 'lodash';
|
||||
import url from 'url';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import archives from '../../common/fixtures/es_archiver/archives_metadata';
|
||||
import { registry } from '../../common/registry';
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
const archiveName = 'apm_8.0.0';
|
||||
const { start, end } = archives[archiveName];
|
||||
|
||||
registry.when(
|
||||
'Transaction groups overview when data is not loaded',
|
||||
{ config: 'basic', archives: [] },
|
||||
() => {
|
||||
it('handles the empty state', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/overview`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
size: 5,
|
||||
numBuckets: 20,
|
||||
pageIndex: 0,
|
||||
sortDirection: 'desc',
|
||||
sortField: 'impact',
|
||||
latencyAggregationType: 'avg',
|
||||
transactionType: 'request',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body).to.eql({
|
||||
totalTransactionGroups: 0,
|
||||
transactionGroups: [],
|
||||
isAggregationAccurate: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
registry.when(
|
||||
'Top transaction groups when data is loaded',
|
||||
{ config: 'basic', archives: [archiveName] },
|
||||
() => {
|
||||
it('returns the correct data', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/overview`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
size: 5,
|
||||
numBuckets: 20,
|
||||
pageIndex: 0,
|
||||
sortDirection: 'desc',
|
||||
sortField: 'impact',
|
||||
transactionType: 'request',
|
||||
latencyAggregationType: 'avg',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
|
||||
expectSnapshot(response.body.totalTransactionGroups).toMatchInline(`12`);
|
||||
|
||||
expectSnapshot(response.body.transactionGroups.map((group: any) => group.name))
|
||||
.toMatchInline(`
|
||||
Array [
|
||||
"DispatcherServlet#doGet",
|
||||
"APIRestController#customers",
|
||||
"APIRestController#order",
|
||||
"APIRestController#stats",
|
||||
"APIRestController#customerWhoBought",
|
||||
]
|
||||
`);
|
||||
|
||||
expectSnapshot(response.body.transactionGroups.map((group: any) => group.impact))
|
||||
.toMatchInline(`
|
||||
Array [
|
||||
100,
|
||||
1.43059146953109,
|
||||
0.953769516915408,
|
||||
0.905498741191481,
|
||||
0.894989230293471,
|
||||
]
|
||||
`);
|
||||
|
||||
const firstItem = response.body.transactionGroups[0];
|
||||
|
||||
expectSnapshot(
|
||||
pick(firstItem, 'name', 'latency.value', 'throughput.value', 'errorRate.value', 'impact')
|
||||
).toMatchInline(`
|
||||
Object {
|
||||
"errorRate": Object {
|
||||
"value": 0.0625,
|
||||
},
|
||||
"impact": 100,
|
||||
"latency": Object {
|
||||
"value": 1044995.1875,
|
||||
},
|
||||
"name": "DispatcherServlet#doGet",
|
||||
"throughput": Object {
|
||||
"value": 0.533333333333333,
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
expectSnapshot(
|
||||
firstItem.latency.timeseries.filter(({ y }: any) => y > 0).length
|
||||
).toMatchInline(`9`);
|
||||
|
||||
expectSnapshot(
|
||||
firstItem.throughput.timeseries.filter(({ y }: any) => y > 0).length
|
||||
).toMatchInline(`9`);
|
||||
|
||||
expectSnapshot(
|
||||
firstItem.errorRate.timeseries.filter(({ y }: any) => y > 0).length
|
||||
).toMatchInline(`1`);
|
||||
});
|
||||
|
||||
it('sorts items in the correct order', async () => {
|
||||
const descendingResponse = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/overview`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
size: 5,
|
||||
numBuckets: 20,
|
||||
pageIndex: 0,
|
||||
sortDirection: 'desc',
|
||||
sortField: 'impact',
|
||||
transactionType: 'request',
|
||||
latencyAggregationType: 'avg',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(descendingResponse.status).to.be(200);
|
||||
|
||||
const descendingOccurrences = descendingResponse.body.transactionGroups.map(
|
||||
(item: any) => item.impact
|
||||
);
|
||||
|
||||
expect(descendingOccurrences).to.eql(sortBy(descendingOccurrences.concat()).reverse());
|
||||
|
||||
const ascendingResponse = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/overview`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
size: 5,
|
||||
numBuckets: 20,
|
||||
pageIndex: 0,
|
||||
sortDirection: 'desc',
|
||||
sortField: 'impact',
|
||||
transactionType: 'request',
|
||||
latencyAggregationType: 'avg',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const ascendingOccurrences = ascendingResponse.body.transactionGroups.map(
|
||||
(item: any) => item.impact
|
||||
);
|
||||
|
||||
expect(ascendingOccurrences).to.eql(sortBy(ascendingOccurrences.concat()).reverse());
|
||||
});
|
||||
|
||||
it('sorts items by the correct field', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/overview`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
size: 5,
|
||||
numBuckets: 20,
|
||||
pageIndex: 0,
|
||||
sortDirection: 'desc',
|
||||
sortField: 'latency',
|
||||
transactionType: 'request',
|
||||
latencyAggregationType: 'avg',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
|
||||
const latencies = response.body.transactionGroups.map((group: any) => group.latency.value);
|
||||
|
||||
expect(latencies).to.eql(sortBy(latencies.concat()).reverse());
|
||||
});
|
||||
|
||||
it('paginates through the items', async () => {
|
||||
const size = 1;
|
||||
|
||||
const firstPage = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/overview`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
size,
|
||||
numBuckets: 20,
|
||||
pageIndex: 0,
|
||||
sortDirection: 'desc',
|
||||
sortField: 'impact',
|
||||
transactionType: 'request',
|
||||
latencyAggregationType: 'avg',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(firstPage.status).to.eql(200);
|
||||
|
||||
const totalItems = firstPage.body.totalTransactionGroups;
|
||||
|
||||
const pages = Math.floor(totalItems / size);
|
||||
|
||||
const items = await new Array(pages)
|
||||
.fill(undefined)
|
||||
.reduce(async (prevItemsPromise, _, pageIndex) => {
|
||||
const prevItems = await prevItemsPromise;
|
||||
|
||||
const thisPage = await supertest.get(
|
||||
url.format({
|
||||
pathname: '/api/apm/services/opbeans-java/transactions/groups/overview',
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
size,
|
||||
numBuckets: 20,
|
||||
pageIndex,
|
||||
sortDirection: 'desc',
|
||||
sortField: 'impact',
|
||||
transactionType: 'request',
|
||||
latencyAggregationType: 'avg',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return prevItems.concat(thisPage.body.transactionGroups);
|
||||
}, Promise.resolve([]));
|
||||
|
||||
expect(items.length).to.eql(totalItems);
|
||||
|
||||
expect(uniqBy(items, 'name').length).to.eql(totalItems);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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 { pick, sum } from 'lodash';
|
||||
import url from 'url';
|
||||
import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import archives from '../../common/fixtures/es_archiver/archives_metadata';
|
||||
import { registry } from '../../common/registry';
|
||||
|
||||
type TransactionsGroupsPrimaryStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/groups/primary_statistics'>;
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
const archiveName = 'apm_8.0.0';
|
||||
const { start, end } = archives[archiveName];
|
||||
|
||||
registry.when(
|
||||
'Transaction groups primary statistics when data is not loaded',
|
||||
{ config: 'basic', archives: [] },
|
||||
() => {
|
||||
it('handles the empty state', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/primary_statistics`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
latencyAggregationType: 'avg',
|
||||
transactionType: 'request',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
const transctionsGroupsPrimaryStatistics = response.body as TransactionsGroupsPrimaryStatistics;
|
||||
expect(transctionsGroupsPrimaryStatistics.transactionGroups).to.empty();
|
||||
expect(transctionsGroupsPrimaryStatistics.isAggregationAccurate).to.be(true);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
registry.when(
|
||||
'Transaction groups primary statistics when data is loaded',
|
||||
{ config: 'basic', archives: [archiveName] },
|
||||
() => {
|
||||
it('returns the correct data', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/primary_statistics`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
transactionType: 'request',
|
||||
latencyAggregationType: 'avg',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
|
||||
const transctionsGroupsPrimaryStatistics = response.body as TransactionsGroupsPrimaryStatistics;
|
||||
|
||||
expectSnapshot(
|
||||
transctionsGroupsPrimaryStatistics.transactionGroups.map((group: any) => group.name)
|
||||
).toMatchInline(`
|
||||
Array [
|
||||
"DispatcherServlet#doGet",
|
||||
"APIRestController#customerWhoBought",
|
||||
"APIRestController#order",
|
||||
"APIRestController#customer",
|
||||
"ResourceHttpRequestHandler",
|
||||
"APIRestController#customers",
|
||||
"APIRestController#stats",
|
||||
"APIRestController#topProducts",
|
||||
"APIRestController#orders",
|
||||
"APIRestController#product",
|
||||
"APIRestController#products",
|
||||
"DispatcherServlet#doPost",
|
||||
]
|
||||
`);
|
||||
|
||||
const impacts = transctionsGroupsPrimaryStatistics.transactionGroups.map(
|
||||
(group: any) => group.impact
|
||||
);
|
||||
expectSnapshot(impacts).toMatchInline(`
|
||||
Array [
|
||||
93.9295870910491,
|
||||
0.850308244392878,
|
||||
0.905514602241759,
|
||||
0.699947181217412,
|
||||
0.143906183235671,
|
||||
1.35334507158962,
|
||||
0.860178761411346,
|
||||
0.476138685202191,
|
||||
0.446650726277923,
|
||||
0.262571482598846,
|
||||
0.062116281544223,
|
||||
0.00973568923904662,
|
||||
]
|
||||
`);
|
||||
|
||||
expect(Math.round(sum(impacts))).to.eql(100);
|
||||
|
||||
const firstItem = transctionsGroupsPrimaryStatistics.transactionGroups[0];
|
||||
|
||||
expectSnapshot(pick(firstItem, 'name', 'latency', 'throughput', 'errorRate', 'impact'))
|
||||
.toMatchInline(`
|
||||
Object {
|
||||
"errorRate": 0.0625,
|
||||
"impact": 93.9295870910491,
|
||||
"latency": 1044995.1875,
|
||||
"name": "DispatcherServlet#doGet",
|
||||
"throughput": 0.533333333333333,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns the correct data for latency aggregation 99th percentile', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/transactions/groups/primary_statistics`,
|
||||
query: {
|
||||
start,
|
||||
end,
|
||||
uiFilters: '{}',
|
||||
transactionType: 'request',
|
||||
latencyAggregationType: 'p99',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
|
||||
const transctionsGroupsPrimaryStatistics = response.body as TransactionsGroupsPrimaryStatistics;
|
||||
|
||||
const firstItem = transctionsGroupsPrimaryStatistics.transactionGroups[0];
|
||||
expectSnapshot(firstItem.latency).toMatchInline(`8198285`);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
18
x-pack/test/apm_api_integration/utils.ts
Normal file
18
x-pack/test/apm_api_integration/utils.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { Coordinate } from '../../plugins/apm/typings/timeseries';
|
||||
import { isFiniteNumber } from '../../plugins/apm/common/utils/is_finite_number';
|
||||
import { Maybe } from '../../plugins/apm/typings/common';
|
||||
|
||||
export function roundNumber(num: Maybe<number>) {
|
||||
return isFiniteNumber(num) ? num.toPrecision(4) : '';
|
||||
}
|
||||
|
||||
export function removeEmptyCoordinates(coordinates: Coordinate[]) {
|
||||
return coordinates.filter(({ y }) => isFiniteNumber(y));
|
||||
}
|
12
x-pack/typings/elasticsearch/aggregations.d.ts
vendored
12
x-pack/typings/elasticsearch/aggregations.d.ts
vendored
|
@ -190,6 +190,15 @@ export interface AggregationOptionsByType {
|
|||
gap_policy?: 'skip' | 'insert_zeros';
|
||||
format?: string;
|
||||
};
|
||||
rate: {
|
||||
unit: 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
|
||||
} & (
|
||||
| {
|
||||
field: string;
|
||||
mode: 'sum' | 'value_count';
|
||||
}
|
||||
| {}
|
||||
);
|
||||
}
|
||||
|
||||
type AggregationType = keyof AggregationOptionsByType;
|
||||
|
@ -409,6 +418,9 @@ interface AggregationResponsePart<TAggregationOptionsMap extends AggregationOpti
|
|||
avg_bucket: {
|
||||
value: number | null;
|
||||
};
|
||||
rate: {
|
||||
value: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
type TopMetricsMap<TFieldName> = TFieldName extends string
|
||||
|
|
Loading…
Reference in a new issue