mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[APM] Errors: Enhancements to the Errors list page (part II) (#118878)
* refactor errors groups endpoints * Add sparkline and reorder columns * Fix i18n * Delete is_aggregation_accurate not used * fix rebase conflicts * rename endpoint path * fix api test * fix i18n conflict * fix e2e tests * PR review * rename variables for consistency * fix tests after rename endpoints * rename functions * fix conflict * fix i18n conflict * fix i18n conflict
This commit is contained in:
parent
14ed0cb899
commit
ba9dfeafa0
24 changed files with 390 additions and 595 deletions
|
@ -91,19 +91,13 @@ describe('Errors page', () => {
|
|||
it('sorts by ocurrences', () => {
|
||||
cy.visit(javaServiceErrorsPageHref);
|
||||
cy.contains('span', 'Occurrences').click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
'&sortField=occurrenceCount&sortDirection=asc'
|
||||
);
|
||||
cy.url().should('include', '&sortField=occurrences&sortDirection=asc');
|
||||
});
|
||||
|
||||
it('sorts by latest occurrences', () => {
|
||||
cy.visit(javaServiceErrorsPageHref);
|
||||
cy.contains('span', 'Latest occurrence').click();
|
||||
cy.url().should(
|
||||
'include',
|
||||
'&sortField=latestOccurrenceAt&sortDirection=asc'
|
||||
);
|
||||
cy.contains('span', 'Last seen').click();
|
||||
cy.url().should('include', '&sortField=lastSeen&sortDirection=asc');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -48,7 +48,7 @@ const apisToIntercept = [
|
|||
},
|
||||
{
|
||||
endpoint:
|
||||
'/internal/apm/services/opbeans-node/error_groups/main_statistics?*',
|
||||
'/internal/apm/services/opbeans-node/errors/groups/main_statistics?*',
|
||||
name: 'errorGroupsMainStatisticsRequest',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -40,7 +40,7 @@ const apisToIntercept = [
|
|||
},
|
||||
{
|
||||
endpoint:
|
||||
'/internal/apm/services/opbeans-java/error_groups/detailed_statistics?*',
|
||||
'/internal/apm/services/opbeans-java/errors/groups/detailed_statistics?*',
|
||||
name: 'errorGroupsDetailedRequest',
|
||||
},
|
||||
{
|
||||
|
|
|
@ -38,52 +38,49 @@ export const Example: Story<Args> = (args) => {
|
|||
return <ErrorGroupList {...args} />;
|
||||
};
|
||||
Example.args = {
|
||||
items: [
|
||||
mainStatistics: [
|
||||
{
|
||||
message: 'net/http: abort Handler',
|
||||
occurrenceCount: 14,
|
||||
name: 'net/http: abort Handler',
|
||||
occurrences: 14,
|
||||
culprit: 'Main.func2',
|
||||
groupId: '83a653297ec29afed264d7b60d5cda7b',
|
||||
latestOccurrenceAt: '2021-10-21T16:18:41.434Z',
|
||||
lastSeen: 1634833121434,
|
||||
handled: false,
|
||||
type: 'errorString',
|
||||
},
|
||||
{
|
||||
message: 'POST /api/orders (500)',
|
||||
occurrenceCount: 5,
|
||||
name: 'POST /api/orders (500)',
|
||||
occurrences: 5,
|
||||
culprit: 'logrusMiddleware',
|
||||
groupId: '7a640436a9be648fd708703d1ac84650',
|
||||
latestOccurrenceAt: '2021-10-21T16:18:40.162Z',
|
||||
lastSeen: 1634833121434,
|
||||
handled: false,
|
||||
type: 'OpError',
|
||||
},
|
||||
{
|
||||
message:
|
||||
'write tcp 10.36.2.24:3000->10.36.1.14:34232: write: connection reset by peer',
|
||||
occurrenceCount: 4,
|
||||
name: 'write tcp 10.36.2.24:3000->10.36.1.14:34232: write: connection reset by peer',
|
||||
occurrences: 4,
|
||||
culprit: 'apiHandlers.getProductCustomers',
|
||||
groupId: '95ca0e312c109aa11e298bcf07f1445b',
|
||||
latestOccurrenceAt: '2021-10-21T16:18:42.650Z',
|
||||
lastSeen: 1634833121434,
|
||||
handled: false,
|
||||
type: 'OpError',
|
||||
},
|
||||
{
|
||||
message:
|
||||
'write tcp 10.36.0.21:3000->10.36.1.252:57070: write: connection reset by peer',
|
||||
occurrenceCount: 3,
|
||||
name: 'write tcp 10.36.0.21:3000->10.36.1.252:57070: write: connection reset by peer',
|
||||
occurrences: 3,
|
||||
culprit: 'apiHandlers.getCustomers',
|
||||
groupId: '4053d7e33d2b716c819bd96d9d6121a2',
|
||||
latestOccurrenceAt: '2021-10-21T16:07:44.078Z',
|
||||
lastSeen: 1634833121434,
|
||||
handled: false,
|
||||
type: 'OpError',
|
||||
},
|
||||
{
|
||||
message:
|
||||
'write tcp 10.36.0.21:3000->10.36.0.88:33926: write: broken pipe',
|
||||
occurrenceCount: 2,
|
||||
name: 'write tcp 10.36.0.21:3000->10.36.0.88:33926: write: broken pipe',
|
||||
occurrences: 2,
|
||||
culprit: 'apiHandlers.getOrders',
|
||||
groupId: '94f4ca8ec8c02e5318cf03f46ae4c1f3',
|
||||
latestOccurrenceAt: '2021-10-21T16:13:45.742Z',
|
||||
lastSeen: 1634833121434,
|
||||
handled: false,
|
||||
type: 'OpError',
|
||||
},
|
||||
|
@ -95,6 +92,6 @@ export const EmptyState: Story<Args> = (args) => {
|
|||
return <ErrorGroupList {...args} />;
|
||||
};
|
||||
EmptyState.args = {
|
||||
items: [],
|
||||
mainStatistics: [],
|
||||
serviceName: 'test service',
|
||||
};
|
||||
|
|
|
@ -11,9 +11,9 @@ import {
|
|||
EuiToolTip,
|
||||
RIGHT_ALIGNMENT,
|
||||
} from '@elastic/eui';
|
||||
import numeral from '@elastic/numeral';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useMemo } from 'react';
|
||||
import { asInteger } from '../../../../../common/utils/formatters';
|
||||
import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common';
|
||||
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
|
||||
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
|
||||
|
@ -24,6 +24,7 @@ import { ErrorOverviewLink } from '../../../shared/Links/apm/ErrorOverviewLink';
|
|||
import { APMQueryParams } from '../../../shared/Links/url_helpers';
|
||||
import { ITableColumn, ManagedTable } from '../../../shared/managed_table';
|
||||
import { TimestampTooltip } from '../../../shared/TimestampTooltip';
|
||||
import { SparkPlot } from '../../../shared/charts/spark_plot';
|
||||
|
||||
const GroupIdLink = euiStyled(ErrorDetailLink)`
|
||||
font-family: ${({ theme }) => theme.eui.euiCodeFontFamily};
|
||||
|
@ -48,14 +49,23 @@ const Culprit = euiStyled.div`
|
|||
`;
|
||||
|
||||
type ErrorGroupItem =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors'>['errorGroups'][0];
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>['errorGroups'][0];
|
||||
type ErrorGroupDetailedStatistics =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics'>;
|
||||
|
||||
interface Props {
|
||||
items: ErrorGroupItem[];
|
||||
mainStatistics: ErrorGroupItem[];
|
||||
serviceName: string;
|
||||
detailedStatistics: ErrorGroupDetailedStatistics;
|
||||
comparisonEnabled?: boolean;
|
||||
}
|
||||
|
||||
function ErrorGroupList({ items, serviceName }: Props) {
|
||||
function ErrorGroupList({
|
||||
mainStatistics,
|
||||
serviceName,
|
||||
detailedStatistics,
|
||||
comparisonEnabled,
|
||||
}: Props) {
|
||||
const { urlParams } = useLegacyUrlParams();
|
||||
|
||||
const columns = useMemo(() => {
|
||||
|
@ -132,13 +142,13 @@ function ErrorGroupList({ items, serviceName }: Props) {
|
|||
<MessageAndCulpritCell>
|
||||
<EuiToolTip
|
||||
id="error-message-tooltip"
|
||||
content={item.message || NOT_AVAILABLE_LABEL}
|
||||
content={item.name || NOT_AVAILABLE_LABEL}
|
||||
>
|
||||
<MessageLink
|
||||
serviceName={serviceName}
|
||||
errorGroupId={item.groupId}
|
||||
>
|
||||
{item.message || NOT_AVAILABLE_LABEL}
|
||||
{item.name || NOT_AVAILABLE_LABEL}
|
||||
</MessageLink>
|
||||
</EuiToolTip>
|
||||
<br />
|
||||
|
@ -167,46 +177,64 @@ function ErrorGroupList({ items, serviceName }: Props) {
|
|||
),
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.apm.errorsTable.occurrencesColumnLabel', {
|
||||
defaultMessage: 'Occurrences',
|
||||
field: 'lastSeen',
|
||||
sortable: true,
|
||||
name: i18n.translate('xpack.apm.errorsTable.lastSeenColumnLabel', {
|
||||
defaultMessage: 'Last seen',
|
||||
}),
|
||||
field: 'occurrenceCount',
|
||||
sortable: true,
|
||||
dataType: 'number',
|
||||
render: (_, { occurrenceCount }) =>
|
||||
occurrenceCount
|
||||
? numeral(occurrenceCount).format('0.[0]a')
|
||||
: NOT_AVAILABLE_LABEL,
|
||||
},
|
||||
{
|
||||
field: 'latestOccurrenceAt',
|
||||
sortable: true,
|
||||
name: i18n.translate(
|
||||
'xpack.apm.errorsTable.latestOccurrenceColumnLabel',
|
||||
{
|
||||
defaultMessage: 'Latest occurrence',
|
||||
}
|
||||
),
|
||||
align: RIGHT_ALIGNMENT,
|
||||
render: (_, { latestOccurrenceAt }) =>
|
||||
latestOccurrenceAt ? (
|
||||
<TimestampTooltip time={latestOccurrenceAt} timeUnit="minutes" />
|
||||
render: (_, { lastSeen }) =>
|
||||
lastSeen ? (
|
||||
<TimestampTooltip time={lastSeen} timeUnit="minutes" />
|
||||
) : (
|
||||
NOT_AVAILABLE_LABEL
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'occurrences',
|
||||
name: i18n.translate('xpack.apm.errorsTable.occurrencesColumnLabel', {
|
||||
defaultMessage: 'Occurrences',
|
||||
}),
|
||||
sortable: true,
|
||||
dataType: 'number',
|
||||
align: RIGHT_ALIGNMENT,
|
||||
render: (_, { occurrences, groupId }) => {
|
||||
const currentPeriodTimeseries =
|
||||
detailedStatistics?.currentPeriod?.[groupId]?.timeseries;
|
||||
const previousPeriodTimeseries =
|
||||
detailedStatistics?.previousPeriod?.[groupId]?.timeseries;
|
||||
return (
|
||||
<SparkPlot
|
||||
color="euiColorVis7"
|
||||
series={currentPeriodTimeseries}
|
||||
valueLabel={i18n.translate(
|
||||
'xpack.apm.serviceOveriew.errorsTableOccurrences',
|
||||
{
|
||||
defaultMessage: `{occurrences} occ.`,
|
||||
values: {
|
||||
occurrences: asInteger(occurrences),
|
||||
},
|
||||
}
|
||||
)}
|
||||
comparisonSeries={
|
||||
comparisonEnabled ? previousPeriodTimeseries : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
] as Array<ITableColumn<ErrorGroupItem>>;
|
||||
}, [serviceName, urlParams]);
|
||||
}, [serviceName, urlParams, detailedStatistics, comparisonEnabled]);
|
||||
|
||||
return (
|
||||
<ManagedTable
|
||||
noItemsMessage={i18n.translate('xpack.apm.errorsTable.noErrorsLabel', {
|
||||
defaultMessage: 'No errors found',
|
||||
})}
|
||||
items={items}
|
||||
items={mainStatistics}
|
||||
columns={columns}
|
||||
initialPageSize={25}
|
||||
initialSortField="occurrenceCount"
|
||||
initialSortField="occurrences"
|
||||
initialSortDirection="desc"
|
||||
sortItems={false}
|
||||
/>
|
||||
|
|
|
@ -14,24 +14,60 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import uuid from 'uuid';
|
||||
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
|
||||
import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context';
|
||||
import { useApmParams } from '../../../hooks/use_apm_params';
|
||||
import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher';
|
||||
import { useFetcher } from '../../../hooks/use_fetcher';
|
||||
import { useTimeRange } from '../../../hooks/use_time_range';
|
||||
import { APIReturnType } from '../../../services/rest/createCallApmApi';
|
||||
import { FailedTransactionRateChart } from '../../shared/charts/failed_transaction_rate_chart';
|
||||
import { getTimeRangeComparison } from '../../shared/time_comparison/get_time_range_comparison';
|
||||
import { ErrorDistribution } from '../error_group_details/Distribution';
|
||||
import { ErrorGroupList } from './error_group_list';
|
||||
|
||||
type ErrorGroupMainStatistics =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>;
|
||||
type ErrorGroupDetailedStatistics =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics'>;
|
||||
|
||||
const INITIAL_STATE_MAIN_STATISTICS: {
|
||||
errorGroupMainStatistics: ErrorGroupMainStatistics['errorGroups'];
|
||||
requestId?: string;
|
||||
} = {
|
||||
errorGroupMainStatistics: [],
|
||||
requestId: undefined,
|
||||
};
|
||||
|
||||
const INITIAL_STATE_DETAILED_STATISTICS: ErrorGroupDetailedStatistics = {
|
||||
currentPeriod: {},
|
||||
previousPeriod: {},
|
||||
};
|
||||
|
||||
export function ErrorGroupOverview() {
|
||||
const { serviceName } = useApmServiceContext();
|
||||
const { serviceName, transactionType } = useApmServiceContext();
|
||||
|
||||
const {
|
||||
query: { environment, kuery, sortField, sortDirection, rangeFrom, rangeTo },
|
||||
query: {
|
||||
environment,
|
||||
kuery,
|
||||
sortField,
|
||||
sortDirection,
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
comparisonType,
|
||||
comparisonEnabled,
|
||||
},
|
||||
} = useApmParams('/services/{serviceName}/errors');
|
||||
|
||||
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
|
||||
const { comparisonStart, comparisonEnd } = getTimeRangeComparison({
|
||||
start,
|
||||
end,
|
||||
comparisonType,
|
||||
comparisonEnabled,
|
||||
});
|
||||
|
||||
const { errorDistributionData, status } = useErrorGroupDistributionFetcher({
|
||||
serviceName,
|
||||
|
@ -40,30 +76,90 @@ export function ErrorGroupOverview() {
|
|||
kuery,
|
||||
});
|
||||
|
||||
const { data: errorGroupListData } = useFetcher(
|
||||
(callApmApi) => {
|
||||
const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc';
|
||||
const { data: errorGroupListData = INITIAL_STATE_MAIN_STATISTICS } =
|
||||
useFetcher(
|
||||
(callApmApi) => {
|
||||
const normalizedSortDirection =
|
||||
sortDirection === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
if (start && end) {
|
||||
return callApmApi({
|
||||
endpoint: 'GET /internal/apm/services/{serviceName}/errors',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
if (start && end && transactionType) {
|
||||
return callApmApi({
|
||||
endpoint:
|
||||
'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics',
|
||||
params: {
|
||||
path: {
|
||||
serviceName,
|
||||
},
|
||||
query: {
|
||||
environment,
|
||||
transactionType,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
sortField,
|
||||
sortDirection: normalizedSortDirection,
|
||||
},
|
||||
},
|
||||
}).then((response) => {
|
||||
return {
|
||||
// Everytime the main statistics is refetched, updates the requestId making the comparison API to be refetched.
|
||||
requestId: uuid(),
|
||||
errorGroupMainStatistics: response.errorGroups,
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
transactionType,
|
||||
start,
|
||||
end,
|
||||
sortField,
|
||||
sortDirection,
|
||||
]
|
||||
);
|
||||
|
||||
const { requestId, errorGroupMainStatistics } = errorGroupListData;
|
||||
|
||||
const {
|
||||
data: errorGroupDetailedStatistics = INITIAL_STATE_DETAILED_STATISTICS,
|
||||
} = useFetcher(
|
||||
(callApmApi) => {
|
||||
if (
|
||||
requestId &&
|
||||
errorGroupMainStatistics.length &&
|
||||
start &&
|
||||
end &&
|
||||
transactionType
|
||||
) {
|
||||
return callApmApi({
|
||||
endpoint:
|
||||
'GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
environment,
|
||||
kuery,
|
||||
start,
|
||||
end,
|
||||
sortField,
|
||||
sortDirection: normalizedSortDirection,
|
||||
numBuckets: 20,
|
||||
transactionType,
|
||||
groupIds: JSON.stringify(
|
||||
errorGroupMainStatistics.map(({ groupId }) => groupId).sort()
|
||||
),
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[environment, kuery, serviceName, start, end, sortField, sortDirection]
|
||||
// only fetches agg results when requestId changes
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[requestId],
|
||||
{ preservePreviousData: false }
|
||||
);
|
||||
|
||||
if (!errorDistributionData || !errorGroupListData) {
|
||||
|
@ -110,8 +206,10 @@ export function ErrorGroupOverview() {
|
|||
<EuiSpacer size="s" />
|
||||
|
||||
<ErrorGroupList
|
||||
items={errorGroupListData.errorGroups}
|
||||
mainStatistics={errorGroupMainStatistics}
|
||||
serviceName={serviceName}
|
||||
detailedStatistics={errorGroupDetailedStatistics}
|
||||
comparisonEnabled={comparisonEnabled}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -16,9 +16,9 @@ import { TimestampTooltip } from '../../../shared/TimestampTooltip';
|
|||
import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip';
|
||||
|
||||
type ErrorGroupMainStatistics =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>;
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>;
|
||||
type ErrorGroupDetailedStatistics =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>;
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics'>;
|
||||
|
||||
export function getColumns({
|
||||
serviceName,
|
||||
|
@ -28,14 +28,14 @@ export function getColumns({
|
|||
serviceName: string;
|
||||
errorGroupDetailedStatistics: ErrorGroupDetailedStatistics;
|
||||
comparisonEnabled?: boolean;
|
||||
}): Array<EuiBasicTableColumn<ErrorGroupMainStatistics['error_groups'][0]>> {
|
||||
}): Array<EuiBasicTableColumn<ErrorGroupMainStatistics['errorGroups'][0]>> {
|
||||
return [
|
||||
{
|
||||
field: 'name',
|
||||
name: i18n.translate('xpack.apm.serviceOverview.errorsTableColumnName', {
|
||||
defaultMessage: 'Name',
|
||||
}),
|
||||
render: (_, { name, group_id: errorGroupId }) => {
|
||||
render: (_, { name, groupId: errorGroupId }) => {
|
||||
return (
|
||||
<TruncateWithTooltip
|
||||
text={name}
|
||||
|
@ -77,7 +77,7 @@ export function getColumns({
|
|||
}
|
||||
),
|
||||
align: RIGHT_ALIGNMENT,
|
||||
render: (_, { occurrences, group_id: errorGroupId }) => {
|
||||
render: (_, { occurrences, groupId: errorGroupId }) => {
|
||||
const currentPeriodTimeseries =
|
||||
errorGroupDetailedStatistics?.currentPeriod?.[errorGroupId]
|
||||
?.timeseries;
|
||||
|
@ -92,9 +92,9 @@ export function getColumns({
|
|||
valueLabel={i18n.translate(
|
||||
'xpack.apm.serviceOveriew.errorsTableOccurrences',
|
||||
{
|
||||
defaultMessage: `{occurrencesCount} occ.`,
|
||||
defaultMessage: `{occurrences} occ.`,
|
||||
values: {
|
||||
occurrencesCount: asInteger(occurrences),
|
||||
occurrences: asInteger(occurrences),
|
||||
},
|
||||
}
|
||||
)}
|
||||
|
|
|
@ -30,9 +30,9 @@ interface Props {
|
|||
serviceName: string;
|
||||
}
|
||||
type ErrorGroupMainStatistics =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>;
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>;
|
||||
type ErrorGroupDetailedStatistics =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>;
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics'>;
|
||||
|
||||
type SortDirection = 'asc' | 'desc';
|
||||
type SortField = 'name' | 'lastSeen' | 'occurrences';
|
||||
|
@ -44,7 +44,7 @@ const DEFAULT_SORT = {
|
|||
};
|
||||
|
||||
const INITIAL_STATE_MAIN_STATISTICS: {
|
||||
items: ErrorGroupMainStatistics['error_groups'];
|
||||
items: ErrorGroupMainStatistics['errorGroups'];
|
||||
totalItems: number;
|
||||
requestId?: string;
|
||||
} = {
|
||||
|
@ -97,7 +97,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
|
|||
}
|
||||
return callApmApi({
|
||||
endpoint:
|
||||
'GET /internal/apm/services/{serviceName}/error_groups/main_statistics',
|
||||
'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
|
@ -110,7 +110,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
|
|||
},
|
||||
}).then((response) => {
|
||||
const currentPageErrorGroups = orderBy(
|
||||
response.error_groups,
|
||||
response.errorGroups,
|
||||
field,
|
||||
direction
|
||||
).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE);
|
||||
|
@ -119,7 +119,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
|
|||
// Everytime the main statistics is refetched, updates the requestId making the comparison API to be refetched.
|
||||
requestId: uuid(),
|
||||
items: currentPageErrorGroups,
|
||||
totalItems: response.error_groups.length,
|
||||
totalItems: response.errorGroups.length,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
@ -150,7 +150,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
|
|||
if (requestId && items.length && start && end && transactionType) {
|
||||
return callApmApi({
|
||||
endpoint:
|
||||
'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics',
|
||||
'GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics',
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
|
@ -161,7 +161,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
|
|||
numBuckets: 20,
|
||||
transactionType,
|
||||
groupIds: JSON.stringify(
|
||||
items.map(({ group_id: groupId }) => groupId).sort()
|
||||
items.map(({ groupId: groupId }) => groupId).sort()
|
||||
),
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
|
|
|
@ -78,11 +78,9 @@ Object {
|
|||
"@timestamp",
|
||||
],
|
||||
"size": 1,
|
||||
"sort": Array [
|
||||
Object {
|
||||
"@timestamp": "desc",
|
||||
},
|
||||
],
|
||||
"sort": Object {
|
||||
"@timestamp": "desc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -103,6 +101,11 @@ Object {
|
|||
"service.name": "serviceName",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"transaction.type": "request",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
|
@ -120,7 +123,7 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`error queries fetches multiple error groups when sortField = latestOccurrenceAt 1`] = `
|
||||
exports[`error queries fetches multiple error groups when sortField = lastSeen 1`] = `
|
||||
Object {
|
||||
"apm": Object {
|
||||
"events": Array [
|
||||
|
@ -148,11 +151,9 @@ Object {
|
|||
"@timestamp",
|
||||
],
|
||||
"size": 1,
|
||||
"sort": Array [
|
||||
Object {
|
||||
"@timestamp": "desc",
|
||||
},
|
||||
],
|
||||
"sort": Object {
|
||||
"@timestamp": "desc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -173,6 +174,11 @@ Object {
|
|||
"service.name": "serviceName",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"term": Object {
|
||||
"transaction.type": "request",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"range": Object {
|
||||
"@timestamp": Object {
|
||||
|
|
|
@ -1,121 +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 { AggregationsTermsAggregationOrder } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { ProcessorEvent } from '../../../common/processor_event';
|
||||
import { environmentQuery } from '../../../common/utils/environment_query';
|
||||
import { kqlQuery, rangeQuery } from '../../../../observability/server';
|
||||
import {
|
||||
ERROR_CULPRIT,
|
||||
ERROR_EXC_HANDLED,
|
||||
ERROR_EXC_MESSAGE,
|
||||
ERROR_EXC_TYPE,
|
||||
ERROR_GROUP_ID,
|
||||
ERROR_LOG_MESSAGE,
|
||||
SERVICE_NAME,
|
||||
} from '../../../common/elasticsearch_fieldnames';
|
||||
import { getErrorName } from '../../lib/helpers/get_error_name';
|
||||
import { Setup } from '../../lib/helpers/setup_request';
|
||||
|
||||
export async function getErrorGroups({
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
sortField,
|
||||
sortDirection = 'desc',
|
||||
setup,
|
||||
start,
|
||||
end,
|
||||
}: {
|
||||
environment: string;
|
||||
kuery: string;
|
||||
serviceName: string;
|
||||
sortField?: string;
|
||||
sortDirection?: 'asc' | 'desc';
|
||||
setup: Setup;
|
||||
start: number;
|
||||
end: number;
|
||||
}) {
|
||||
const { apmEventClient } = setup;
|
||||
|
||||
// sort buckets by last occurrence of error
|
||||
const sortByLatestOccurrence = sortField === 'latestOccurrenceAt';
|
||||
|
||||
const maxTimestampAggKey = 'max_timestamp';
|
||||
const order: AggregationsTermsAggregationOrder = sortByLatestOccurrence
|
||||
? { [maxTimestampAggKey]: sortDirection }
|
||||
: { _count: sortDirection };
|
||||
|
||||
const params = {
|
||||
apm: {
|
||||
events: [ProcessorEvent.error as const],
|
||||
},
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
...rangeQuery(start, end),
|
||||
...environmentQuery(environment),
|
||||
...kqlQuery(kuery),
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
error_groups: {
|
||||
terms: {
|
||||
field: ERROR_GROUP_ID,
|
||||
size: 500,
|
||||
order,
|
||||
},
|
||||
aggs: {
|
||||
sample: {
|
||||
top_hits: {
|
||||
_source: [
|
||||
ERROR_LOG_MESSAGE,
|
||||
ERROR_EXC_MESSAGE,
|
||||
ERROR_EXC_HANDLED,
|
||||
ERROR_EXC_TYPE,
|
||||
ERROR_CULPRIT,
|
||||
ERROR_GROUP_ID,
|
||||
'@timestamp',
|
||||
],
|
||||
sort: [{ '@timestamp': 'desc' as const }],
|
||||
size: 1,
|
||||
},
|
||||
},
|
||||
...(sortByLatestOccurrence
|
||||
? { [maxTimestampAggKey]: { max: { field: '@timestamp' } } }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const resp = await apmEventClient.search('get_error_groups', params);
|
||||
|
||||
// aggregations can be undefined when no matching indices are found.
|
||||
// this is an exception rather than the rule so the ES type does not account for this.
|
||||
const hits = (resp.aggregations?.error_groups.buckets || []).map((bucket) => {
|
||||
const source = bucket.sample.hits.hits[0]._source;
|
||||
const message = getErrorName(source);
|
||||
|
||||
return {
|
||||
message,
|
||||
occurrenceCount: bucket.doc_count,
|
||||
culprit: source.error.culprit,
|
||||
groupId: source.error.grouping_key,
|
||||
latestOccurrenceAt: source['@timestamp'],
|
||||
handled: source.error.exception?.[0].handled,
|
||||
type: source.error.exception?.[0].type,
|
||||
};
|
||||
});
|
||||
|
||||
return hits;
|
||||
}
|
|
@ -18,7 +18,7 @@ import { environmentQuery } from '../../../../common/utils/environment_query';
|
|||
import { getBucketSize } from '../../../lib/helpers/get_bucket_size';
|
||||
import { Setup } from '../../../lib/helpers/setup_request';
|
||||
|
||||
export async function getServiceErrorGroupDetailedStatistics({
|
||||
export async function getErrorGroupDetailedStatistics({
|
||||
kuery,
|
||||
serviceName,
|
||||
setup,
|
||||
|
@ -106,7 +106,7 @@ export async function getServiceErrorGroupDetailedStatistics({
|
|||
});
|
||||
}
|
||||
|
||||
export async function getServiceErrorGroupPeriods({
|
||||
export async function getErrorGroupPeriods({
|
||||
kuery,
|
||||
serviceName,
|
||||
setup,
|
||||
|
@ -141,7 +141,7 @@ export async function getServiceErrorGroupPeriods({
|
|||
groupIds,
|
||||
};
|
||||
|
||||
const currentPeriodPromise = getServiceErrorGroupDetailedStatistics({
|
||||
const currentPeriodPromise = getErrorGroupDetailedStatistics({
|
||||
...commonProps,
|
||||
start,
|
||||
end,
|
||||
|
@ -149,7 +149,7 @@ export async function getServiceErrorGroupPeriods({
|
|||
|
||||
const previousPeriodPromise =
|
||||
comparisonStart && comparisonEnd
|
||||
? getServiceErrorGroupDetailedStatistics({
|
||||
? getErrorGroupDetailedStatistics({
|
||||
...commonProps,
|
||||
start: comparisonStart,
|
||||
end: comparisonEnd,
|
|
@ -5,9 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { AggregationsTermsAggregationOrder } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { kqlQuery, rangeQuery } from '../../../../../observability/server';
|
||||
import {
|
||||
ERROR_CULPRIT,
|
||||
ERROR_EXC_HANDLED,
|
||||
ERROR_EXC_MESSAGE,
|
||||
ERROR_EXC_TYPE,
|
||||
ERROR_GROUP_ID,
|
||||
ERROR_LOG_MESSAGE,
|
||||
SERVICE_NAME,
|
||||
|
@ -18,27 +22,40 @@ import { environmentQuery } from '../../../../common/utils/environment_query';
|
|||
import { getErrorName } from '../../../lib/helpers/get_error_name';
|
||||
import { Setup } from '../../../lib/helpers/setup_request';
|
||||
|
||||
export async function getServiceErrorGroupMainStatistics({
|
||||
export async function getErrorGroupMainStatistics({
|
||||
kuery,
|
||||
serviceName,
|
||||
setup,
|
||||
transactionType,
|
||||
environment,
|
||||
transactionType,
|
||||
sortField,
|
||||
sortDirection = 'desc',
|
||||
start,
|
||||
end,
|
||||
}: {
|
||||
kuery: string;
|
||||
serviceName: string;
|
||||
setup: Setup;
|
||||
transactionType: string;
|
||||
environment: string;
|
||||
transactionType: string;
|
||||
sortField?: string;
|
||||
sortDirection?: 'asc' | 'desc';
|
||||
start: number;
|
||||
end: number;
|
||||
}) {
|
||||
const { apmEventClient } = setup;
|
||||
|
||||
// sort buckets by last occurrence of error
|
||||
const sortByLatestOccurrence = sortField === 'lastSeen';
|
||||
|
||||
const maxTimestampAggKey = 'max_timestamp';
|
||||
|
||||
const order: AggregationsTermsAggregationOrder = sortByLatestOccurrence
|
||||
? { [maxTimestampAggKey]: sortDirection }
|
||||
: { _count: sortDirection };
|
||||
|
||||
const response = await apmEventClient.search(
|
||||
'get_service_error_group_main_statistics',
|
||||
'get_error_group_main_statistics',
|
||||
{
|
||||
apm: {
|
||||
events: [ProcessorEvent.error],
|
||||
|
@ -61,20 +78,30 @@ export async function getServiceErrorGroupMainStatistics({
|
|||
terms: {
|
||||
field: ERROR_GROUP_ID,
|
||||
size: 500,
|
||||
order: {
|
||||
_count: 'desc',
|
||||
},
|
||||
order,
|
||||
},
|
||||
aggs: {
|
||||
sample: {
|
||||
// change to top_metrics
|
||||
top_hits: {
|
||||
size: 1,
|
||||
_source: [ERROR_LOG_MESSAGE, ERROR_EXC_MESSAGE, '@timestamp'],
|
||||
_source: [
|
||||
ERROR_LOG_MESSAGE,
|
||||
ERROR_EXC_MESSAGE,
|
||||
ERROR_EXC_HANDLED,
|
||||
ERROR_EXC_TYPE,
|
||||
ERROR_CULPRIT,
|
||||
ERROR_GROUP_ID,
|
||||
'@timestamp',
|
||||
],
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
...(sortByLatestOccurrence
|
||||
? { [maxTimestampAggKey]: { max: { field: '@timestamp' } } }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -82,19 +109,17 @@ export async function getServiceErrorGroupMainStatistics({
|
|||
}
|
||||
);
|
||||
|
||||
const errorGroups =
|
||||
return (
|
||||
response.aggregations?.error_groups.buckets.map((bucket) => ({
|
||||
group_id: bucket.key as string,
|
||||
groupId: bucket.key as string,
|
||||
name: getErrorName(bucket.sample.hits.hits[0]._source),
|
||||
lastSeen: new Date(
|
||||
bucket.sample.hits.hits[0]?._source['@timestamp']
|
||||
).getTime(),
|
||||
occurrences: bucket.doc_count,
|
||||
})) ?? [];
|
||||
|
||||
return {
|
||||
is_aggregation_accurate:
|
||||
(response.aggregations?.error_groups.sum_other_doc_count ?? 0) === 0,
|
||||
error_groups: errorGroups,
|
||||
};
|
||||
culprit: bucket.sample.hits.hits[0]?._source.error.culprit,
|
||||
handled: bucket.sample.hits.hits[0]?._source.error.exception?.[0].handled,
|
||||
type: bucket.sample.hits.hits[0]?._source.error.exception?.[0].type,
|
||||
})) ?? []
|
||||
);
|
||||
}
|
|
@ -5,17 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { asMutableArray } from '../../../common/utils/as_mutable_array';
|
||||
import { asMutableArray } from '../../../../common/utils/as_mutable_array';
|
||||
import {
|
||||
ERROR_GROUP_ID,
|
||||
SERVICE_NAME,
|
||||
TRANSACTION_SAMPLED,
|
||||
} from '../../../common/elasticsearch_fieldnames';
|
||||
import { ProcessorEvent } from '../../../common/processor_event';
|
||||
import { rangeQuery, kqlQuery } from '../../../../observability/server';
|
||||
import { environmentQuery } from '../../../common/utils/environment_query';
|
||||
import { Setup } from '../../lib/helpers/setup_request';
|
||||
import { getTransaction } from '../transactions/get_transaction';
|
||||
} from '../../../../common/elasticsearch_fieldnames';
|
||||
import { ProcessorEvent } from '../../../../common/processor_event';
|
||||
import { rangeQuery, kqlQuery } from '../../../../../observability/server';
|
||||
import { environmentQuery } from '../../../../common/utils/environment_query';
|
||||
import { getTransaction } from '../../transactions/get_transaction';
|
||||
import { Setup } from '../../../lib/helpers/setup_request';
|
||||
|
||||
export async function getErrorGroupSample({
|
||||
environment,
|
|
@ -5,13 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getErrorGroupSample } from './get_error_group_sample';
|
||||
import { getErrorGroups } from './get_error_groups';
|
||||
import {
|
||||
SearchParamsMock,
|
||||
inspectSearchParams,
|
||||
} from '../../utils/test_helpers';
|
||||
import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
|
||||
import { getErrorGroupMainStatistics } from './get_error_groups/get_error_group_main_statistics';
|
||||
import { getErrorGroupSample } from './get_error_groups/get_error_group_sample';
|
||||
|
||||
describe('error queries', () => {
|
||||
let mock: SearchParamsMock;
|
||||
|
@ -38,10 +38,11 @@ describe('error queries', () => {
|
|||
|
||||
it('fetches multiple error groups', async () => {
|
||||
mock = await inspectSearchParams((setup) =>
|
||||
getErrorGroups({
|
||||
getErrorGroupMainStatistics({
|
||||
sortDirection: 'asc',
|
||||
sortField: 'foo',
|
||||
serviceName: 'serviceName',
|
||||
transactionType: 'request',
|
||||
setup,
|
||||
environment: ENVIRONMENT_ALL.value,
|
||||
kuery: '',
|
||||
|
@ -53,12 +54,13 @@ describe('error queries', () => {
|
|||
expect(mock.params).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('fetches multiple error groups when sortField = latestOccurrenceAt', async () => {
|
||||
it('fetches multiple error groups when sortField = lastSeen', async () => {
|
||||
mock = await inspectSearchParams((setup) =>
|
||||
getErrorGroups({
|
||||
getErrorGroupMainStatistics({
|
||||
sortDirection: 'asc',
|
||||
sortField: 'latestOccurrenceAt',
|
||||
sortField: 'lastSeen',
|
||||
serviceName: 'serviceName',
|
||||
transactionType: 'request',
|
||||
setup,
|
||||
environment: ENVIRONMENT_ALL.value,
|
||||
kuery: '',
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { toNumberRt } from '@kbn/io-ts-utils/to_number_rt';
|
||||
import { jsonRt } from '@kbn/io-ts-utils/json_rt';
|
||||
import * as t from 'io-ts';
|
||||
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
|
||||
import { getErrorDistribution } from './distribution/get_distribution';
|
||||
import { getErrorGroupSample } from './get_error_group_sample';
|
||||
import { getErrorGroups } from './get_error_groups';
|
||||
import { setupRequest } from '../../lib/helpers/setup_request';
|
||||
import {
|
||||
environmentRt,
|
||||
|
@ -18,9 +18,13 @@ import {
|
|||
comparisonRangeRt,
|
||||
} from '../default_api_types';
|
||||
import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository';
|
||||
import { getErrorGroupMainStatistics } from './get_error_groups/get_error_group_main_statistics';
|
||||
import { getErrorGroupPeriods } from './get_error_groups/get_error_group_detailed_statistics';
|
||||
import { getErrorGroupSample } from './get_error_groups/get_error_group_sample';
|
||||
|
||||
const errorsRoute = createApmServerRoute({
|
||||
endpoint: 'GET /internal/apm/services/{serviceName}/errors',
|
||||
const errorsMainStatisticsRoute = createApmServerRoute({
|
||||
endpoint:
|
||||
'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics',
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
serviceName: t.string,
|
||||
|
@ -33,6 +37,9 @@ const errorsRoute = createApmServerRoute({
|
|||
environmentRt,
|
||||
kueryRt,
|
||||
rangeRt,
|
||||
t.type({
|
||||
transactionType: t.string,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
options: { tags: ['access:apm'] },
|
||||
|
@ -40,13 +47,21 @@ const errorsRoute = createApmServerRoute({
|
|||
const { params } = resources;
|
||||
const setup = await setupRequest(resources);
|
||||
const { serviceName } = params.path;
|
||||
const { environment, kuery, sortField, sortDirection, start, end } =
|
||||
params.query;
|
||||
const {
|
||||
environment,
|
||||
transactionType,
|
||||
kuery,
|
||||
sortField,
|
||||
sortDirection,
|
||||
start,
|
||||
end,
|
||||
} = params.query;
|
||||
|
||||
const errorGroups = await getErrorGroups({
|
||||
const errorGroups = await getErrorGroupMainStatistics({
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
transactionType,
|
||||
sortField,
|
||||
sortDirection,
|
||||
setup,
|
||||
|
@ -58,6 +73,61 @@ const errorsRoute = createApmServerRoute({
|
|||
},
|
||||
});
|
||||
|
||||
const errorsDetailedStatisticsRoute = createApmServerRoute({
|
||||
endpoint:
|
||||
'GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics',
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
serviceName: t.string,
|
||||
}),
|
||||
query: t.intersection([
|
||||
environmentRt,
|
||||
kueryRt,
|
||||
rangeRt,
|
||||
comparisonRangeRt,
|
||||
t.type({
|
||||
numBuckets: toNumberRt,
|
||||
transactionType: t.string,
|
||||
groupIds: jsonRt.pipe(t.array(t.string)),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
options: { tags: ['access:apm'] },
|
||||
handler: async (resources) => {
|
||||
const setup = await setupRequest(resources);
|
||||
const { params } = resources;
|
||||
|
||||
const {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
environment,
|
||||
kuery,
|
||||
numBuckets,
|
||||
transactionType,
|
||||
groupIds,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
start,
|
||||
end,
|
||||
},
|
||||
} = params;
|
||||
|
||||
return getErrorGroupPeriods({
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
setup,
|
||||
numBuckets,
|
||||
transactionType,
|
||||
groupIds,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const errorGroupsRoute = createApmServerRoute({
|
||||
endpoint: 'GET /internal/apm/services/{serviceName}/errors/{groupId}',
|
||||
params: t.type({
|
||||
|
@ -131,6 +201,7 @@ const errorDistributionRoute = createApmServerRoute({
|
|||
});
|
||||
|
||||
export const errorsRouteRepository = createApmServerRouteRepository()
|
||||
.add(errorsRoute)
|
||||
.add(errorsMainStatisticsRoute)
|
||||
.add(errorsDetailedStatisticsRoute)
|
||||
.add(errorGroupsRoute)
|
||||
.add(errorDistributionRoute);
|
||||
|
|
|
@ -1,205 +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 { orderBy } from 'lodash';
|
||||
import { ValuesType } from 'utility-types';
|
||||
import { kqlQuery, rangeQuery } from '../../../../../observability/server';
|
||||
import { PromiseReturnType } from '../../../../../observability/typings/common';
|
||||
import {
|
||||
ERROR_EXC_MESSAGE,
|
||||
ERROR_GROUP_ID,
|
||||
ERROR_LOG_MESSAGE,
|
||||
SERVICE_NAME,
|
||||
TRANSACTION_TYPE,
|
||||
} from '../../../../common/elasticsearch_fieldnames';
|
||||
import { ProcessorEvent } from '../../../../common/processor_event';
|
||||
import { environmentQuery } from '../../../../common/utils/environment_query';
|
||||
import { withApmSpan } from '../../../utils/with_apm_span';
|
||||
import { getBucketSize } from '../../../lib/helpers/get_bucket_size';
|
||||
import { getErrorName } from '../../../lib/helpers/get_error_name';
|
||||
import { Setup } from '../../../lib/helpers/setup_request';
|
||||
|
||||
export type ServiceErrorGroupItem = ValuesType<
|
||||
PromiseReturnType<typeof getServiceErrorGroups>
|
||||
>;
|
||||
|
||||
export async function getServiceErrorGroups({
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
setup,
|
||||
size,
|
||||
numBuckets,
|
||||
pageIndex,
|
||||
sortDirection,
|
||||
sortField,
|
||||
transactionType,
|
||||
start,
|
||||
end,
|
||||
}: {
|
||||
environment: string;
|
||||
kuery: string;
|
||||
serviceName: string;
|
||||
setup: Setup;
|
||||
size: number;
|
||||
pageIndex: number;
|
||||
numBuckets: number;
|
||||
sortDirection: 'asc' | 'desc';
|
||||
sortField: 'name' | 'lastSeen' | 'occurrences';
|
||||
transactionType: string;
|
||||
start: number;
|
||||
end: number;
|
||||
}) {
|
||||
return withApmSpan('get_service_error_groups', async () => {
|
||||
const { apmEventClient } = setup;
|
||||
|
||||
const { intervalString } = getBucketSize({ start, end, numBuckets });
|
||||
|
||||
const response = await apmEventClient.search(
|
||||
'get_top_service_error_groups',
|
||||
{
|
||||
apm: {
|
||||
events: [ProcessorEvent.error],
|
||||
},
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [TRANSACTION_TYPE]: transactionType } },
|
||||
...rangeQuery(start, end),
|
||||
...environmentQuery(environment),
|
||||
...kqlQuery(kuery),
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
error_groups: {
|
||||
terms: {
|
||||
field: ERROR_GROUP_ID,
|
||||
size: 500,
|
||||
order: {
|
||||
_count: 'desc',
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
sample: {
|
||||
top_hits: {
|
||||
size: 1,
|
||||
_source: [
|
||||
ERROR_LOG_MESSAGE,
|
||||
ERROR_EXC_MESSAGE,
|
||||
'@timestamp',
|
||||
] as any as string,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const errorGroups =
|
||||
response.aggregations?.error_groups.buckets.map((bucket) => ({
|
||||
group_id: bucket.key as string,
|
||||
name: getErrorName(bucket.sample.hits.hits[0]._source),
|
||||
lastSeen: new Date(
|
||||
bucket.sample.hits.hits[0]?._source['@timestamp']
|
||||
).getTime(),
|
||||
occurrences: {
|
||||
value: bucket.doc_count,
|
||||
},
|
||||
})) ?? [];
|
||||
|
||||
// Sort error groups first, and only get timeseries for data in view.
|
||||
// This is to limit the possibility of creating too many buckets.
|
||||
|
||||
const sortedAndSlicedErrorGroups = orderBy(
|
||||
errorGroups,
|
||||
(group) => {
|
||||
if (sortField === 'occurrences') {
|
||||
return group.occurrences.value;
|
||||
}
|
||||
return group[sortField];
|
||||
},
|
||||
[sortDirection]
|
||||
).slice(pageIndex * size, pageIndex * size + size);
|
||||
|
||||
const sortedErrorGroupIds = sortedAndSlicedErrorGroups.map(
|
||||
(group) => group.group_id
|
||||
);
|
||||
|
||||
const timeseriesResponse = await apmEventClient.search(
|
||||
'get_service_error_groups_timeseries',
|
||||
{
|
||||
apm: {
|
||||
events: [ProcessorEvent.error],
|
||||
},
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ terms: { [ERROR_GROUP_ID]: sortedErrorGroupIds } },
|
||||
{ term: { [SERVICE_NAME]: serviceName } },
|
||||
{ term: { [TRANSACTION_TYPE]: transactionType } },
|
||||
...rangeQuery(start, end),
|
||||
...environmentQuery(environment),
|
||||
...kqlQuery(kuery),
|
||||
],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
error_groups: {
|
||||
terms: {
|
||||
field: ERROR_GROUP_ID,
|
||||
size,
|
||||
},
|
||||
aggs: {
|
||||
timeseries: {
|
||||
date_histogram: {
|
||||
field: '@timestamp',
|
||||
fixed_interval: intervalString,
|
||||
min_doc_count: 0,
|
||||
extended_bounds: {
|
||||
min: start,
|
||||
max: end,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
total_error_groups: errorGroups.length,
|
||||
is_aggregation_accurate:
|
||||
(response.aggregations?.error_groups.sum_other_doc_count ?? 0) === 0,
|
||||
error_groups: sortedAndSlicedErrorGroups.map((errorGroup) => ({
|
||||
...errorGroup,
|
||||
occurrences: {
|
||||
...errorGroup.occurrences,
|
||||
timeseries:
|
||||
timeseriesResponse.aggregations?.error_groups.buckets
|
||||
.find((bucket) => bucket.key === errorGroup.group_id)
|
||||
?.timeseries.buckets.map((dateBucket) => ({
|
||||
x: dateBucket.key,
|
||||
y: dateBucket.doc_count,
|
||||
})) ?? null,
|
||||
},
|
||||
})),
|
||||
};
|
||||
});
|
||||
}
|
|
@ -21,9 +21,6 @@ import { getServiceAgent } from './get_service_agent';
|
|||
import { getServiceAlerts } from './get_service_alerts';
|
||||
import { getServiceDependencies } from './get_service_dependencies';
|
||||
import { getServiceInstanceMetadataDetails } from './get_service_instance_metadata_details';
|
||||
import { getServiceErrorGroupPeriods } from './get_service_error_groups/get_service_error_group_detailed_statistics';
|
||||
import { getServiceErrorGroupMainStatistics } from './get_service_error_groups/get_service_error_group_main_statistics';
|
||||
import { getServiceInstancesDetailedStatisticsPeriods } from './get_service_instances/detailed_statistics';
|
||||
import { getServiceInstancesMainStatistics } from './get_service_instances/main_statistics';
|
||||
import { getServiceMetadataDetails } from './get_service_metadata_details';
|
||||
import { getServiceMetadataIcons } from './get_service_metadata_icons';
|
||||
|
@ -47,6 +44,7 @@ import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_pr
|
|||
import { getServicesDetailedStatistics } from './get_services_detailed_statistics';
|
||||
import { getServiceDependenciesBreakdown } from './get_service_dependencies_breakdown';
|
||||
import { getBucketSizeForAggregatedTransactions } from '../../lib/helpers/get_bucket_size_for_aggregated_transactions';
|
||||
import { getServiceInstancesDetailedStatisticsPeriods } from './get_service_instances/detailed_statistics';
|
||||
|
||||
const servicesRoute = createApmServerRoute({
|
||||
endpoint: 'GET /internal/apm/services',
|
||||
|
@ -374,98 +372,6 @@ const serviceAnnotationsCreateRoute = createApmServerRoute({
|
|||
},
|
||||
});
|
||||
|
||||
const serviceErrorGroupsMainStatisticsRoute = createApmServerRoute({
|
||||
endpoint:
|
||||
'GET /internal/apm/services/{serviceName}/error_groups/main_statistics',
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
serviceName: t.string,
|
||||
}),
|
||||
query: t.intersection([
|
||||
environmentRt,
|
||||
kueryRt,
|
||||
rangeRt,
|
||||
t.type({
|
||||
transactionType: t.string,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
options: { tags: ['access:apm'] },
|
||||
handler: async (resources) => {
|
||||
const setup = await setupRequest(resources);
|
||||
const { params } = resources;
|
||||
const {
|
||||
path: { serviceName },
|
||||
query: { kuery, transactionType, environment, start, end },
|
||||
} = params;
|
||||
|
||||
return getServiceErrorGroupMainStatistics({
|
||||
kuery,
|
||||
serviceName,
|
||||
setup,
|
||||
transactionType,
|
||||
environment,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const serviceErrorGroupsDetailedStatisticsRoute = createApmServerRoute({
|
||||
endpoint:
|
||||
'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics',
|
||||
params: t.type({
|
||||
path: t.type({
|
||||
serviceName: t.string,
|
||||
}),
|
||||
query: t.intersection([
|
||||
environmentRt,
|
||||
kueryRt,
|
||||
rangeRt,
|
||||
comparisonRangeRt,
|
||||
t.type({
|
||||
numBuckets: toNumberRt,
|
||||
transactionType: t.string,
|
||||
groupIds: jsonRt.pipe(t.array(t.string)),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
options: { tags: ['access:apm'] },
|
||||
handler: async (resources) => {
|
||||
const setup = await setupRequest(resources);
|
||||
const { params } = resources;
|
||||
|
||||
const {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
environment,
|
||||
kuery,
|
||||
numBuckets,
|
||||
transactionType,
|
||||
groupIds,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
start,
|
||||
end,
|
||||
},
|
||||
} = params;
|
||||
|
||||
return getServiceErrorGroupPeriods({
|
||||
environment,
|
||||
kuery,
|
||||
serviceName,
|
||||
setup,
|
||||
numBuckets,
|
||||
transactionType,
|
||||
groupIds,
|
||||
comparisonStart,
|
||||
comparisonEnd,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const serviceThroughputRoute = createApmServerRoute({
|
||||
endpoint: 'GET /internal/apm/services/{serviceName}/throughput',
|
||||
params: t.type({
|
||||
|
@ -952,8 +858,6 @@ export const serviceRouteRepository = createApmServerRouteRepository()
|
|||
.add(serviceNodeMetadataRoute)
|
||||
.add(serviceAnnotationsRoute)
|
||||
.add(serviceAnnotationsCreateRoute)
|
||||
.add(serviceErrorGroupsMainStatisticsRoute)
|
||||
.add(serviceErrorGroupsDetailedStatisticsRoute)
|
||||
.add(serviceInstancesMetadataDetails)
|
||||
.add(serviceThroughputRoute)
|
||||
.add(serviceInstancesMainStatisticsRoute)
|
||||
|
|
|
@ -5662,7 +5662,6 @@
|
|||
"xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "エラーメッセージと原因",
|
||||
"xpack.apm.errorsTable.groupIdColumnDescription": "スタックトレースのハッシュ。動的パラメータのため、エラーメッセージが異なる場合でも、類似したエラーをグループ化します。",
|
||||
"xpack.apm.errorsTable.groupIdColumnLabel": "グループ ID",
|
||||
"xpack.apm.errorsTable.latestOccurrenceColumnLabel": "最近のオカレンス",
|
||||
"xpack.apm.errorsTable.noErrorsLabel": "エラーが見つかりません",
|
||||
"xpack.apm.errorsTable.occurrencesColumnLabel": "オカレンス",
|
||||
"xpack.apm.errorsTable.typeColumnLabel": "型",
|
||||
|
@ -5971,7 +5970,6 @@
|
|||
"xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningText": "これらのメトリックが所属する JVM を特定できませんでした。7.5 よりも古い APM Server を実行していることが原因である可能性が高いです。この問題は APM Server 7.5 以降にアップグレードすることで解決されます。アップグレードに関する詳細は、{link} をご覧ください。代わりに Kibana クエリバーを使ってホスト名、コンテナー ID、またはその他フィールドでフィルタリングすることもできます。",
|
||||
"xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningTitle": "JVM を特定できませんでした",
|
||||
"xpack.apm.serviceNodeNameMissing": "(空)",
|
||||
"xpack.apm.serviceOveriew.errorsTableOccurrences": "{occurrencesCount} occ.",
|
||||
"xpack.apm.serviceOverview.dependenciesTableColumn": "依存関係",
|
||||
"xpack.apm.serviceOverview.dependenciesTableTabLink": "依存関係を表示",
|
||||
"xpack.apm.serviceOverview.dependenciesTableTitle": "依存関係",
|
||||
|
@ -27665,4 +27663,4 @@
|
|||
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。",
|
||||
"xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5700,8 +5700,7 @@
|
|||
"xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "错误消息和原因",
|
||||
"xpack.apm.errorsTable.groupIdColumnDescription": "堆栈跟踪的哈希。将类似错误分组在一起,即使因动态参数造成错误消息不同。",
|
||||
"xpack.apm.errorsTable.groupIdColumnLabel": "组 ID",
|
||||
"xpack.apm.errorsTable.latestOccurrenceColumnLabel": "最新一次发生",
|
||||
"xpack.apm.errorsTable.noErrorsLabel": "未找到错误",
|
||||
"xpack.apm.errorsTable.noErrorsLabel": "未找到任何错误",
|
||||
"xpack.apm.errorsTable.occurrencesColumnLabel": "发生次数",
|
||||
"xpack.apm.errorsTable.typeColumnLabel": "类型",
|
||||
"xpack.apm.errorsTable.unhandledLabel": "未处理",
|
||||
|
@ -6012,8 +6011,6 @@
|
|||
"xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningText": "无法识别这些指标属于哪些 JVM。这可能因为运行的 APM Server 版本低于 7.5。如果升级到 APM Server 7.5 或更高版本,应可解决此问题。有关升级的详细信息,请参阅 {link}。或者,也可以使用 Kibana 查询栏按主机名、容器 ID 或其他字段筛选。",
|
||||
"xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningTitle": "找不到 JVM",
|
||||
"xpack.apm.serviceNodeNameMissing": "(空)",
|
||||
"xpack.apm.serviceOveriew.errorsTableOccurrences": "{occurrencesCount} 次",
|
||||
"xpack.apm.serviceOverview.dependenciesTableColumn": "依赖项",
|
||||
"xpack.apm.serviceOverview.dependenciesTableTabLink": "查看依赖项",
|
||||
"xpack.apm.serviceOverview.dependenciesTableTitle": "依赖项",
|
||||
"xpack.apm.serviceOverview.errorsTable.errorMessage": "无法提取",
|
||||
|
|
|
@ -13,7 +13,8 @@ import {
|
|||
import { RecursivePartial } from '../../../../plugins/apm/typings/common';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
|
||||
type ErrorGroups = APIReturnType<'GET /internal/apm/services/{serviceName}/errors'>['errorGroups'];
|
||||
type ErrorGroups =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>['errorGroups'];
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const registry = getService('registry');
|
||||
|
@ -26,17 +27,18 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
async function callApi(
|
||||
overrides?: RecursivePartial<
|
||||
APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors'>['params']
|
||||
APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>['params']
|
||||
>
|
||||
) {
|
||||
return await apmApiClient.readUser({
|
||||
endpoint: `GET /internal/apm/services/{serviceName}/errors`,
|
||||
endpoint: 'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics',
|
||||
params: {
|
||||
path: { serviceName, ...overrides?.path },
|
||||
query: {
|
||||
start: new Date(start).toISOString(),
|
||||
end: new Date(end).toISOString(),
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
transactionType: 'request',
|
||||
kuery: '',
|
||||
...overrides?.query,
|
||||
},
|
||||
|
@ -133,12 +135,12 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
it('returns correct number of errors', () => {
|
||||
expect(errorGroups.length).to.equal(2);
|
||||
expect(errorGroups.map((error) => error.message).sort()).to.eql(['error 1', 'error 2']);
|
||||
expect(errorGroups.map((error) => error.name).sort()).to.eql(['error 1', 'error 2']);
|
||||
});
|
||||
|
||||
it('returns correct occurences', () => {
|
||||
const numberOfBuckets = 15;
|
||||
expect(errorGroups.map((error) => error.occurrenceCount).sort()).to.eql([
|
||||
expect(errorGroups.map((error) => error.occurrences).sort()).to.eql([
|
||||
appleTransaction.failureRate * numberOfBuckets,
|
||||
bananaTransaction.failureRate * numberOfBuckets,
|
||||
]);
|
||||
|
|
|
@ -44,7 +44,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
|
|||
{
|
||||
// this doubles as a smoke test for the _inspect query parameter
|
||||
req: {
|
||||
url: `/internal/apm/services/foo/errors?start=${start}&end=${end}&_inspect=true&environment=ENVIRONMENT_ALL&kuery=`,
|
||||
url: `/internal/apm/services/foo/errors/groups/main_statistics?start=${start}&end=${end}&_inspect=true&environment=ENVIRONMENT_ALL&transactionType=bar&kuery=`,
|
||||
},
|
||||
expectForbidden: expect403,
|
||||
expectResponse: expect200,
|
||||
|
|
|
@ -19,7 +19,7 @@ import { config, generateData } from './generate_data';
|
|||
import { getErrorGroupIds } from './get_error_group_ids';
|
||||
|
||||
type ErrorGroupsDetailedStatistics =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>;
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics'>;
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const registry = getService('registry');
|
||||
|
@ -32,11 +32,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
async function callApi(
|
||||
overrides?: RecursivePartial<
|
||||
APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics'>['params']
|
||||
APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics'>['params']
|
||||
>
|
||||
) {
|
||||
return await apmApiClient.readUser({
|
||||
endpoint: `GET /internal/apm/services/{serviceName}/error_groups/detailed_statistics`,
|
||||
endpoint: `GET /internal/apm/services/{serviceName}/errors/groups/detailed_statistics`,
|
||||
params: {
|
||||
path: { serviceName, ...overrides?.path },
|
||||
query: {
|
||||
|
|
|
@ -15,7 +15,7 @@ import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
|||
import { generateData, config } from './generate_data';
|
||||
|
||||
type ErrorGroupsMainStatistics =
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>;
|
||||
APIReturnType<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>;
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const registry = getService('registry');
|
||||
|
@ -28,11 +28,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
async function callApi(
|
||||
overrides?: RecursivePartial<
|
||||
APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/error_groups/main_statistics'>['params']
|
||||
APIClientRequestParamsOf<'GET /internal/apm/services/{serviceName}/errors/groups/main_statistics'>['params']
|
||||
>
|
||||
) {
|
||||
return await apmApiClient.readUser({
|
||||
endpoint: `GET /internal/apm/services/{serviceName}/error_groups/main_statistics`,
|
||||
endpoint: `GET /internal/apm/services/{serviceName}/errors/groups/main_statistics`,
|
||||
params: {
|
||||
path: { serviceName, ...overrides?.path },
|
||||
query: {
|
||||
|
@ -54,8 +54,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
it('handles empty state', async () => {
|
||||
const response = await callApi();
|
||||
expect(response.status).to.be(200);
|
||||
expect(response.body.error_groups).to.empty();
|
||||
expect(response.body.is_aggregation_accurate).to.eql(true);
|
||||
expect(response.body.errorGroups).to.empty();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -81,8 +80,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('returns correct number of occurrences', () => {
|
||||
expect(errorGroupMainStatistics.error_groups.length).to.equal(2);
|
||||
expect(errorGroupMainStatistics.error_groups.map((error) => error.name).sort()).to.eql([
|
||||
expect(errorGroupMainStatistics.errorGroups.length).to.equal(2);
|
||||
expect(errorGroupMainStatistics.errorGroups.map((error) => error.name).sort()).to.eql([
|
||||
ERROR_NAME_1,
|
||||
ERROR_NAME_2,
|
||||
]);
|
||||
|
@ -91,7 +90,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
it('returns correct occurences', () => {
|
||||
const numberOfBuckets = 15;
|
||||
expect(
|
||||
errorGroupMainStatistics.error_groups.map((error) => error.occurrences).sort()
|
||||
errorGroupMainStatistics.errorGroups.map((error) => error.occurrences).sort()
|
||||
).to.eql([
|
||||
PROD_LIST_ERROR_RATE * numberOfBuckets,
|
||||
PROD_ID_ERROR_RATE * numberOfBuckets,
|
||||
|
@ -99,7 +98,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('has same last seen value as end date', () => {
|
||||
errorGroupMainStatistics.error_groups.map((error) => {
|
||||
errorGroupMainStatistics.errorGroups.map((error) => {
|
||||
expect(error.lastSeen).to.equal(moment(end).startOf('minute').valueOf());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ export async function getErrorGroupIds({
|
|||
count?: number;
|
||||
}) {
|
||||
const { body } = await apmApiClient.readUser({
|
||||
endpoint: `GET /internal/apm/services/{serviceName}/error_groups/main_statistics`,
|
||||
endpoint: `GET /internal/apm/services/{serviceName}/errors/groups/main_statistics`,
|
||||
params: {
|
||||
path: { serviceName },
|
||||
query: {
|
||||
|
@ -35,5 +35,5 @@ export async function getErrorGroupIds({
|
|||
},
|
||||
});
|
||||
|
||||
return take(body.error_groups.map((group) => group.group_id).sort(), count);
|
||||
return take(body.errorGroups.map((group) => group.groupId).sort(), count);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue