[APM] Adding comparison to Throughput chart, Error rate chart, and Errors table (#94204)
* adding comparison to throuput chart * adding comparison to error rate chart * adding comparison to errors table * fixing/adding api test * addressing pr comments * addressing pr comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
6c9cfd4893
commit
b0fa077e8a
|
@ -22,9 +22,11 @@ type ErrorGroupComparisonStatistics = APIReturnType<'GET /api/apm/services/{serv
|
|||
export function getColumns({
|
||||
serviceName,
|
||||
errorGroupComparisonStatistics,
|
||||
comparisonEnabled,
|
||||
}: {
|
||||
serviceName: string;
|
||||
errorGroupComparisonStatistics: ErrorGroupComparisonStatistics;
|
||||
comparisonEnabled?: boolean;
|
||||
}): Array<EuiBasicTableColumn<ErrorGroupPrimaryStatistics['error_groups'][0]>> {
|
||||
return [
|
||||
{
|
||||
|
@ -71,12 +73,17 @@ export function getColumns({
|
|||
),
|
||||
width: px(unit * 12),
|
||||
render: (_, { occurrences, group_id: errorGroupId }) => {
|
||||
const timeseries =
|
||||
errorGroupComparisonStatistics?.[errorGroupId]?.timeseries;
|
||||
const currentPeriodTimeseries =
|
||||
errorGroupComparisonStatistics?.currentPeriod?.[errorGroupId]
|
||||
?.timeseries;
|
||||
const previousPeriodTimeseries =
|
||||
errorGroupComparisonStatistics?.previousPeriod?.[errorGroupId]
|
||||
?.timeseries;
|
||||
|
||||
return (
|
||||
<SparkPlot
|
||||
color="euiColorVis7"
|
||||
series={timeseries}
|
||||
series={currentPeriodTimeseries}
|
||||
valueLabel={i18n.translate(
|
||||
'xpack.apm.serviceOveriew.errorsTableOccurrences',
|
||||
{
|
||||
|
@ -86,6 +93,9 @@ export function getColumns({
|
|||
},
|
||||
}
|
||||
)}
|
||||
comparisonSeries={
|
||||
comparisonEnabled ? previousPeriodTimeseries : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -18,14 +18,18 @@ 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 { ErrorOverviewLink } from '../../../shared/Links/apm/ErrorOverviewLink';
|
||||
import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper';
|
||||
import { getTimeRangeComparison } from '../../../shared/time_comparison/get_time_range_comparison';
|
||||
import { ServiceOverviewTableContainer } from '../service_overview_table_container';
|
||||
import { getColumns } from './get_column';
|
||||
|
||||
interface Props {
|
||||
serviceName: string;
|
||||
}
|
||||
type ErrorGroupPrimaryStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/primary_statistics'>;
|
||||
type ErrorGroupComparisonStatistics = APIReturnType<'GET /api/apm/services/{serviceName}/error_groups/comparison_statistics'>;
|
||||
|
||||
type SortDirection = 'asc' | 'desc';
|
||||
type SortField = 'name' | 'last_seen' | 'occurrences';
|
||||
|
@ -36,14 +40,31 @@ const DEFAULT_SORT = {
|
|||
field: 'occurrences' as const,
|
||||
};
|
||||
|
||||
const INITIAL_STATE = {
|
||||
const INITIAL_STATE_PRIMARY_STATISTICS: {
|
||||
items: ErrorGroupPrimaryStatistics['error_groups'];
|
||||
totalItems: number;
|
||||
requestId?: string;
|
||||
} = {
|
||||
items: [],
|
||||
totalItems: 0,
|
||||
requestId: undefined,
|
||||
};
|
||||
|
||||
const INITIAL_STATE_COMPARISON_STATISTICS: ErrorGroupComparisonStatistics = {
|
||||
currentPeriod: {},
|
||||
previousPeriod: {},
|
||||
};
|
||||
|
||||
export function ServiceOverviewErrorsTable({ serviceName }: Props) {
|
||||
const {
|
||||
urlParams: { environment, kuery, start, end },
|
||||
urlParams: {
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
comparisonType,
|
||||
comparisonEnabled,
|
||||
},
|
||||
} = useUrlParams();
|
||||
const { transactionType } = useApmServiceContext();
|
||||
const [tableOptions, setTableOptions] = useState<{
|
||||
|
@ -57,9 +78,16 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
|
|||
sort: DEFAULT_SORT,
|
||||
});
|
||||
|
||||
const { pageIndex, sort } = tableOptions;
|
||||
const { comparisonStart, comparisonEnd } = getTimeRangeComparison({
|
||||
start,
|
||||
end,
|
||||
comparisonType,
|
||||
});
|
||||
|
||||
const { data = INITIAL_STATE, status } = useFetcher(
|
||||
const { pageIndex, sort } = tableOptions;
|
||||
const { direction, field } = sort;
|
||||
|
||||
const { data = INITIAL_STATE_PRIMARY_STATISTICS, status } = useFetcher(
|
||||
(callApmApi) => {
|
||||
if (!start || !end || !transactionType) {
|
||||
return;
|
||||
|
@ -78,37 +106,43 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
|
|||
},
|
||||
},
|
||||
}).then((response) => {
|
||||
const currentPageErrorGroups = orderBy(
|
||||
response.error_groups,
|
||||
field,
|
||||
direction
|
||||
).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE);
|
||||
|
||||
return {
|
||||
requestId: uuid(),
|
||||
items: response.error_groups,
|
||||
items: currentPageErrorGroups,
|
||||
totalItems: response.error_groups.length,
|
||||
};
|
||||
});
|
||||
},
|
||||
[environment, kuery, start, end, serviceName, transactionType]
|
||||
// comparisonType is listed as dependency even thought it is not used. This is needed to trigger the comparison api when it is changed.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
serviceName,
|
||||
transactionType,
|
||||
pageIndex,
|
||||
direction,
|
||||
field,
|
||||
comparisonType,
|
||||
]
|
||||
);
|
||||
|
||||
const { requestId, items } = data;
|
||||
const currentPageErrorGroups = orderBy(
|
||||
items,
|
||||
sort.field,
|
||||
sort.direction
|
||||
).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE);
|
||||
const { requestId, items, totalItems } = data;
|
||||
|
||||
const groupIds = JSON.stringify(
|
||||
currentPageErrorGroups.map(({ group_id: groupId }) => groupId).sort()
|
||||
);
|
||||
const {
|
||||
data: errorGroupComparisonStatistics,
|
||||
data: errorGroupComparisonStatistics = INITIAL_STATE_COMPARISON_STATISTICS,
|
||||
status: errorGroupComparisonStatisticsStatus,
|
||||
} = useFetcher(
|
||||
(callApmApi) => {
|
||||
if (
|
||||
requestId &&
|
||||
currentPageErrorGroups.length &&
|
||||
start &&
|
||||
end &&
|
||||
transactionType
|
||||
) {
|
||||
if (requestId && items.length && start && end && transactionType) {
|
||||
return callApmApi({
|
||||
endpoint:
|
||||
'GET /api/apm/services/{serviceName}/error_groups/comparison_statistics',
|
||||
|
@ -121,21 +155,26 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
|
|||
end,
|
||||
numBuckets: 20,
|
||||
transactionType,
|
||||
groupIds,
|
||||
groupIds: JSON.stringify(
|
||||
items.map(({ group_id: groupId }) => groupId).sort()
|
||||
),
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
// only fetches agg results when requestId or group ids change
|
||||
// only fetches agg results when requestId changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[requestId, groupIds],
|
||||
[requestId],
|
||||
{ preservePreviousData: false }
|
||||
);
|
||||
|
||||
const columns = getColumns({
|
||||
serviceName,
|
||||
errorGroupComparisonStatistics: errorGroupComparisonStatistics ?? {},
|
||||
errorGroupComparisonStatistics,
|
||||
comparisonEnabled,
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -164,16 +203,16 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
|
|||
<TableFetchWrapper status={status}>
|
||||
<ServiceOverviewTableContainer
|
||||
isEmptyAndLoading={
|
||||
items.length === 0 && status === FETCH_STATUS.LOADING
|
||||
totalItems === 0 && status === FETCH_STATUS.LOADING
|
||||
}
|
||||
>
|
||||
<EuiBasicTable
|
||||
columns={columns}
|
||||
items={currentPageErrorGroups}
|
||||
items={items}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize: PAGE_SIZE,
|
||||
totalItemCount: items.length,
|
||||
totalItemCount: totalItems,
|
||||
pageSizeOptions: [PAGE_SIZE],
|
||||
hidePerPageOptions: true,
|
||||
}}
|
||||
|
|
|
@ -10,11 +10,15 @@ import { i18n } from '@kbn/i18n';
|
|||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { asTransactionRate } from '../../../../common/utils/formatters';
|
||||
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { useFetcher } from '../../../hooks/use_fetcher';
|
||||
import { useTheme } from '../../../hooks/use_theme';
|
||||
import { useUrlParams } from '../../../context/url_params_context/use_url_params';
|
||||
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
||||
import { TimeseriesChart } from '../../shared/charts/timeseries_chart';
|
||||
import {
|
||||
getComparisonChartTheme,
|
||||
getTimeRangeComparison,
|
||||
} from '../../shared/time_comparison/get_time_range_comparison';
|
||||
|
||||
const INITIAL_STATE = {
|
||||
currentPeriod: [],
|
||||
|
@ -29,9 +33,22 @@ export function ServiceOverviewThroughputChart({
|
|||
const theme = useTheme();
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const {
|
||||
urlParams: { environment, kuery, start, end },
|
||||
urlParams: {
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
comparisonEnabled,
|
||||
comparisonType,
|
||||
},
|
||||
} = useUrlParams();
|
||||
const { transactionType } = useApmServiceContext();
|
||||
const comparisonChartTheme = getComparisonChartTheme(theme);
|
||||
const { comparisonStart, comparisonEnd } = getTimeRangeComparison({
|
||||
start,
|
||||
end,
|
||||
comparisonType,
|
||||
});
|
||||
|
||||
const { data = INITIAL_STATE, status } = useFetcher(
|
||||
(callApmApi) => {
|
||||
|
@ -48,14 +65,49 @@ export function ServiceOverviewThroughputChart({
|
|||
start,
|
||||
end,
|
||||
transactionType,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[environment, kuery, serviceName, start, end, transactionType]
|
||||
[
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
start,
|
||||
end,
|
||||
transactionType,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
]
|
||||
);
|
||||
|
||||
const timeseries = [
|
||||
{
|
||||
data: data.currentPeriod,
|
||||
type: 'linemark',
|
||||
color: theme.eui.euiColorVis0,
|
||||
title: i18n.translate('xpack.apm.serviceOverview.throughtputChartTitle', {
|
||||
defaultMessage: 'Throughput',
|
||||
}),
|
||||
},
|
||||
...(comparisonEnabled
|
||||
? [
|
||||
{
|
||||
data: data.previousPeriod,
|
||||
type: 'area',
|
||||
color: theme.eui.euiColorLightestShade,
|
||||
title: i18n.translate(
|
||||
'xpack.apm.serviceOverview.throughtputChart.previousPeriodLabel',
|
||||
{ defaultMessage: 'Previous period' }
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiPanel>
|
||||
<EuiTitle size="xs">
|
||||
|
@ -70,18 +122,9 @@ export function ServiceOverviewThroughputChart({
|
|||
height={height}
|
||||
showAnnotations={false}
|
||||
fetchStatus={status}
|
||||
timeseries={[
|
||||
{
|
||||
data: data.currentPeriod,
|
||||
type: 'linemark',
|
||||
color: theme.eui.euiColorVis0,
|
||||
title: i18n.translate(
|
||||
'xpack.apm.serviceOverview.throughtputChartTitle',
|
||||
{ defaultMessage: 'Throughput' }
|
||||
),
|
||||
},
|
||||
]}
|
||||
timeseries={timeseries}
|
||||
yLabelFormat={asTransactionRate}
|
||||
customTheme={comparisonChartTheme}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
|
|
@ -159,14 +159,13 @@ export function getColumns({
|
|||
transactionGroupComparisonStatistics?.currentPeriod?.[name]?.impact ??
|
||||
0;
|
||||
const previousImpact =
|
||||
transactionGroupComparisonStatistics?.previousPeriod?.[name]
|
||||
?.impact ?? 0;
|
||||
transactionGroupComparisonStatistics?.previousPeriod?.[name]?.impact;
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="xs" direction="column">
|
||||
<EuiFlexItem>
|
||||
<ImpactBar value={currentImpact} size="m" />
|
||||
</EuiFlexItem>
|
||||
{comparisonEnabled && (
|
||||
{comparisonEnabled && previousImpact && (
|
||||
<EuiFlexItem>
|
||||
<ImpactBar value={previousImpact} size="s" color="subdued" />
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -9,12 +9,17 @@ import { EuiPanel, EuiTitle } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { APIReturnType } from '../../../../services/rest/createCallApmApi';
|
||||
import { asPercent } from '../../../../../common/utils/formatters';
|
||||
import { useFetcher } from '../../../../hooks/use_fetcher';
|
||||
import { useTheme } from '../../../../hooks/use_theme';
|
||||
import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
import { TimeseriesChart } from '../timeseries_chart';
|
||||
import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
|
||||
import {
|
||||
getComparisonChartTheme,
|
||||
getTimeRangeComparison,
|
||||
} from '../../time_comparison/get_time_range_comparison';
|
||||
|
||||
function yLabelFormat(y?: number | null) {
|
||||
return asPercent(y || 0, 1);
|
||||
|
@ -25,6 +30,21 @@ interface Props {
|
|||
showAnnotations?: boolean;
|
||||
}
|
||||
|
||||
type ErrorRate = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/error_rate'>;
|
||||
|
||||
const INITIAL_STATE: ErrorRate = {
|
||||
currentPeriod: {
|
||||
noHits: true,
|
||||
transactionErrorRate: [],
|
||||
average: null,
|
||||
},
|
||||
previousPeriod: {
|
||||
noHits: true,
|
||||
transactionErrorRate: [],
|
||||
average: null,
|
||||
},
|
||||
};
|
||||
|
||||
export function TransactionErrorRateChart({
|
||||
height,
|
||||
showAnnotations = true,
|
||||
|
@ -32,11 +52,25 @@ export function TransactionErrorRateChart({
|
|||
const theme = useTheme();
|
||||
const { serviceName } = useParams<{ serviceName?: string }>();
|
||||
const {
|
||||
urlParams: { environment, kuery, start, end, transactionName },
|
||||
urlParams: {
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
transactionName,
|
||||
comparisonEnabled,
|
||||
comparisonType,
|
||||
},
|
||||
} = useUrlParams();
|
||||
const { transactionType } = useApmServiceContext();
|
||||
const comparisonChartThem = getComparisonChartTheme(theme);
|
||||
const { comparisonStart, comparisonEnd } = getTimeRangeComparison({
|
||||
start,
|
||||
end,
|
||||
comparisonType,
|
||||
});
|
||||
|
||||
const { data, status } = useFetcher(
|
||||
const { data = INITIAL_STATE, status } = useFetcher(
|
||||
(callApmApi) => {
|
||||
if (transactionType && serviceName && start && end) {
|
||||
return callApmApi({
|
||||
|
@ -53,6 +87,8 @@ export function TransactionErrorRateChart({
|
|||
end,
|
||||
transactionType,
|
||||
transactionName,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -66,10 +102,34 @@ export function TransactionErrorRateChart({
|
|||
end,
|
||||
transactionType,
|
||||
transactionName,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
]
|
||||
);
|
||||
|
||||
const errorRates = data?.transactionErrorRate || [];
|
||||
const timeseries = [
|
||||
{
|
||||
data: data.currentPeriod.transactionErrorRate,
|
||||
type: 'linemark',
|
||||
color: theme.eui.euiColorVis7,
|
||||
title: i18n.translate('xpack.apm.errorRate.chart.errorRate', {
|
||||
defaultMessage: 'Error rate (avg.)',
|
||||
}),
|
||||
},
|
||||
...(comparisonEnabled
|
||||
? [
|
||||
{
|
||||
data: data.previousPeriod.transactionErrorRate,
|
||||
type: 'area',
|
||||
color: theme.eui.euiColorLightestShade,
|
||||
title: i18n.translate(
|
||||
'xpack.apm.errorRate.chart.errorRate.previousPeriodLabel',
|
||||
{ defaultMessage: 'Previous period' }
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiPanel>
|
||||
|
@ -85,18 +145,10 @@ export function TransactionErrorRateChart({
|
|||
height={height}
|
||||
showAnnotations={showAnnotations}
|
||||
fetchStatus={status}
|
||||
timeseries={[
|
||||
{
|
||||
data: errorRates,
|
||||
type: 'linemark',
|
||||
color: theme.eui.euiColorVis7,
|
||||
title: i18n.translate('xpack.apm.errorRate.chart.errorRate', {
|
||||
defaultMessage: 'Error rate (avg.)',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
timeseries={timeseries}
|
||||
yLabelFormat={yLabelFormat}
|
||||
yDomain={{ min: 0, max: 1 }}
|
||||
customTheme={comparisonChartThem}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
|
|
@ -106,11 +106,14 @@ async function getErrorStats({
|
|||
searchAggregatedTransactions: boolean;
|
||||
}) {
|
||||
return withApmSpan('get_error_rate_for_service_map_node', async () => {
|
||||
const { start, end } = setup;
|
||||
const { noHits, average } = await getErrorRate({
|
||||
environment,
|
||||
setup,
|
||||
serviceName,
|
||||
searchAggregatedTransactions,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
|
||||
return { avgErrorRate: noHits ? null : average };
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { keyBy } from 'lodash';
|
||||
import { Coordinate } from '../../../../typings/timeseries';
|
||||
import {
|
||||
ERROR_GROUP_ID,
|
||||
SERVICE_NAME,
|
||||
|
@ -16,6 +17,7 @@ import {
|
|||
rangeQuery,
|
||||
kqlQuery,
|
||||
} from '../../../../server/utils/queries';
|
||||
import { offsetPreviousPeriodCoordinates } from '../../../utils/offset_previous_period_coordinate';
|
||||
import { withApmSpan } from '../../../utils/with_apm_span';
|
||||
import { getBucketSize } from '../../helpers/get_bucket_size';
|
||||
import { Setup, SetupTimeRange } from '../../helpers/setup_request';
|
||||
|
@ -28,19 +30,23 @@ export async function getServiceErrorGroupComparisonStatistics({
|
|||
transactionType,
|
||||
groupIds,
|
||||
environment,
|
||||
start,
|
||||
end,
|
||||
}: {
|
||||
kuery?: string;
|
||||
serviceName: string;
|
||||
setup: Setup & SetupTimeRange;
|
||||
setup: Setup;
|
||||
numBuckets: number;
|
||||
transactionType: string;
|
||||
groupIds: string[];
|
||||
environment?: string;
|
||||
}) {
|
||||
start: number;
|
||||
end: number;
|
||||
}): Promise<Array<{ groupId: string; timeseries: Coordinate[] }>> {
|
||||
return withApmSpan(
|
||||
'get_service_error_group_comparison_statistics',
|
||||
async () => {
|
||||
const { apmEventClient, start, end } = setup;
|
||||
const { apmEventClient } = setup;
|
||||
|
||||
const { intervalString } = getBucketSize({ start, end, numBuckets });
|
||||
|
||||
|
@ -87,10 +93,10 @@ export async function getServiceErrorGroupComparisonStatistics({
|
|||
});
|
||||
|
||||
if (!timeseriesResponse.aggregations) {
|
||||
return {};
|
||||
return [];
|
||||
}
|
||||
|
||||
const groups = timeseriesResponse.aggregations.error_groups.buckets.map(
|
||||
return timeseriesResponse.aggregations.error_groups.buckets.map(
|
||||
(bucket) => {
|
||||
const groupId = bucket.key as string;
|
||||
return {
|
||||
|
@ -104,8 +110,76 @@ export async function getServiceErrorGroupComparisonStatistics({
|
|||
};
|
||||
}
|
||||
);
|
||||
|
||||
return keyBy(groups, 'groupId');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServiceErrorGroupPeriods({
|
||||
kuery,
|
||||
serviceName,
|
||||
setup,
|
||||
numBuckets,
|
||||
transactionType,
|
||||
groupIds,
|
||||
environment,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
}: {
|
||||
kuery?: string;
|
||||
serviceName: string;
|
||||
setup: Setup & SetupTimeRange;
|
||||
numBuckets: number;
|
||||
transactionType: string;
|
||||
groupIds: string[];
|
||||
environment?: string;
|
||||
comparisonStart?: number;
|
||||
comparisonEnd?: number;
|
||||
}) {
|
||||
const { start, end } = setup;
|
||||
|
||||
const commonProps = {
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
setup,
|
||||
numBuckets,
|
||||
transactionType,
|
||||
groupIds,
|
||||
};
|
||||
|
||||
const currentPeriodPromise = getServiceErrorGroupComparisonStatistics({
|
||||
...commonProps,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
|
||||
const previousPeriodPromise =
|
||||
comparisonStart && comparisonEnd
|
||||
? getServiceErrorGroupComparisonStatistics({
|
||||
...commonProps,
|
||||
start: comparisonStart,
|
||||
end: comparisonEnd,
|
||||
})
|
||||
: [];
|
||||
|
||||
const [currentPeriod, previousPeriod] = await Promise.all([
|
||||
currentPeriodPromise,
|
||||
previousPeriodPromise,
|
||||
]);
|
||||
|
||||
const firtCurrentPeriod = currentPeriod.length ? currentPeriod[0] : undefined;
|
||||
|
||||
return {
|
||||
currentPeriod: keyBy(currentPeriod, 'groupId'),
|
||||
previousPeriod: keyBy(
|
||||
previousPeriod.map((errorRateGroup) => ({
|
||||
...errorRateGroup,
|
||||
timeseries: offsetPreviousPeriodCoordinates({
|
||||
currentPeriodTimeseries: firtCurrentPeriod?.timeseries,
|
||||
previousPeriodTimeseries: errorRateGroup.timeseries,
|
||||
}),
|
||||
})),
|
||||
'groupId'
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
getTransactionErrorRateTimeSeries,
|
||||
} from '../helpers/transaction_error_rate';
|
||||
import { withApmSpan } from '../../utils/with_apm_span';
|
||||
import { offsetPreviousPeriodCoordinates } from '../../utils/offset_previous_period_coordinate';
|
||||
|
||||
export async function getErrorRate({
|
||||
environment,
|
||||
|
@ -40,21 +41,25 @@ export async function getErrorRate({
|
|||
transactionName,
|
||||
setup,
|
||||
searchAggregatedTransactions,
|
||||
start,
|
||||
end,
|
||||
}: {
|
||||
environment?: string;
|
||||
kuery?: string;
|
||||
serviceName: string;
|
||||
transactionType?: string;
|
||||
transactionName?: string;
|
||||
setup: Setup & SetupTimeRange;
|
||||
setup: Setup;
|
||||
searchAggregatedTransactions: boolean;
|
||||
start: number;
|
||||
end: number;
|
||||
}): Promise<{
|
||||
noHits: boolean;
|
||||
transactionErrorRate: Coordinate[];
|
||||
average: number | null;
|
||||
}> {
|
||||
return withApmSpan('get_transaction_group_error_rate', async () => {
|
||||
const { start, end, apmEventClient } = setup;
|
||||
const { apmEventClient } = setup;
|
||||
|
||||
const transactionNamefilter = transactionName
|
||||
? [{ term: { [TRANSACTION_NAME]: transactionName } }]
|
||||
|
@ -129,3 +134,67 @@ export async function getErrorRate({
|
|||
return { noHits, transactionErrorRate, average };
|
||||
});
|
||||
}
|
||||
|
||||
export async function getErrorRatePeriods({
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
transactionType,
|
||||
transactionName,
|
||||
setup,
|
||||
searchAggregatedTransactions,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
}: {
|
||||
environment?: string;
|
||||
kuery?: string;
|
||||
serviceName: string;
|
||||
transactionType?: string;
|
||||
transactionName?: string;
|
||||
setup: Setup & SetupTimeRange;
|
||||
searchAggregatedTransactions: boolean;
|
||||
comparisonStart?: number;
|
||||
comparisonEnd?: number;
|
||||
}) {
|
||||
const { start, end } = setup;
|
||||
const commonProps = {
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
transactionType,
|
||||
transactionName,
|
||||
setup,
|
||||
searchAggregatedTransactions,
|
||||
};
|
||||
|
||||
const currentPeriodPromise = getErrorRate({ ...commonProps, start, end });
|
||||
|
||||
const previousPeriodPromise =
|
||||
comparisonStart && comparisonEnd
|
||||
? getErrorRate({
|
||||
...commonProps,
|
||||
start: comparisonStart,
|
||||
end: comparisonEnd,
|
||||
})
|
||||
: { noHits: true, transactionErrorRate: [], average: null };
|
||||
|
||||
const [currentPeriod, previousPeriod] = await Promise.all([
|
||||
currentPeriodPromise,
|
||||
previousPeriodPromise,
|
||||
]);
|
||||
|
||||
const firtCurrentPeriod = currentPeriod.transactionErrorRate.length
|
||||
? currentPeriod.transactionErrorRate
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
currentPeriod,
|
||||
previousPeriod: {
|
||||
...previousPeriod,
|
||||
transactionErrorRate: offsetPreviousPeriodCoordinates({
|
||||
currentPeriodTimeseries: firtCurrentPeriod,
|
||||
previousPeriodTimeseries: previousPeriod.transactionErrorRate,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import { getServices } from '../lib/services/get_services';
|
|||
import { getServiceAgentName } from '../lib/services/get_service_agent_name';
|
||||
import { getServiceDependencies } from '../lib/services/get_service_dependencies';
|
||||
import { getServiceErrorGroupPrimaryStatistics } from '../lib/services/get_service_error_groups/get_service_error_group_primary_statistics';
|
||||
import { getServiceErrorGroupComparisonStatistics } from '../lib/services/get_service_error_groups/get_service_error_group_comparison_statistics';
|
||||
import { getServiceErrorGroupPeriods } from '../lib/services/get_service_error_groups/get_service_error_group_comparison_statistics';
|
||||
import { getServiceInstances } from '../lib/services/get_service_instances';
|
||||
import { getServiceMetadataDetails } from '../lib/services/get_service_metadata_details';
|
||||
import { getServiceMetadataIcons } from '../lib/services/get_service_metadata_icons';
|
||||
|
@ -329,6 +329,7 @@ export const serviceErrorGroupsComparisonStatisticsRoute = createRoute({
|
|||
environmentRt,
|
||||
kueryRt,
|
||||
rangeRt,
|
||||
comparisonRangeRt,
|
||||
t.type({
|
||||
numBuckets: toNumberRt,
|
||||
transactionType: t.string,
|
||||
|
@ -342,10 +343,18 @@ export const serviceErrorGroupsComparisonStatisticsRoute = createRoute({
|
|||
|
||||
const {
|
||||
path: { serviceName },
|
||||
query: { environment, kuery, numBuckets, transactionType, groupIds },
|
||||
query: {
|
||||
environment,
|
||||
kuery,
|
||||
numBuckets,
|
||||
transactionType,
|
||||
groupIds,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
},
|
||||
} = context.params;
|
||||
|
||||
return getServiceErrorGroupComparisonStatistics({
|
||||
return getServiceErrorGroupPeriods({
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
|
@ -353,6 +362,8 @@ export const serviceErrorGroupsComparisonStatisticsRoute = createRoute({
|
|||
numBuckets,
|
||||
transactionType,
|
||||
groupIds,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ import { getAnomalySeries } from '../lib/transactions/get_anomaly_data';
|
|||
import { getLatencyPeriods } 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 { getErrorRatePeriods } from '../lib/transaction_groups/get_error_rate';
|
||||
import { createRoute } from './create_route';
|
||||
import {
|
||||
comparisonRangeRt,
|
||||
|
@ -380,11 +380,9 @@ export const transactionChartsErrorRateRoute = createRoute({
|
|||
serviceName: t.string,
|
||||
}),
|
||||
query: t.intersection([
|
||||
environmentRt,
|
||||
kueryRt,
|
||||
rangeRt,
|
||||
t.type({ transactionType: t.string }),
|
||||
t.partial({ transactionName: t.string }),
|
||||
t.intersection([environmentRt, kueryRt, rangeRt, comparisonRangeRt]),
|
||||
]),
|
||||
}),
|
||||
options: { tags: ['access:apm'] },
|
||||
|
@ -397,13 +395,15 @@ export const transactionChartsErrorRateRoute = createRoute({
|
|||
kuery,
|
||||
transactionType,
|
||||
transactionName,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
} = params.query;
|
||||
|
||||
const searchAggregatedTransactions = await getSearchAggregatedTransactions(
|
||||
setup
|
||||
);
|
||||
|
||||
return getErrorRate({
|
||||
return getErrorRatePeriods({
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
|
@ -411,6 +411,8 @@ export const transactionChartsErrorRateRoute = createRoute({
|
|||
transactionName,
|
||||
setup,
|
||||
searchAggregatedTransactions,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -131,3 +131,75 @@ Object {
|
|||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`APM API tests basic apm_8.0.0 Error groups comparison statistics when data is loaded with previous data returns the correct data returns correct timeseries 1`] = `
|
||||
Object {
|
||||
"groupId": "051f95eabf120ebe2f8b0399fe3e54c5",
|
||||
"timeseries": Array [
|
||||
Object {
|
||||
"x": 1607436720000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436780000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436840000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436900000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436960000,
|
||||
"y": 0,
|
||||
},
|
||||
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": 1,
|
||||
},
|
||||
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,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import url from 'url';
|
||||
import expect from '@kbn/expect';
|
||||
import moment from 'moment';
|
||||
import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import { registry } from '../../common/registry';
|
||||
|
@ -45,8 +46,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body).to.empty();
|
||||
expect(response.body).to.be.eql({ currentPeriod: {}, previousPeriod: {} });
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -72,20 +74,23 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
expect(response.status).to.be(200);
|
||||
|
||||
const errorGroupsComparisonStatistics = response.body as ErrorGroupsComparisonStatistics;
|
||||
expect(Object.keys(errorGroupsComparisonStatistics).sort()).to.eql(groupIds.sort());
|
||||
expect(Object.keys(errorGroupsComparisonStatistics.currentPeriod).sort()).to.eql(
|
||||
groupIds.sort()
|
||||
);
|
||||
|
||||
groupIds.forEach((groupId) => {
|
||||
expect(errorGroupsComparisonStatistics[groupId]).not.to.be.empty();
|
||||
expect(errorGroupsComparisonStatistics.currentPeriod[groupId]).not.to.be.empty();
|
||||
});
|
||||
|
||||
const errorgroupsComparisonStatistics = errorGroupsComparisonStatistics[groupIds[0]];
|
||||
const errorgroupsComparisonStatistics =
|
||||
errorGroupsComparisonStatistics.currentPeriod[groupIds[0]];
|
||||
expect(
|
||||
errorgroupsComparisonStatistics.timeseries.map(({ y }) => isFinite(y)).length
|
||||
errorgroupsComparisonStatistics.timeseries.map(({ y }) => y && isFinite(y)).length
|
||||
).to.be.greaterThan(0);
|
||||
expectSnapshot(errorgroupsComparisonStatistics).toMatch();
|
||||
});
|
||||
|
||||
it('returns an empty list when requested groupIds are not available in the given time range', async () => {
|
||||
it('returns an empty state when requested groupIds are not available in the given time range', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/error_groups/comparison_statistics`,
|
||||
|
@ -100,7 +105,82 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body).to.empty();
|
||||
expect(response.body).to.be.eql({ currentPeriod: {}, previousPeriod: {} });
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
registry.when(
|
||||
'Error groups comparison statistics when data is loaded with previous data',
|
||||
{ config: 'basic', archives: [archiveName] },
|
||||
() => {
|
||||
describe('returns the correct data', async () => {
|
||||
let response: {
|
||||
status: number;
|
||||
body: ErrorGroupsComparisonStatistics;
|
||||
};
|
||||
before(async () => {
|
||||
response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/error_groups/comparison_statistics`,
|
||||
query: {
|
||||
numBuckets: 20,
|
||||
transactionType: 'request',
|
||||
groupIds: JSON.stringify(groupIds),
|
||||
start: moment(end).subtract(15, 'minutes').toISOString(),
|
||||
end,
|
||||
comparisonStart: start,
|
||||
comparisonEnd: moment(start).add(15, 'minutes').toISOString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
});
|
||||
|
||||
it('returns correct timeseries', () => {
|
||||
const errorGroupsComparisonStatistics = response.body as ErrorGroupsComparisonStatistics;
|
||||
const errorgroupsComparisonStatistics =
|
||||
errorGroupsComparisonStatistics.currentPeriod[groupIds[0]];
|
||||
expect(
|
||||
errorgroupsComparisonStatistics.timeseries.map(({ y }) => y && isFinite(y)).length
|
||||
).to.be.greaterThan(0);
|
||||
expectSnapshot(errorgroupsComparisonStatistics).toMatch();
|
||||
});
|
||||
|
||||
it('matches x-axis on current period and previous period', () => {
|
||||
const errorGroupsComparisonStatistics = response.body as ErrorGroupsComparisonStatistics;
|
||||
|
||||
const currentPeriodItems = Object.values(errorGroupsComparisonStatistics.currentPeriod);
|
||||
const previousPeriodItems = Object.values(errorGroupsComparisonStatistics.previousPeriod);
|
||||
|
||||
const currentPeriodFirstItem = currentPeriodItems[0];
|
||||
const previousPeriodFirstItem = previousPeriodItems[0];
|
||||
|
||||
expect(currentPeriodFirstItem.timeseries.map(({ x }) => x)).to.be.eql(
|
||||
previousPeriodFirstItem.timeseries.map(({ x }) => x)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an empty state when requested groupIds are not available in the given time range', async () => {
|
||||
const response = await supertest.get(
|
||||
url.format({
|
||||
pathname: `/api/apm/services/opbeans-java/error_groups/comparison_statistics`,
|
||||
query: {
|
||||
numBuckets: 20,
|
||||
transactionType: 'request',
|
||||
groupIds: JSON.stringify(['foo']),
|
||||
start: moment(end).subtract(15, 'minutes').toISOString(),
|
||||
end,
|
||||
comparisonStart: start,
|
||||
comparisonEnd: moment(start).add(15, 'minutes').toISOString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body).to.be.eql({ currentPeriod: {}, previousPeriod: {} });
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -248,3 +248,741 @@ Array [
|
|||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`APM API tests basic apm_8.0.0 Error rate when data is loaded returns the transaction error rate with comparison data has the correct error rate 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"x": 1607436770000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436780000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436790000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436800000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436810000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436820000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436830000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436840000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436850000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436860000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436870000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436880000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436890000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436900000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436910000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436920000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436930000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436940000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436950000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436960000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436970000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436980000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436990000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437000000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437010000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437020000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437030000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437040000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437050000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437060000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437070000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437080000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437090000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437100000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437110000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437120000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437130000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437140000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437150000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437160000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437170000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437180000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437190000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437200000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437210000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437220000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437230000,
|
||||
"y": 0.6,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437240000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437250000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437260000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437270000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437280000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437290000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437300000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437310000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437320000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437330000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437340000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437350000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437360000,
|
||||
"y": 0.5,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437370000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437380000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437390000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437400000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437410000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437420000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437430000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437440000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437450000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437460000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437470000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437480000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437490000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437500000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437510000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437520000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437530000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437540000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437550000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437560000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437570000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437580000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437590000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437600000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437610000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437620000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437630000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437640000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437650000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437660000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437670000,
|
||||
"y": null,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`APM API tests basic apm_8.0.0 Error rate when data is loaded returns the transaction error rate with comparison data has the correct error rate 2`] = `
|
||||
Array [
|
||||
Object {
|
||||
"x": 1607436770000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436780000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436790000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436800000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436810000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436820000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436830000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436840000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436850000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436860000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436870000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436880000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436890000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436900000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436910000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436920000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436930000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436940000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436950000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436960000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436970000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436980000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607436990000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437000000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437010000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437020000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437030000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437040000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437050000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437060000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437070000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437080000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437090000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437100000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437110000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437120000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437130000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437140000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437150000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437160000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437170000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437180000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437190000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437200000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437210000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437220000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437230000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437240000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437250000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437260000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437270000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437280000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437290000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437300000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437310000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437320000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437330000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437340000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437350000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437360000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437370000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437380000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437390000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437400000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437410000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437420000,
|
||||
"y": 0.25,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437430000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437440000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437450000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437460000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437470000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437480000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437490000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437500000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437510000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437520000,
|
||||
"y": 0.5,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437530000,
|
||||
"y": 0.2,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437540000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437550000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437560000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437570000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437580000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437590000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437600000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437610000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437620000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437630000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437640000,
|
||||
"y": null,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437650000,
|
||||
"y": 1,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437660000,
|
||||
"y": 0,
|
||||
},
|
||||
Object {
|
||||
"x": 1607437670000,
|
||||
"y": null,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
|
@ -8,10 +8,14 @@
|
|||
import expect from '@kbn/expect';
|
||||
import { first, last } from 'lodash';
|
||||
import { format } from 'url';
|
||||
import moment from 'moment';
|
||||
import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi';
|
||||
import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import { registry } from '../../common/registry';
|
||||
|
||||
type ErrorRate = APIReturnType<'GET /api/apm/services/{serviceName}/transactions/charts/error_rate'>;
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
||||
|
@ -21,20 +25,43 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
const { start, end } = archives_metadata[archiveName];
|
||||
const transactionType = 'request';
|
||||
|
||||
const url = format({
|
||||
pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate',
|
||||
query: { start, end, transactionType },
|
||||
});
|
||||
|
||||
registry.when('Error rate when data is not loaded', { config: 'basic', archives: [] }, () => {
|
||||
it('handles the empty state', async () => {
|
||||
const response = await supertest.get(url);
|
||||
const response = await supertest.get(
|
||||
format({
|
||||
pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate',
|
||||
query: { start, end, transactionType },
|
||||
})
|
||||
);
|
||||
expect(response.status).to.be(200);
|
||||
|
||||
expect(response.body.noHits).to.be(true);
|
||||
const body = response.body as ErrorRate;
|
||||
expect(body).to.be.eql({
|
||||
currentPeriod: { noHits: true, transactionErrorRate: [], average: null },
|
||||
previousPeriod: { noHits: true, transactionErrorRate: [], average: null },
|
||||
});
|
||||
});
|
||||
|
||||
expect(response.body.transactionErrorRate.length).to.be(0);
|
||||
expect(response.body.average).to.be(null);
|
||||
it('handles the empty state with comparison data', async () => {
|
||||
const response = await supertest.get(
|
||||
format({
|
||||
pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate',
|
||||
query: {
|
||||
transactionType,
|
||||
start: moment(end).subtract(15, 'minutes').toISOString(),
|
||||
end,
|
||||
comparisonStart: start,
|
||||
comparisonEnd: moment(start).add(15, 'minutes').toISOString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
expect(response.status).to.be(200);
|
||||
|
||||
const body = response.body as ErrorRate;
|
||||
expect(body).to.be.eql({
|
||||
currentPeriod: { noHits: true, transactionErrorRate: [], average: null },
|
||||
previousPeriod: { noHits: true, transactionErrorRate: [], average: null },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -43,22 +70,26 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
{ config: 'basic', archives: [archiveName] },
|
||||
() => {
|
||||
describe('returns the transaction error rate', () => {
|
||||
let errorRateResponse: {
|
||||
transactionErrorRate: Array<{ x: number; y: number | null }>;
|
||||
average: number;
|
||||
};
|
||||
let errorRateResponse: ErrorRate;
|
||||
|
||||
before(async () => {
|
||||
const response = await supertest.get(url);
|
||||
const response = await supertest.get(
|
||||
format({
|
||||
pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate',
|
||||
query: { start, end, transactionType },
|
||||
})
|
||||
);
|
||||
errorRateResponse = response.body;
|
||||
});
|
||||
|
||||
it('returns some data', () => {
|
||||
expect(errorRateResponse.average).to.be.greaterThan(0);
|
||||
expect(errorRateResponse.currentPeriod.average).to.be.greaterThan(0);
|
||||
expect(errorRateResponse.previousPeriod.average).to.be(null);
|
||||
|
||||
expect(errorRateResponse.transactionErrorRate.length).to.be.greaterThan(0);
|
||||
expect(errorRateResponse.currentPeriod.transactionErrorRate.length).to.be.greaterThan(0);
|
||||
expect(errorRateResponse.previousPeriod.transactionErrorRate).to.empty();
|
||||
|
||||
const nonNullDataPoints = errorRateResponse.transactionErrorRate.filter(
|
||||
const nonNullDataPoints = errorRateResponse.currentPeriod.transactionErrorRate.filter(
|
||||
({ y }) => y !== null
|
||||
);
|
||||
|
||||
|
@ -67,26 +98,126 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
it('has the correct start date', () => {
|
||||
expectSnapshot(
|
||||
new Date(first(errorRateResponse.transactionErrorRate)?.x ?? NaN).toISOString()
|
||||
new Date(
|
||||
first(errorRateResponse.currentPeriod.transactionErrorRate)?.x ?? NaN
|
||||
).toISOString()
|
||||
).toMatchInline(`"2020-12-08T13:57:30.000Z"`);
|
||||
});
|
||||
|
||||
it('has the correct end date', () => {
|
||||
expectSnapshot(
|
||||
new Date(last(errorRateResponse.transactionErrorRate)?.x ?? NaN).toISOString()
|
||||
new Date(
|
||||
last(errorRateResponse.currentPeriod.transactionErrorRate)?.x ?? NaN
|
||||
).toISOString()
|
||||
).toMatchInline(`"2020-12-08T14:27:30.000Z"`);
|
||||
});
|
||||
|
||||
it('has the correct number of buckets', () => {
|
||||
expectSnapshot(errorRateResponse.transactionErrorRate.length).toMatchInline(`61`);
|
||||
expectSnapshot(errorRateResponse.currentPeriod.transactionErrorRate.length).toMatchInline(
|
||||
`61`
|
||||
);
|
||||
});
|
||||
|
||||
it('has the correct calculation for average', () => {
|
||||
expectSnapshot(errorRateResponse.average).toMatchInline(`0.16`);
|
||||
expectSnapshot(errorRateResponse.currentPeriod.average).toMatchInline(`0.16`);
|
||||
});
|
||||
|
||||
it('has the correct error rate', () => {
|
||||
expectSnapshot(errorRateResponse.transactionErrorRate).toMatch();
|
||||
expectSnapshot(errorRateResponse.currentPeriod.transactionErrorRate).toMatch();
|
||||
});
|
||||
});
|
||||
|
||||
describe('returns the transaction error rate with comparison data', () => {
|
||||
let errorRateResponse: ErrorRate;
|
||||
|
||||
before(async () => {
|
||||
const response = await supertest.get(
|
||||
format({
|
||||
pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate',
|
||||
query: {
|
||||
transactionType,
|
||||
start: moment(end).subtract(15, 'minutes').toISOString(),
|
||||
end,
|
||||
comparisonStart: start,
|
||||
comparisonEnd: moment(start).add(15, 'minutes').toISOString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
errorRateResponse = response.body;
|
||||
});
|
||||
|
||||
it('returns some data', () => {
|
||||
expect(errorRateResponse.currentPeriod.average).to.be.greaterThan(0);
|
||||
expect(errorRateResponse.previousPeriod.average).to.be.greaterThan(0);
|
||||
|
||||
expect(errorRateResponse.currentPeriod.transactionErrorRate.length).to.be.greaterThan(0);
|
||||
expect(errorRateResponse.previousPeriod.transactionErrorRate.length).to.be.greaterThan(0);
|
||||
|
||||
const currentPeriodNonNullDataPoints = errorRateResponse.currentPeriod.transactionErrorRate.filter(
|
||||
({ y }) => y !== null
|
||||
);
|
||||
|
||||
const previousPeriodNonNullDataPoints = errorRateResponse.previousPeriod.transactionErrorRate.filter(
|
||||
({ y }) => y !== null
|
||||
);
|
||||
|
||||
expect(currentPeriodNonNullDataPoints.length).to.be.greaterThan(0);
|
||||
expect(previousPeriodNonNullDataPoints.length).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('has the correct start date', () => {
|
||||
expectSnapshot(
|
||||
new Date(
|
||||
first(errorRateResponse.currentPeriod.transactionErrorRate)?.x ?? NaN
|
||||
).toISOString()
|
||||
).toMatchInline(`"2020-12-08T14:12:50.000Z"`);
|
||||
expectSnapshot(
|
||||
new Date(
|
||||
first(errorRateResponse.previousPeriod.transactionErrorRate)?.x ?? NaN
|
||||
).toISOString()
|
||||
).toMatchInline(`"2020-12-08T14:12:50.000Z"`);
|
||||
});
|
||||
|
||||
it('has the correct end date', () => {
|
||||
expectSnapshot(
|
||||
new Date(
|
||||
last(errorRateResponse.currentPeriod.transactionErrorRate)?.x ?? NaN
|
||||
).toISOString()
|
||||
).toMatchInline(`"2020-12-08T14:27:50.000Z"`);
|
||||
expectSnapshot(
|
||||
new Date(
|
||||
last(errorRateResponse.previousPeriod.transactionErrorRate)?.x ?? NaN
|
||||
).toISOString()
|
||||
).toMatchInline(`"2020-12-08T14:27:50.000Z"`);
|
||||
});
|
||||
|
||||
it('has the correct number of buckets', () => {
|
||||
expectSnapshot(errorRateResponse.currentPeriod.transactionErrorRate.length).toMatchInline(
|
||||
`91`
|
||||
);
|
||||
expectSnapshot(
|
||||
errorRateResponse.previousPeriod.transactionErrorRate.length
|
||||
).toMatchInline(`91`);
|
||||
});
|
||||
|
||||
it('has the correct calculation for average', () => {
|
||||
expectSnapshot(errorRateResponse.currentPeriod.average).toMatchInline(
|
||||
`0.233333333333333`
|
||||
);
|
||||
expectSnapshot(errorRateResponse.previousPeriod.average).toMatchInline(
|
||||
`0.111111111111111`
|
||||
);
|
||||
});
|
||||
|
||||
it('has the correct error rate', () => {
|
||||
expectSnapshot(errorRateResponse.currentPeriod.transactionErrorRate).toMatch();
|
||||
expectSnapshot(errorRateResponse.previousPeriod.transactionErrorRate).toMatch();
|
||||
});
|
||||
|
||||
it('matches x-axis on current period and previous period', () => {
|
||||
expect(errorRateResponse.currentPeriod.transactionErrorRate.map(({ x }) => x)).to.be.eql(
|
||||
errorRateResponse.previousPeriod.transactionErrorRate.map(({ x }) => x)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue