[UX Dashboard] Move core web vitals query from APM routes to UX plugin (#133974)

* Migrate service list query out of APM.

* Rename non-snakecase files.

* Migrate service list query out of APM.

* Rename non-snakecase files.

* Move core web vitals query out of APM routes.

* Delete obsolete snapshot.

* Refactor o11y overview registration to not rely on deleted APM route.

* Fix some types.

* Delete obsolete API test.

* Delete obsolete APM API tests.

* Delete CWV test from APM API suite.

* Add test journey for core web vitals.

Co-authored-by: Shahzad <shahzad.muhammad@elastic.co>
This commit is contained in:
Justin Kambic 2022-06-13 06:33:22 -04:00 committed by GitHub
parent 027dad3d3c
commit 34fa750da5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 406 additions and 1265 deletions

View file

@ -465,121 +465,3 @@ Object {
},
}
`;
exports[`rum client dashboard queries fetches rum core vitals 1`] = `
Object {
"apm": Object {
"events": Array [
"transaction",
],
},
"body": Object {
"aggs": Object {
"cls": Object {
"percentiles": Object {
"field": "transaction.experience.cls",
"percents": Array [
50,
],
},
},
"clsRanks": Object {
"percentile_ranks": Object {
"field": "transaction.experience.cls",
"keyed": false,
"values": Array [
0.1,
0.25,
],
},
},
"coreVitalPages": Object {
"filter": Object {
"exists": Object {
"field": "transaction.experience",
},
},
},
"fcp": Object {
"percentiles": Object {
"field": "transaction.marks.agent.firstContentfulPaint",
"percents": Array [
50,
],
},
},
"fid": Object {
"percentiles": Object {
"field": "transaction.experience.fid",
"percents": Array [
50,
],
},
},
"fidRanks": Object {
"percentile_ranks": Object {
"field": "transaction.experience.fid",
"keyed": false,
"values": Array [
100,
300,
],
},
},
"lcp": Object {
"percentiles": Object {
"field": "transaction.marks.agent.largestContentfulPaint",
"percents": Array [
50,
],
},
},
"lcpRanks": Object {
"percentile_ranks": Object {
"field": "transaction.marks.agent.largestContentfulPaint",
"keyed": false,
"values": Array [
2500,
4000,
],
},
},
"tbt": Object {
"percentiles": Object {
"field": "transaction.experience.tbt",
"percents": Array [
50,
],
},
},
},
"query": Object {
"bool": Object {
"filter": Array [
Object {
"range": Object {
"@timestamp": Object {
"format": "epoch_millis",
"gte": 0,
"lte": 50000,
},
},
},
Object {
"term": Object {
"transaction.type": "page-load",
},
},
Object {
"exists": Object {
"field": "transaction.marks.navigationTiming.fetchStart",
},
},
],
"must_not": Array [],
},
},
"size": 0,
},
}
`;

View file

@ -13,8 +13,6 @@ import { getClientMetrics } from './get_client_metrics';
import { getPageViewTrends } from './get_page_view_trends';
import { getPageLoadDistribution } from './get_page_load_distribution';
import { getLongTaskMetrics } from './get_long_task_metrics';
import { getWebCoreVitals } from './get_web_core_vitals';
import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
describe('rum client dashboard queries', () => {
let mock: SearchParamsMock;
@ -66,19 +64,6 @@ describe('rum client dashboard queries', () => {
expect(mock.params).toMatchSnapshot();
});
it('fetches rum core vitals', async () => {
mock = await inspectSearchParams(
(setup) =>
getWebCoreVitals({
setup,
start: 0,
end: 50000,
}),
{ uiFilters: { environment: ENVIRONMENT_ALL.value } }
);
expect(mock.params).toMatchSnapshot();
});
it('fetches long task metrics', async () => {
mock = await inspectSearchParams((setup) =>
getLongTaskMetrics({

View file

@ -14,7 +14,6 @@ import { getPageLoadDistribution } from './get_page_load_distribution';
import { getPageViewTrends } from './get_page_view_trends';
import { getPageLoadDistBreakdown } from './get_pl_dist_breakdown';
import { getVisitorBreakdown } from './get_visitor_breakdown';
import { getWebCoreVitals } from './get_web_core_vitals';
import { hasRumData } from './has_rum_data';
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
import { rangeRt } from '../default_api_types';
@ -214,41 +213,6 @@ const rumVisitorsBreakdownRoute = createApmServerRoute({
},
});
const rumWebCoreVitals = createApmServerRoute({
endpoint: 'GET /internal/apm/ux/web-core-vitals',
params: t.type({
query: uxQueryRt,
}),
options: { tags: ['access:apm'] },
handler: async (
resources
): Promise<{
coreVitalPages: number;
cls: number | null;
fid: number | null | undefined;
lcp: number | null | undefined;
tbt: number;
fcp: number | null | undefined;
lcpRanks: number[];
fidRanks: number[];
clsRanks: number[];
}> => {
const setup = await setupUXRequest(resources);
const {
query: { urlQuery, percentile, start, end },
} = resources.params;
return getWebCoreVitals({
setup,
urlQuery,
percentile: percentile ? Number(percentile) : undefined,
start,
end,
});
},
});
const rumLongTaskMetrics = createApmServerRoute({
endpoint: 'GET /internal/apm/ux/long-task-metrics',
params: t.type({
@ -338,7 +302,6 @@ export const rumRouteRepository = {
...rumPageLoadDistBreakdownRoute,
...rumPageViewsTrendRoute,
...rumVisitorsBreakdownRoute,
...rumWebCoreVitals,
...rumLongTaskMetrics,
...rumHasDataRoute,
};

View file

@ -0,0 +1,56 @@
/*
* 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 { journey, step, expect, before } from '@elastic/synthetics';
import { UXDashboardDatePicker } from '../page_objects/date_picker';
import { loginToKibana, waitForLoadingToFinish } from './utils';
journey('Core Web Vitals', async ({ page, params }) => {
before(async () => {
await waitForLoadingToFinish({ page });
});
const queryParams = {
percentile: '50',
rangeFrom: '2020-05-18T11:51:00.000Z',
rangeTo: '2021-10-30T06:37:15.536Z',
};
const queryString = new URLSearchParams(queryParams).toString();
const baseUrl = `${params.kibanaUrl}/app/ux`;
step('Go to UX Dashboard', async () => {
await page.goto(`${baseUrl}?${queryString}`, {
waitUntil: 'networkidle',
});
await loginToKibana({
page,
user: { username: 'viewer_user', password: 'changeme' },
});
});
step('Set date range', async () => {
const datePickerPage = new UXDashboardDatePicker(page);
await datePickerPage.setDefaultE2eRange();
});
step('Check Core Web Vitals', async () => {
expect(await page.$('text=Largest contentful paint'));
expect(await page.$('text=First input delay'));
expect(await page.$('text=Cumulative layout shift'));
expect(
await page.innerText('text=1.93 s', {
strict: true,
timeout: 29000,
})
).toEqual('1.93 s');
expect(await page.innerText('text=5 ms', { strict: true })).toEqual('5 ms');
expect(await page.innerText('text=0.003', { strict: true })).toEqual(
'0.003'
);
});
});

View file

@ -5,5 +5,6 @@
* 2.0.
*/
export * from './core_web_vitals';
export * from './url_ux_query.journey';
export * from './ux_js_errors.journey';

View file

@ -17,11 +17,11 @@ import {
import { getCoreVitalsComponent } from '@kbn/observability-plugin/public';
import { I18LABELS } from '../translations';
import { KeyUXMetrics } from './key_ux_metrics';
import { useFetcher } from '../../../../hooks/use_fetcher';
import { useUxQuery } from '../hooks/use_ux_query';
import { CsmSharedContext } from '../csm_shared_context';
import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params';
import { getPercentileLabel } from './translations';
import { useCoreWebVitalsQuery } from '../../../../hooks/use_core_web_vitals_query';
export function UXMetrics() {
const {
@ -30,19 +30,9 @@ export function UXMetrics() {
const uxQuery = useUxQuery();
const { data, status } = useFetcher(
(callApmApi) => {
if (uxQuery) {
return callApmApi('GET /internal/apm/ux/web-core-vitals', {
params: {
query: uxQuery,
},
});
}
return Promise.resolve(null);
},
[uxQuery]
);
const { data, loading: loadingResponse } = useCoreWebVitalsQuery(uxQuery);
const loading = loadingResponse ?? true;
const {
sharedData: { totalPageViews },
@ -53,11 +43,11 @@ export function UXMetrics() {
getCoreVitalsComponent({
data,
totalPageViews,
loading: status !== 'success',
loading,
displayTrafficMetric: true,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[status]
[loading]
);
return (
@ -70,7 +60,7 @@ export function UXMetrics() {
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<KeyUXMetrics data={data} loading={status !== 'success'} />
<KeyUXMetrics data={data} loading={loading} />
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="xs" />

View file

@ -5,35 +5,91 @@
* 2.0.
*/
import { ESSearchResponse } from '@kbn/core/types/elasticsearch';
import {
DataPublicPluginStart,
isCompleteResponse,
} from '@kbn/data-plugin/public';
import {
FetchDataParams,
HasDataParams,
UxFetchDataResponse,
UXHasDataResponse,
UXMetrics,
} from '@kbn/observability-plugin/public';
import {
coreWebVitalsQuery,
transformCoreWebVitalsResponse,
DEFAULT_RANKS,
} from '../../../services/data/core_web_vitals_query';
import { callApmApi } from '../../../services/rest/create_call_apm_api';
export { createCallApmApi } from '../../../services/rest/create_call_apm_api';
export const fetchUxOverviewDate = async ({
absoluteTime,
relativeTime,
serviceName,
}: FetchDataParams): Promise<UxFetchDataResponse> => {
const data = await callApmApi('GET /internal/apm/ux/web-core-vitals', {
signal: null,
params: {
query: {
start: new Date(absoluteTime.start).toISOString(),
end: new Date(absoluteTime.end).toISOString(),
uiFilters: `{"serviceName":["${serviceName}"]}`,
},
},
});
type FetchUxOverviewDateParams = FetchDataParams & {
dataStartPlugin: DataPublicPluginStart;
};
async function getCoreWebVitalsResponse({
absoluteTime,
serviceName,
dataStartPlugin,
}: FetchUxOverviewDateParams) {
const dataView = await callApmApi('GET /internal/apm/data_view/dynamic', {
signal: null,
});
return new Promise<
ESSearchResponse<{}, ReturnType<typeof coreWebVitalsQuery>>
>((resolve) => {
const search$ = dataStartPlugin.search
.search(
{
params: {
index: dataView.dynamicDataView?.title,
...coreWebVitalsQuery(
absoluteTime.start,
absoluteTime.end,
undefined,
{
serviceName: serviceName ? [serviceName] : undefined,
}
),
},
},
{}
)
.subscribe({
next: (result) => {
if (isCompleteResponse(result)) {
resolve(result.rawResponse as any);
search$.unsubscribe();
}
},
});
});
}
const CORE_WEB_VITALS_DEFAULTS: UXMetrics = {
coreVitalPages: 0,
cls: 0,
fid: 0,
lcp: 0,
tbt: 0,
fcp: 0,
lcpRanks: DEFAULT_RANKS,
fidRanks: DEFAULT_RANKS,
clsRanks: DEFAULT_RANKS,
};
export const fetchUxOverviewDate = async (
params: FetchUxOverviewDateParams
): Promise<UxFetchDataResponse> => {
const coreWebVitalsResponse = await getCoreWebVitalsResponse(params);
return {
coreWebVitals: data,
appLink: `/app/ux?rangeFrom=${relativeTime.start}&rangeTo=${relativeTime.end}`,
coreWebVitals:
transformCoreWebVitalsResponse(coreWebVitalsResponse) ??
CORE_WEB_VITALS_DEFAULTS,
appLink: `/app/ux?rangeFrom=${params.relativeTime.start}&rangeTo=${params.relativeTime.end}`,
};
};

View file

@ -0,0 +1,44 @@
/*
* 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 { useEsSearch } from '@kbn/observability-plugin/public';
import { useMemo } from 'react';
import { useDataView } from '../components/app/rum_dashboard/local_uifilters/use_data_view';
import { callDateMath } from '../services/data/call_date_math';
import {
coreWebVitalsQuery,
transformCoreWebVitalsResponse,
PERCENTILE_DEFAULT,
} from '../services/data/core_web_vitals_query';
import { useUxQuery } from '../components/app/rum_dashboard/hooks/use_ux_query';
export function useCoreWebVitalsQuery(uxQuery: ReturnType<typeof useUxQuery>) {
const { dataViewTitle } = useDataView();
const { data: esQueryResponse, loading } = useEsSearch(
{
index: uxQuery ? dataViewTitle : undefined,
...coreWebVitalsQuery(
callDateMath(uxQuery?.start),
callDateMath(uxQuery?.end),
uxQuery?.urlQuery,
uxQuery?.uiFilters ? JSON.parse(uxQuery.uiFilters) : {},
uxQuery?.percentile ? Number(uxQuery.percentile) : undefined
),
},
[uxQuery, dataViewTitle],
{ name: 'UxCoreWebVitals' }
);
const data = useMemo(
() =>
transformCoreWebVitalsResponse(
esQueryResponse,
uxQuery?.percentile ? Number(uxQuery?.percentile) : PERCENTILE_DEFAULT
),
[esQueryResponse, uxQuery?.percentile]
);
return { data, loading };
}

View file

@ -79,8 +79,14 @@ export class UxPlugin implements Plugin<UxPluginSetup, UxPluginStart> {
return await dataHelper.hasRumData(params!);
},
fetchData: async (params: FetchDataParams) => {
const [_, startPlugins] = await core.getStartServices();
const { data: dataStartPlugin } = startPlugins as ApmPluginStartDeps;
const dataHelper = await getUxDataHelper();
return await dataHelper.fetchUxOverviewDate(params);
return await dataHelper.fetchUxOverviewDate({
...params,
dataStartPlugin,
});
},
});
}

View file

@ -0,0 +1,114 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`core web vitals query fetches rum core vitals 1`] = `
Object {
"body": Object {
"aggs": Object {
"cls": Object {
"percentiles": Object {
"field": "transaction.experience.cls",
"percents": Array [
50,
],
},
},
"clsRanks": Object {
"percentile_ranks": Object {
"field": "transaction.experience.cls",
"keyed": false,
"values": Array [
0.1,
0.25,
],
},
},
"coreVitalPages": Object {
"filter": Object {
"exists": Object {
"field": "transaction.experience",
},
},
},
"fcp": Object {
"percentiles": Object {
"field": "transaction.marks.agent.firstContentfulPaint",
"percents": Array [
50,
],
},
},
"fid": Object {
"percentiles": Object {
"field": "transaction.experience.fid",
"percents": Array [
50,
],
},
},
"fidRanks": Object {
"percentile_ranks": Object {
"field": "transaction.experience.fid",
"keyed": false,
"values": Array [
100,
300,
],
},
},
"lcp": Object {
"percentiles": Object {
"field": "transaction.marks.agent.largestContentfulPaint",
"percents": Array [
50,
],
},
},
"lcpRanks": Object {
"percentile_ranks": Object {
"field": "transaction.marks.agent.largestContentfulPaint",
"keyed": false,
"values": Array [
2500,
4000,
],
},
},
"tbt": Object {
"percentiles": Object {
"field": "transaction.experience.tbt",
"percents": Array [
50,
],
},
},
},
"query": Object {
"bool": Object {
"filter": Array [
Object {
"range": Object {
"@timestamp": Object {
"format": "epoch_millis",
"gte": 0,
"lte": 5000,
},
},
},
Object {
"term": Object {
"transaction.type": "page-load",
},
},
Object {
"exists": Object {
"field": "transaction.marks.navigationTiming.fetchStart",
},
},
],
"must_not": Array [],
},
},
"size": 0,
},
}
`;

View file

@ -0,0 +1,24 @@
/*
* 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 { coreWebVitalsQuery } from './core_web_vitals_query';
describe('core web vitals query', () => {
it('fetches rum core vitals', async () => {
expect(
coreWebVitalsQuery(
0,
5000,
'',
{
environment: 'ENVIRONMENT_ALL',
},
50
)
).toMatchSnapshot();
});
});

View file

@ -5,37 +5,89 @@
* 2.0.
*/
import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions';
import { mergeProjection } from '../../projections/util/merge_projection';
import { SetupUX } from './route';
import { ESSearchResponse } from '@kbn/core/types/elasticsearch';
import { UXMetrics } from '@kbn/observability-plugin/public';
import {
CLS_FIELD,
TBT_FIELD,
FCP_FIELD,
CLS_FIELD,
FID_FIELD,
LCP_FIELD,
TBT_FIELD,
} from '../../../common/elasticsearch_fieldnames';
import { SetupUX, UxUIFilters } from '../../../typings/ui_filters';
import { mergeProjection } from '../../../common/utils/merge_projection';
import { getRumPageLoadTransactionsProjection } from './projections';
export const DEFAULT_RANKS = [100, 0, 0];
const getRanksPercentages = (ranks?: Record<string, number | null>) => {
if (!Array.isArray(ranks)) return null;
const ranksVal = ranks?.map(({ value }) => value?.toFixed(0) ?? 0) ?? [];
return [
Number(ranksVal?.[0]),
Number(ranksVal?.[1]) - Number(ranksVal?.[0]),
100 - Number(ranksVal?.[1]),
];
};
export function transformCoreWebVitalsResponse<T>(
response?: ESSearchResponse<T, ReturnType<typeof coreWebVitalsQuery>>,
percentile = PERCENTILE_DEFAULT
): UXMetrics | undefined {
if (!response) return response;
const {
lcp,
cls,
fid,
tbt,
fcp,
lcpRanks,
fidRanks,
clsRanks,
coreVitalPages,
} = response.aggregations ?? {};
const pkey = percentile.toFixed(1);
return {
coreVitalPages: coreVitalPages?.doc_count ?? 0,
/* Because cls is required in the type UXMetrics, and defined as number | null,
* we need to default to null in the case where cls is undefined in order to satisfy the UXMetrics type */
cls: cls?.values[pkey] ?? null,
fid: fid?.values[pkey],
lcp: lcp?.values[pkey],
tbt: tbt?.values[pkey] ?? 0,
fcp: fcp?.values[pkey],
lcpRanks: lcp?.values[pkey]
? getRanksPercentages(lcpRanks?.values) ?? DEFAULT_RANKS
: DEFAULT_RANKS,
fidRanks: fid?.values[pkey]
? getRanksPercentages(fidRanks?.values) ?? DEFAULT_RANKS
: DEFAULT_RANKS,
clsRanks: cls?.values[pkey]
? getRanksPercentages(clsRanks?.values) ?? DEFAULT_RANKS
: DEFAULT_RANKS,
};
}
export const PERCENTILE_DEFAULT = 50;
export function coreWebVitalsQuery(
start: number,
end: number,
urlQuery?: string,
uiFilters?: UxUIFilters,
percentile = PERCENTILE_DEFAULT
) {
const setup: SetupUX = { uiFilters: uiFilters ? uiFilters : {} };
export async function getWebCoreVitals({
setup,
urlQuery,
percentile = 50,
start,
end,
}: {
setup: SetupUX;
urlQuery?: string;
percentile?: number;
start: number;
end: number;
}) {
const projection = getRumPageLoadTransactionsProjection({
setup,
urlQuery,
start,
end,
});
const params = mergeProjection(projection, {
body: {
size: 0,
@ -106,55 +158,6 @@ export async function getWebCoreVitals({
},
},
});
const { apmEventClient } = setup;
const response = await apmEventClient.search('get_web_core_vitals', params);
const {
lcp,
cls,
fid,
tbt,
fcp,
lcpRanks,
fidRanks,
clsRanks,
coreVitalPages,
} = response.aggregations ?? {};
const getRanksPercentages = (
ranks?: Array<{ key: number; value: number | null }>
) => {
const ranksVal = ranks?.map(({ value }) => value?.toFixed(0) ?? 0) ?? [];
return [
Number(ranksVal?.[0]),
Number(ranksVal?.[1]) - Number(ranksVal?.[0]),
100 - Number(ranksVal?.[1]),
];
};
const defaultRanks = [100, 0, 0];
const pkey = percentile.toFixed(1);
return {
coreVitalPages: coreVitalPages?.doc_count ?? 0,
/* Because cls is required in the type UXMetrics, and defined as number | null,
* we need to default to null in the case where cls is undefined in order to satisfy the UXMetrics type */
cls: cls?.values[pkey] ?? null,
fid: fid?.values[pkey],
lcp: lcp?.values[pkey],
tbt: tbt?.values[pkey] ?? 0,
fcp: fcp?.values[pkey],
lcpRanks: lcp?.values[pkey]
? getRanksPercentages(lcpRanks?.values)
: defaultRanks,
fidRanks: fid?.values[pkey]
? getRanksPercentages(fidRanks?.values)
: defaultRanks,
clsRanks: cls?.values[pkey]
? getRanksPercentages(clsRanks?.values)
: defaultRanks,
};
const { apm, ...rest } = params;
return rest;
}

View file

@ -5,17 +5,17 @@
* 2.0.
*/
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { TRANSACTION_TYPE } from '../../../common/elasticsearch_fieldnames';
import {
AGENT_NAME,
PROCESSOR_EVENT,
SERVICE_LANGUAGE_NAME,
TRANSACTION_TYPE,
} from '../../../common/elasticsearch_fieldnames';
import { ProcessorEvent } from '../../../common/processor_event';
import { TRANSACTION_PAGE_LOAD } from '../../../common/transaction_types';
import { SetupUX } from '../../../typings/ui_filters';
import { getEsFilter } from './get_es_filter';
import { rangeQuery } from './range_query';
import {
AGENT_NAME,
SERVICE_LANGUAGE_NAME,
PROCESSOR_EVENT,
} from '../../../common/elasticsearch_fieldnames';
export function getRumPageLoadTransactionsProjection({
setup,

View file

@ -37,6 +37,6 @@ export function serviceNameQuery(
},
},
});
const { apm: _apm, ...rest } = params;
const { apm, ...rest } = params;
return rest;
}

View file

@ -1,832 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`APM API tests trial 8.0.0,rum_8.0.0 UX page load dist with data returns page load distribution 1`] = `
Object {
"pageLoadDistribution": Object {
"maxDuration": 54.46,
"minDuration": 0,
"pageLoadDistribution": Array [
Object {
"x": 0,
"y": 0,
},
Object {
"x": 0.5,
"y": 0,
},
Object {
"x": 1,
"y": 0,
},
Object {
"x": 1.5,
"y": 0,
},
Object {
"x": 2,
"y": 0,
},
Object {
"x": 2.5,
"y": 0,
},
Object {
"x": 3,
"y": 16.6666666666667,
},
Object {
"x": 3.5,
"y": 0,
},
Object {
"x": 4,
"y": 0,
},
Object {
"x": 4.5,
"y": 0,
},
Object {
"x": 5,
"y": 50,
},
Object {
"x": 5.5,
"y": 0,
},
Object {
"x": 6,
"y": 0,
},
Object {
"x": 6.5,
"y": 0,
},
Object {
"x": 7,
"y": 0,
},
Object {
"x": 7.5,
"y": 0,
},
Object {
"x": 8,
"y": 0,
},
Object {
"x": 8.5,
"y": 0,
},
Object {
"x": 9,
"y": 0,
},
Object {
"x": 9.5,
"y": 0,
},
Object {
"x": 10,
"y": 0,
},
Object {
"x": 10.5,
"y": 0,
},
Object {
"x": 11,
"y": 0,
},
Object {
"x": 11.5,
"y": 0,
},
Object {
"x": 12,
"y": 0,
},
Object {
"x": 12.5,
"y": 0,
},
Object {
"x": 13,
"y": 0,
},
Object {
"x": 13.5,
"y": 0,
},
Object {
"x": 14,
"y": 0,
},
Object {
"x": 14.5,
"y": 0,
},
Object {
"x": 15,
"y": 0,
},
Object {
"x": 15.5,
"y": 0,
},
Object {
"x": 16,
"y": 0,
},
Object {
"x": 16.5,
"y": 0,
},
Object {
"x": 17,
"y": 0,
},
Object {
"x": 17.5,
"y": 0,
},
Object {
"x": 18,
"y": 0,
},
Object {
"x": 18.5,
"y": 0,
},
Object {
"x": 19,
"y": 0,
},
Object {
"x": 19.5,
"y": 0,
},
Object {
"x": 20,
"y": 0,
},
Object {
"x": 20.5,
"y": 0,
},
Object {
"x": 21,
"y": 0,
},
Object {
"x": 21.5,
"y": 0,
},
Object {
"x": 22,
"y": 0,
},
Object {
"x": 22.5,
"y": 0,
},
Object {
"x": 23,
"y": 0,
},
Object {
"x": 23.5,
"y": 0,
},
Object {
"x": 24,
"y": 0,
},
Object {
"x": 24.5,
"y": 0,
},
Object {
"x": 25,
"y": 0,
},
Object {
"x": 25.5,
"y": 0,
},
Object {
"x": 26,
"y": 0,
},
Object {
"x": 26.5,
"y": 0,
},
Object {
"x": 27,
"y": 0,
},
Object {
"x": 27.5,
"y": 0,
},
Object {
"x": 28,
"y": 0,
},
Object {
"x": 28.5,
"y": 0,
},
Object {
"x": 29,
"y": 0,
},
Object {
"x": 29.5,
"y": 0,
},
Object {
"x": 30,
"y": 0,
},
Object {
"x": 30.5,
"y": 0,
},
Object {
"x": 31,
"y": 0,
},
Object {
"x": 31.5,
"y": 0,
},
Object {
"x": 32,
"y": 0,
},
Object {
"x": 32.5,
"y": 0,
},
Object {
"x": 33,
"y": 0,
},
Object {
"x": 33.5,
"y": 0,
},
Object {
"x": 34,
"y": 0,
},
Object {
"x": 34.5,
"y": 0,
},
Object {
"x": 35,
"y": 0,
},
Object {
"x": 35.5,
"y": 0,
},
Object {
"x": 36,
"y": 0,
},
Object {
"x": 36.5,
"y": 0,
},
Object {
"x": 37,
"y": 0,
},
Object {
"x": 37.5,
"y": 16.6666666666667,
},
Object {
"x": 38,
"y": 0,
},
Object {
"x": 38.5,
"y": 0,
},
Object {
"x": 39,
"y": 0,
},
Object {
"x": 39.5,
"y": 0,
},
Object {
"x": 40,
"y": 0,
},
Object {
"x": 40.5,
"y": 0,
},
Object {
"x": 41,
"y": 0,
},
Object {
"x": 41.5,
"y": 0,
},
Object {
"x": 42,
"y": 0,
},
Object {
"x": 42.5,
"y": 0,
},
Object {
"x": 43,
"y": 0,
},
Object {
"x": 43.5,
"y": 0,
},
Object {
"x": 44,
"y": 0,
},
Object {
"x": 44.5,
"y": 0,
},
Object {
"x": 45,
"y": 0,
},
Object {
"x": 45.5,
"y": 0,
},
Object {
"x": 46,
"y": 0,
},
Object {
"x": 46.5,
"y": 0,
},
Object {
"x": 47,
"y": 0,
},
Object {
"x": 47.5,
"y": 0,
},
Object {
"x": 48,
"y": 0,
},
Object {
"x": 48.5,
"y": 0,
},
Object {
"x": 49,
"y": 0,
},
Object {
"x": 49.5,
"y": 0,
},
Object {
"x": 50,
"y": 0,
},
Object {
"x": 50.5,
"y": 0,
},
Object {
"x": 51,
"y": 0,
},
Object {
"x": 51.5,
"y": 0,
},
Object {
"x": 52,
"y": 0,
},
Object {
"x": 52.5,
"y": 0,
},
Object {
"x": 53,
"y": 0,
},
Object {
"x": 53.5,
"y": 0,
},
Object {
"x": 54,
"y": 0,
},
Object {
"x": 54.5,
"y": 16.6666666666667,
},
],
"percentiles": Object {
"50.0": 4.88,
"75.0": 37.09,
"90.0": 37.09,
"95.0": 54.46,
"99.0": 54.46,
},
},
}
`;
exports[`APM API tests trial 8.0.0,rum_8.0.0 UX page load dist with data returns page load distribution with breakdown 1`] = `
Object {
"pageLoadDistBreakdown": Array [
Object {
"data": Array [
Object {
"x": 0,
"y": 0,
},
Object {
"x": 0.5,
"y": 0,
},
Object {
"x": 1,
"y": 0,
},
Object {
"x": 1.5,
"y": 0,
},
Object {
"x": 2,
"y": 0,
},
Object {
"x": 2.5,
"y": 0,
},
Object {
"x": 3,
"y": 25,
},
Object {
"x": 3.5,
"y": 0,
},
Object {
"x": 4,
"y": 0,
},
Object {
"x": 4.5,
"y": 0,
},
Object {
"x": 5,
"y": 25,
},
Object {
"x": 5.5,
"y": 0,
},
Object {
"x": 6,
"y": 0,
},
Object {
"x": 6.5,
"y": 0,
},
Object {
"x": 7,
"y": 0,
},
Object {
"x": 7.5,
"y": 0,
},
Object {
"x": 8,
"y": 0,
},
Object {
"x": 8.5,
"y": 0,
},
Object {
"x": 9,
"y": 0,
},
Object {
"x": 9.5,
"y": 0,
},
Object {
"x": 10,
"y": 0,
},
Object {
"x": 10.5,
"y": 0,
},
Object {
"x": 11,
"y": 0,
},
Object {
"x": 11.5,
"y": 0,
},
Object {
"x": 12,
"y": 0,
},
Object {
"x": 12.5,
"y": 0,
},
Object {
"x": 13,
"y": 0,
},
Object {
"x": 13.5,
"y": 0,
},
Object {
"x": 14,
"y": 0,
},
Object {
"x": 14.5,
"y": 0,
},
Object {
"x": 15,
"y": 0,
},
Object {
"x": 15.5,
"y": 0,
},
Object {
"x": 16,
"y": 0,
},
Object {
"x": 16.5,
"y": 0,
},
Object {
"x": 17,
"y": 0,
},
Object {
"x": 17.5,
"y": 0,
},
Object {
"x": 18,
"y": 0,
},
Object {
"x": 18.5,
"y": 0,
},
Object {
"x": 19,
"y": 0,
},
Object {
"x": 19.5,
"y": 0,
},
Object {
"x": 20,
"y": 0,
},
Object {
"x": 20.5,
"y": 0,
},
Object {
"x": 21,
"y": 0,
},
Object {
"x": 21.5,
"y": 0,
},
Object {
"x": 22,
"y": 0,
},
Object {
"x": 22.5,
"y": 0,
},
Object {
"x": 23,
"y": 0,
},
Object {
"x": 23.5,
"y": 0,
},
Object {
"x": 24,
"y": 0,
},
Object {
"x": 24.5,
"y": 0,
},
Object {
"x": 25,
"y": 0,
},
Object {
"x": 25.5,
"y": 0,
},
Object {
"x": 26,
"y": 0,
},
Object {
"x": 26.5,
"y": 0,
},
Object {
"x": 27,
"y": 0,
},
Object {
"x": 27.5,
"y": 0,
},
Object {
"x": 28,
"y": 0,
},
Object {
"x": 28.5,
"y": 0,
},
Object {
"x": 29,
"y": 0,
},
Object {
"x": 29.5,
"y": 0,
},
Object {
"x": 30,
"y": 0,
},
Object {
"x": 30.5,
"y": 0,
},
Object {
"x": 31,
"y": 0,
},
Object {
"x": 31.5,
"y": 0,
},
Object {
"x": 32,
"y": 0,
},
Object {
"x": 32.5,
"y": 0,
},
Object {
"x": 33,
"y": 0,
},
Object {
"x": 33.5,
"y": 0,
},
Object {
"x": 34,
"y": 0,
},
Object {
"x": 34.5,
"y": 0,
},
Object {
"x": 35,
"y": 0,
},
Object {
"x": 35.5,
"y": 0,
},
Object {
"x": 36,
"y": 0,
},
Object {
"x": 36.5,
"y": 0,
},
Object {
"x": 37,
"y": 0,
},
Object {
"x": 37.5,
"y": 25,
},
],
"name": "Chrome",
},
Object {
"data": Array [
Object {
"x": 0,
"y": 0,
},
Object {
"x": 0.5,
"y": 0,
},
Object {
"x": 1,
"y": 0,
},
Object {
"x": 1.5,
"y": 0,
},
Object {
"x": 2,
"y": 0,
},
Object {
"x": 2.5,
"y": 0,
},
Object {
"x": 3,
"y": 0,
},
Object {
"x": 3.5,
"y": 0,
},
Object {
"x": 4,
"y": 0,
},
Object {
"x": 4.5,
"y": 0,
},
Object {
"x": 5,
"y": 100,
},
],
"name": "Chrome Mobile",
},
],
}
`;
exports[`APM API tests trial no data UX page load dist without data returns empty list 1`] = `
Object {
"pageLoadDistribution": null,
}
`;
exports[`APM API tests trial no data UX page load dist without data returns empty list with breakdowns 1`] = `Object {}`;

View file

@ -1,73 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function rumServicesApiTests({ getService }: FtrProviderContext) {
const registry = getService('registry');
const supertest = getService('legacySupertestAsApmReadUser');
registry.when('UX page load dist without data', { config: 'trial', archives: [] }, () => {
it('returns empty list', async () => {
const response = await supertest.get('/internal/apm/ux/page-load-distribution').query({
start: '2020-09-07T20:35:54.654Z',
end: '2020-09-14T20:35:54.654Z',
uiFilters: '{"serviceName":["elastic-co-rum-test"]}',
});
expect(response.status).to.be(200);
expectSnapshot(response.body).toMatch();
});
it('returns empty list with breakdowns', async () => {
const response = await supertest
.get('/internal/apm/ux/page-load-distribution/breakdown')
.query({
start: '2020-09-07T20:35:54.654Z',
end: '2020-09-14T20:35:54.654Z',
uiFilters: '{"serviceName":["elastic-co-rum-test"]}',
breakdown: 'Browser',
});
expect(response.status).to.be(200);
expectSnapshot(response.body).toMatch();
});
});
registry.when(
'UX page load dist with data',
{ config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] },
() => {
it('returns page load distribution', async () => {
const response = await supertest.get('/internal/apm/ux/page-load-distribution').query({
start: '2020-09-07T20:35:54.654Z',
end: '2020-09-16T20:35:54.654Z',
uiFilters: '{"serviceName":["kibana-frontend-8_0_0"]}',
});
expect(response.status).to.be(200);
expectSnapshot(response.body).toMatch();
});
it('returns page load distribution with breakdown', async () => {
const response = await supertest
.get('/internal/apm/ux/page-load-distribution/breakdown')
.query({
start: '2020-09-07T20:35:54.654Z',
end: '2020-09-16T20:35:54.654Z',
uiFilters: '{"serviceName":["kibana-frontend-8_0_0"]}',
breakdown: 'Browser',
});
expect(response.status).to.be(200);
expectSnapshot(response.body).toMatch();
});
}
);
}

View file

@ -1,78 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function rumServicesApiTests({ getService }: FtrProviderContext) {
const registry = getService('registry');
const supertest = getService('legacySupertestAsApmReadUser');
registry.when('CSM web core vitals without data', { config: 'trial', archives: [] }, () => {
it('returns empty list', async () => {
const response = await supertest.get('/internal/apm/ux/web-core-vitals').query({
start: '2020-09-07T20:35:54.654Z',
end: '2020-09-14T20:35:54.654Z',
uiFilters: '{"serviceName":["elastic-co-rum-test"]}',
percentile: 50,
});
expect(response.status).to.be(200);
expect(response.body).to.eql({
coreVitalPages: 0,
cls: null,
tbt: 0,
lcpRanks: [100, 0, 0],
fidRanks: [100, 0, 0],
clsRanks: [100, 0, 0],
});
});
});
registry.when(
'CSM web core vitals with data',
{ config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] },
() => {
it('returns web core vitals values', async () => {
const response = await supertest.get('/internal/apm/ux/web-core-vitals').query({
start: '2020-09-07T20:35:54.654Z',
end: '2020-09-16T20:35:54.654Z',
uiFilters: '{"serviceName":["kibana-frontend-8_0_0"]}',
percentile: 50,
});
expect(response.status).to.be(200);
expectSnapshot(response.body).toMatchInline(`
Object {
"cls": 0,
"clsRanks": Array [
100,
0,
0,
],
"coreVitalPages": 6,
"fcp": 817.5,
"fid": 1352.13,
"fidRanks": Array [
0,
0,
100,
],
"lcp": 1019,
"lcpRanks": Array [
100,
0,
0,
],
"tbt": 0,
}
`);
});
}
);
}