[CSM] Js errors (#77919)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Shahzad 2020-09-28 15:36:38 +02:00 committed by GitHub
parent aa9774faa0
commit d7a8641f3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 665 additions and 51 deletions

View file

@ -14,16 +14,40 @@ When(/^the user filters by "([^"]*)"$/, (filterName) => {
cy.get('.euiStat__title-isLoading').should('not.be.visible');
cy.get(`#local-filter-${filterName}`).click();
if (filterName === 'os') {
cy.get('span.euiSelectableListItem__text', DEFAULT_TIMEOUT)
.contains('Mac OS X')
.click();
} else {
cy.get('span.euiSelectableListItem__text', DEFAULT_TIMEOUT)
.contains('DE')
.click();
}
cy.get('[data-cy=applyFilter]').click();
cy.get(`#local-filter-popover-${filterName}`, DEFAULT_TIMEOUT).within(() => {
if (filterName === 'os') {
const osItem = cy.get('li.euiSelectableListItem', DEFAULT_TIMEOUT).eq(2);
osItem.should('have.text', 'Mac OS X8 ');
osItem.click();
// sometimes click doesn't work as expected so we need to retry here
osItem.invoke('attr', 'aria-selected').then((val) => {
if (val === 'false') {
cy.get('li.euiSelectableListItem', DEFAULT_TIMEOUT).eq(2).click();
}
});
} else {
const deItem = cy.get('li.euiSelectableListItem', DEFAULT_TIMEOUT).eq(0);
deItem.should('have.text', 'DE28 ');
deItem.click();
// sometimes click doesn't work as expected so we need to retry here
deItem.invoke('attr', 'aria-selected').then((val) => {
if (val === 'false') {
cy.get('li.euiSelectableListItem', DEFAULT_TIMEOUT).eq(0).click();
}
});
}
cy.get('[data-cy=applyFilter]').click();
});
cy.get(`div#local-filter-values-${filterName}`, DEFAULT_TIMEOUT).within(
() => {
cy.get('span.euiBadge__content')
.eq(0)
.should('have.text', filterName === 'os' ? 'Mac OS X' : 'DE');
}
);
});
Then(/^it filters the client metrics "([^"]*)"$/, (filterName) => {

View file

@ -5,15 +5,13 @@
*/
import { When, Then } from 'cypress-cucumber-preprocessor/steps';
import { DEFAULT_TIMEOUT } from '../apm';
import { verifyClientMetrics } from './client_metrics_helper';
import { DEFAULT_TIMEOUT } from './csm_dashboard';
When('the user changes the selected service name', (filterName) => {
When('the user changes the selected service name', () => {
// wait for all loading to finish
cy.get('kbnLoadingIndicator').should('not.be.visible');
cy.get(`[data-cy=serviceNameFilter]`, { timeout: DEFAULT_TIMEOUT }).select(
'client'
);
cy.get(`[data-cy=serviceNameFilter]`, DEFAULT_TIMEOUT).select('client');
});
Then(`it displays relevant client metrics`, () => {

View file

@ -96,7 +96,7 @@ function setRumAgent(item) {
if (item.body) {
item.body = item.body.replace(
'"name":"client"',
'"name":"opbean-client-rum"'
'"name":"elastic-frontend"'
);
}
}

View file

@ -6,10 +6,12 @@
import * as React from 'react';
import numeral from '@elastic/numeral';
import styled from 'styled-components';
import { useContext, useEffect } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiToolTip } from '@elastic/eui';
import { useFetcher } from '../../../../hooks/useFetcher';
import { useUrlParams } from '../../../../hooks/useUrlParams';
import { I18LABELS } from '../translations';
import { CsmSharedContext } from '../CsmSharedContext';
const ClFlexGroup = styled(EuiFlexGroup)`
flex-direction: row;
@ -45,6 +47,12 @@ export function ClientMetrics() {
[start, end, uiFilters, searchTerm]
);
const { setSharedData } = useContext(CsmSharedContext);
useEffect(() => {
setSharedData({ totalPageViews: data?.pageViews?.value ?? 0 });
}, [data, setSharedData]);
const STAT_STYLE = { width: '240px' };
return (

View file

@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { createContext, useMemo, useState } from 'react';
interface SharedData {
totalPageViews: number;
}
interface Index {
sharedData: SharedData;
setSharedData: (data: SharedData) => void;
}
const defaultContext: Index = {
sharedData: { totalPageViews: 0 },
setSharedData: (d) => {
throw new Error(
'setSharedData was not initialized, set it when you invoke the context'
);
},
};
export const CsmSharedContext = createContext(defaultContext);
export function CsmSharedContextProvider({
children,
}: {
children: JSX.Element[];
}) {
const [newData, setNewData] = useState<SharedData>({ totalPageViews: 0 });
const setSharedData = React.useCallback((data: SharedData) => {
setNewData(data);
}, []);
const value = useMemo(() => {
return { sharedData: newData, setSharedData };
}, [newData, setSharedData]);
return <CsmSharedContext.Provider value={value} children={children} />;
}

View file

@ -0,0 +1,138 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useContext, useState } from 'react';
import {
EuiBasicTable,
EuiFlexItem,
EuiFlexGroup,
EuiSpacer,
EuiTitle,
EuiStat,
EuiToolTip,
} from '@elastic/eui';
import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
import { useUrlParams } from '../../../../hooks/useUrlParams';
import { useFetcher } from '../../../../hooks/useFetcher';
import { I18LABELS } from '../translations';
import { CsmSharedContext } from '../CsmSharedContext';
export function JSErrors() {
const { urlParams, uiFilters } = useUrlParams();
const { start, end, serviceName } = urlParams;
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 5 });
const { data, status } = useFetcher(
(callApmApi) => {
if (start && end && serviceName) {
return callApmApi({
pathname: '/api/apm/rum-client/js-errors',
params: {
query: {
start,
end,
uiFilters: JSON.stringify(uiFilters),
pageSize: String(pagination.pageSize),
pageIndex: String(pagination.pageIndex),
},
},
});
}
return Promise.resolve(null);
},
[start, end, serviceName, uiFilters, pagination]
);
const {
sharedData: { totalPageViews },
} = useContext(CsmSharedContext);
const items = (data?.items ?? []).map(({ errorMessage, count }) => ({
errorMessage,
percent: i18n.translate('xpack.apm.rum.jsErrors.percent', {
defaultMessage: '{pageLoadPercent} %',
values: { pageLoadPercent: ((count / totalPageViews) * 100).toFixed(1) },
}),
}));
const cols = [
{
field: 'errorMessage',
name: I18LABELS.errorMessage,
},
{
name: I18LABELS.impactedPageLoads,
field: 'percent',
align: 'right' as const,
},
];
const onTableChange = ({
page,
}: {
page: { size: number; index: number };
}) => {
setPagination({
pageIndex: page.index,
pageSize: page.size,
});
};
return (
<>
<EuiTitle size="xs">
<h3>{I18LABELS.jsErrors}</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiStat
titleSize="s"
title={
<EuiToolTip content={data?.totalErrors ?? 0}>
<>{numeral(data?.totalErrors ?? 0).format('0 a')}</>
</EuiToolTip>
}
description={I18LABELS.totalErrors}
isLoading={status !== 'success'}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiStat
titleSize="s"
title={i18n.translate('xpack.apm.rum.jsErrors.errorRateValue', {
defaultMessage: '{errorRate} %',
values: {
errorRate: (
((data?.totalErrorPages ?? 0) / totalPageViews) *
100
).toFixed(0),
},
})}
description={I18LABELS.errorRate}
isLoading={status !== 'success'}
/>
</EuiFlexItem>{' '}
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiBasicTable
loading={status !== 'success'}
responsive={false}
compressed={true}
columns={cols}
items={items}
onChange={onTableChange}
pagination={{
...pagination,
totalItemCount: data?.totalErrorGroups ?? 0,
}}
/>
</>
);
}

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { EuiFlexItem, EuiPanel, EuiFlexGroup, EuiSpacer } from '@elastic/eui';
import { JSErrors } from './JSErrors';
export function ImpactfulMetrics() {
return (
<EuiPanel>
<EuiSpacer size="xs" />
<EuiFlexGroup wrap>
<EuiFlexItem style={{ flexBasis: 650 }}>
<JSErrors />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
}

View file

@ -19,6 +19,7 @@ import { I18LABELS } from './translations';
import { VisitorBreakdown } from './VisitorBreakdown';
import { UXMetrics } from './UXMetrics';
import { VisitorBreakdownMap } from './VisitorBreakdownMap';
import { ImpactfulMetrics } from './ImpactfulMetrics';
export function RumDashboard() {
return (
@ -66,6 +67,9 @@ export function RumDashboard() {
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<ImpactfulMetrics />
</EuiFlexItem>
</EuiFlexGroup>
);
}

View file

@ -9,6 +9,7 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { RumOverview } from '../RumDashboard';
import { RumHeader } from './RumHeader';
import { CsmSharedContextProvider } from './CsmSharedContext';
export const UX_LABEL = i18n.translate('xpack.apm.ux.title', {
defaultMessage: 'User Experience',
@ -17,16 +18,18 @@ export const UX_LABEL = i18n.translate('xpack.apm.ux.title', {
export function RumHome() {
return (
<div>
<RumHeader>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiTitle size="l">
<h1>{UX_LABEL}</h1>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</RumHeader>
<RumOverview />
<CsmSharedContextProvider>
<RumHeader>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiTitle size="l">
<h1>{UX_LABEL}</h1>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</RumHeader>
<RumOverview />
</CsmSharedContextProvider>
</div>
);
}

View file

@ -37,6 +37,18 @@ export const I18LABELS = {
defaultMessage: 'Page load distribution',
}
),
jsErrors: i18n.translate(
'xpack.apm.rum.dashboard.impactfulMetrics.jsErrors',
{
defaultMessage: 'JavaScript errors',
}
),
highTrafficPages: i18n.translate(
'xpack.apm.rum.dashboard.impactfulMetrics.highTrafficPages',
{
defaultMessage: 'High traffic pages',
}
),
resetZoom: i18n.translate('xpack.apm.rum.dashboard.resetZoom.label', {
defaultMessage: 'Reset zoom',
}),
@ -105,6 +117,21 @@ export const I18LABELS = {
noResults: i18n.translate('xpack.apm.rum.filters.url.noResults', {
defaultMessage: 'No results available',
}),
totalErrors: i18n.translate('xpack.apm.rum.jsErrors.totalErrors', {
defaultMessage: 'Total errors',
}),
errorRate: i18n.translate('xpack.apm.rum.jsErrors.errorRate', {
defaultMessage: 'Error rate',
}),
errorMessage: i18n.translate('xpack.apm.rum.jsErrors.errorMessage', {
defaultMessage: 'Error message',
}),
impactedPageLoads: i18n.translate(
'xpack.apm.rum.jsErrors.impactedPageLoads',
{
defaultMessage: 'Impacted page loads',
}
),
};
export const VisitorBreakdownLabel = i18n.translate(

View file

@ -19,6 +19,7 @@ const BadgeText = styled.div`
interface Props {
value: string[];
onRemove: (val: string) => void;
name: string;
}
const removeFilterLabel = i18n.translate(
@ -26,9 +27,9 @@ const removeFilterLabel = i18n.translate(
{ defaultMessage: 'Remove filter' }
);
function FilterBadgeList({ onRemove, value }: Props) {
function FilterBadgeList({ onRemove, value, name }: Props) {
return (
<EuiFlexGrid gutterSize="s">
<EuiFlexGrid gutterSize="s" id={`local-filter-values-${name}`}>
{value.map((val) => (
<EuiFlexItem key={val} grow={false}>
<EuiBadge

View file

@ -113,7 +113,7 @@ function Filter({ name, title, options, onChange, value, showCount }: Props) {
searchable={true}
>
{(list, search) => (
<SelectContainer>
<SelectContainer id={`local-filter-popover-${name}`}>
<EuiFlexGroup direction="column" gutterSize="none">
<FlexItem grow={true}>
<EuiTitle size="xxxs" textTransform="uppercase">
@ -159,6 +159,7 @@ function Filter({ name, title, options, onChange, value, showCount }: Props) {
{value.length ? (
<>
<FilterBadgeList
name={name}
onRemove={(val) => {
onChange(value.filter((v) => val !== v));
}}

View file

@ -72,6 +72,97 @@ Object {
}
`;
exports[`rum client dashboard queries fetches js errors 1`] = `
Object {
"apm": Object {
"events": Array [
"error",
],
},
"body": Object {
"aggs": Object {
"errors": Object {
"aggs": Object {
"bucket_truncate": Object {
"bucket_sort": Object {
"from": 0,
"size": 5,
},
},
"sample": Object {
"top_hits": Object {
"_source": Array [
"error.exception.message",
"error.exception.type",
"error.grouping_key",
"@timestamp",
],
"size": 1,
"sort": Array [
Object {
"@timestamp": "desc",
},
],
},
},
},
"terms": Object {
"field": "error.grouping_key",
"size": 500,
},
},
"totalErrorGroups": Object {
"cardinality": Object {
"field": "error.grouping_key",
},
},
"totalErrorPages": Object {
"cardinality": Object {
"field": "transaction.id",
},
},
},
"query": Object {
"bool": Object {
"filter": Array [
Object {
"range": Object {
"@timestamp": Object {
"format": "epoch_millis",
"gte": 1528113600000,
"lte": 1528977600000,
},
},
},
Object {
"term": Object {
"agent.name": "rum-js",
},
},
Object {
"term": Object {
"transaction.type": "page-load",
},
},
Object {
"term": Object {
"service.language.name": "javascript",
},
},
Object {
"term": Object {
"my.custom.ui.filter": "foo-bar",
},
},
],
},
},
"size": 0,
"track_total_hits": true,
},
}
`;
exports[`rum client dashboard queries fetches long task metrics 1`] = `
Object {
"apm": Object {

View file

@ -0,0 +1,99 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { mergeProjection } from '../../projections/util/merge_projection';
import {
Setup,
SetupTimeRange,
SetupUIFilters,
} from '../helpers/setup_request';
import { getRumErrorsProjection } from '../../projections/rum_page_load_transactions';
import {
ERROR_EXC_MESSAGE,
ERROR_EXC_TYPE,
ERROR_GROUP_ID,
TRANSACTION_ID,
} from '../../../common/elasticsearch_fieldnames';
export async function getJSErrors({
setup,
pageSize,
pageIndex,
}: {
setup: Setup & SetupTimeRange & SetupUIFilters;
pageSize: number;
pageIndex: number;
}) {
const projection = getRumErrorsProjection({
setup,
});
const params = mergeProjection(projection, {
body: {
size: 0,
track_total_hits: true,
aggs: {
totalErrorGroups: {
cardinality: {
field: ERROR_GROUP_ID,
},
},
totalErrorPages: {
cardinality: {
field: TRANSACTION_ID,
},
},
errors: {
terms: {
field: ERROR_GROUP_ID,
size: 500,
},
aggs: {
bucket_truncate: {
bucket_sort: {
size: pageSize,
from: pageIndex * pageSize,
},
},
sample: {
top_hits: {
_source: [
ERROR_EXC_MESSAGE,
ERROR_EXC_TYPE,
ERROR_GROUP_ID,
'@timestamp',
],
sort: [{ '@timestamp': 'desc' as const }],
size: 1,
},
},
},
},
},
},
});
const { apmEventClient } = setup;
const response = await apmEventClient.search(params);
const { totalErrorGroups, totalErrorPages, errors } =
response.aggregations ?? {};
return {
totalErrorPages: totalErrorPages?.value ?? 0,
totalErrors: response.hits.total.value ?? 0,
totalErrorGroups: totalErrorGroups?.value ?? 0,
items: errors?.buckets.map(({ sample, doc_count: count }) => {
return {
count,
errorMessage: (sample.hits.hits[0]._source as {
error: { exception: Array<{ message: string }> };
}).error.exception?.[0].message,
};
}),
};
}

View file

@ -14,6 +14,7 @@ import { getPageLoadDistribution } from './get_page_load_distribution';
import { getRumServices } from './get_rum_services';
import { getLongTaskMetrics } from './get_long_task_metrics';
import { getWebCoreVitals } from './get_web_core_vitals';
import { getJSErrors } from './get_js_errors';
describe('rum client dashboard queries', () => {
let mock: SearchParamsMock;
@ -79,4 +80,15 @@ describe('rum client dashboard queries', () => {
);
expect(mock.params).toMatchSnapshot();
});
it('fetches js errors', async () => {
mock = await inspectSearchParams((setup) =>
getJSErrors({
setup,
pageSize: 5,
pageIndex: 0,
})
);
expect(mock.params).toMatchSnapshot();
});
});

View file

@ -11,7 +11,9 @@ import {
} from '../../server/lib/helpers/setup_request';
import {
SPAN_TYPE,
AGENT_NAME,
TRANSACTION_TYPE,
SERVICE_LANGUAGE_NAME,
} from '../../common/elasticsearch_fieldnames';
import { rangeFilter } from '../../common/utils/range_filter';
import { ProcessorEvent } from '../../common/processor_event';
@ -90,3 +92,36 @@ export function getRumLongTasksProjection({
},
};
}
export function getRumErrorsProjection({
setup,
}: {
setup: Setup & SetupTimeRange & SetupUIFilters;
}) {
const { start, end, uiFiltersES } = setup;
const bool = {
filter: [
{ range: rangeFilter(start, end) },
{ term: { [AGENT_NAME]: 'rum-js' } },
{ term: { [TRANSACTION_TYPE]: TRANSACTION_PAGE_LOAD } },
{
term: {
[SERVICE_LANGUAGE_NAME]: 'javascript',
},
},
...uiFiltersES,
],
};
return {
apm: {
events: [ProcessorEvent.error],
},
body: {
query: {
bool,
},
},
};
}

View file

@ -69,17 +69,6 @@ import {
listCustomLinksRoute,
customLinkTransactionRoute,
} from './settings/custom_link';
import {
rumClientMetricsRoute,
rumPageViewsTrendRoute,
rumPageLoadDistributionRoute,
rumPageLoadDistBreakdownRoute,
rumServicesRoute,
rumVisitorsBreakdownRoute,
rumWebCoreVitals,
rumUrlSearch,
rumLongTaskMetrics,
} from './rum_client';
import {
observabilityOverviewHasDataRoute,
observabilityOverviewRoute,
@ -89,6 +78,18 @@ import {
createAnomalyDetectionJobsRoute,
anomalyDetectionEnvironmentsRoute,
} from './settings/anomaly_detection';
import {
rumClientMetricsRoute,
rumJSErrors,
rumLongTaskMetrics,
rumPageLoadDistBreakdownRoute,
rumPageLoadDistributionRoute,
rumPageViewsTrendRoute,
rumServicesRoute,
rumUrlSearch,
rumVisitorsBreakdownRoute,
rumWebCoreVitals,
} from './rum_client';
const createApmApi = () => {
const api = createApi()
@ -165,7 +166,16 @@ const createApmApi = () => {
.add(listCustomLinksRoute)
.add(customLinkTransactionRoute)
// Rum Overview
// Observability dashboard
.add(observabilityOverviewHasDataRoute)
.add(observabilityOverviewRoute)
// Anomaly detection
.add(anomalyDetectionJobsRoute)
.add(createAnomalyDetectionJobsRoute)
.add(anomalyDetectionEnvironmentsRoute)
// User Experience app api routes
.add(rumOverviewLocalFiltersRoute)
.add(rumPageViewsTrendRoute)
.add(rumPageLoadDistributionRoute)
@ -174,17 +184,9 @@ const createApmApi = () => {
.add(rumServicesRoute)
.add(rumVisitorsBreakdownRoute)
.add(rumWebCoreVitals)
.add(rumJSErrors)
.add(rumUrlSearch)
.add(rumLongTaskMetrics)
// Observability dashboard
.add(observabilityOverviewHasDataRoute)
.add(observabilityOverviewRoute)
// Anomaly detection
.add(anomalyDetectionJobsRoute)
.add(createAnomalyDetectionJobsRoute)
.add(anomalyDetectionEnvironmentsRoute);
.add(rumLongTaskMetrics);
return api;
};

View file

@ -15,6 +15,7 @@ import { getPageLoadDistBreakdown } from '../lib/rum_client/get_pl_dist_breakdow
import { getRumServices } from '../lib/rum_client/get_rum_services';
import { getVisitorBreakdown } from '../lib/rum_client/get_visitor_breakdown';
import { getWebCoreVitals } from '../lib/rum_client/get_web_core_vitals';
import { getJSErrors } from '../lib/rum_client/get_js_errors';
import { getLongTaskMetrics } from '../lib/rum_client/get_long_task_metrics';
import { getUrlSearch } from '../lib/rum_client/get_url_search';
@ -191,3 +192,27 @@ export const rumUrlSearch = createRoute(() => ({
return getUrlSearch({ setup, urlQuery });
},
}));
export const rumJSErrors = createRoute(() => ({
path: '/api/apm/rum-client/js-errors',
params: {
query: t.intersection([
uiFiltersRt,
rangeRt,
t.type({ pageSize: t.string, pageIndex: t.string }),
]),
},
handler: async ({ context, request }) => {
const setup = await setupRequest(context, request);
const {
query: { pageSize, pageIndex },
} = context.params;
return getJSErrors({
setup,
pageSize: Number(pageSize),
pageIndex: Number(pageIndex),
});
},
}));

View file

@ -153,6 +153,11 @@ export interface AggregationOptionsByType {
keyed?: boolean;
hdr?: { number_of_significant_value_digits: number };
} & AggregationSourceOptions;
bucket_sort: {
sort?: SortOptions;
from?: number;
size?: number;
};
}
type AggregationType = keyof AggregationOptionsByType;
@ -329,6 +334,7 @@ interface AggregationResponsePart<
? Array<{ key: number; value: number }>
: Record<string, number>;
};
bucket_sort: undefined;
}
// Type for debugging purposes. If you see an error in AggregationResponseMap

View file

@ -7,11 +7,22 @@
import { SearchParams, SearchResponse } from 'elasticsearch';
import { AggregationResponseMap, AggregationInputMap } from './aggregations';
interface CollapseQuery {
field: string;
inner_hits: {
name: string;
size?: number;
sort?: [{ date: 'asc' | 'desc' }];
};
max_concurrent_group_searches?: number;
}
export interface ESSearchBody {
query?: any;
size?: number;
aggs?: AggregationInputMap;
track_total_hits?: boolean | number;
collapse?: CollapseQuery;
}
export type ESSearchRequest = Omit<SearchParams, 'body'> & {

View file

@ -0,0 +1,61 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import { expectSnapshot } from '../../../common/match_snapshot';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('CSM js errors', () => {
describe('when there is no data', () => {
it('returns no js errors', async () => {
const response = await supertest.get(
'/api/apm/rum-client/js-errors?pageSize=5&pageIndex=0&start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D'
);
expect(response.status).to.be(200);
expectSnapshot(response.body).toMatchInline(`
Object {
"totalErrorGroups": 0,
"totalErrorPages": 0,
"totalErrors": 0,
}
`);
});
});
describe('when there is data', () => {
before(async () => {
await esArchiver.load('8.0.0');
await esArchiver.load('rum_8.0.0');
});
after(async () => {
await esArchiver.unload('8.0.0');
await esArchiver.unload('rum_8.0.0');
});
it('returns js errors', async () => {
const response = await supertest.get(
'/api/apm/rum-client/js-errors?pageSize=5&pageIndex=0&start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D'
);
expect(response.status).to.be(200);
expectSnapshot(response.body).toMatchInline(`
Object {
"items": Array [],
"totalErrorGroups": 0,
"totalErrorPages": 0,
"totalErrors": 0,
}
`);
});
});
});
}

View file

@ -37,6 +37,7 @@ export default function observabilityApiIntegrationTests({ loadTestFile }: FtrPr
loadTestFile(require.resolve('./csm/long_task_metrics.ts'));
loadTestFile(require.resolve('./csm/url_search.ts'));
loadTestFile(require.resolve('./csm/page_views.ts'));
loadTestFile(require.resolve('./csm/js_errors.ts'));
});
});
}