From 13d205a76c371a672d0827b2656fb26601d1479c Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 21 Sep 2020 19:19:21 +0200 Subject: [PATCH 1/4] Use stacked chart for page views --- .../RumDashboard/Charts/PageViewsChart.tsx | 21 ++- .../lib/rum_client/get_page_view_trends.ts | 63 ++++++--- .../trial/tests/csm/page_views.ts | 129 ++++++++++++++++++ .../apm_api_integration/trial/tests/index.ts | 1 + 4 files changed, 184 insertions(+), 30 deletions(-) create mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx index c76be19edfe47..4200d48c9604a 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx @@ -68,15 +68,10 @@ export function PageViewsChart({ data, loading }: Props) { }); }; - let breakdownAccessors: Set = new Set(); - if (data && data.length > 0) { - data.forEach((item) => { - breakdownAccessors = new Set([ - ...Array.from(breakdownAccessors), - ...Object.keys(item).filter((key) => key !== 'x'), - ]); - }); - } + const breakdownAccessors = + data?.topItems?.length > 0 ? data?.topItems : ['y']; + + const [darkMode] = useUiSetting$('theme:darkMode'); const customSeriesNaming: SeriesNameFn = ({ yAccessor }) => { if (yAccessor === 'y') { @@ -86,8 +81,6 @@ export function PageViewsChart({ data, loading }: Props) { return yAccessor; }; - const [darkMode] = useUiSetting$('theme:darkMode'); - return ( {(!loading || data) && ( @@ -115,7 +108,8 @@ export function PageViewsChart({ data, loading }: Props) { id="page_views" title={I18LABELS.pageViews} position={Position.Left} - tickFormat={(d) => numeral(d).format('0a')} + tickFormat={(d) => numeral(d).format('0')} + labelFormat={(d) => numeral(d).format('0a')} /> diff --git a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts index f25062c67f87a..543aa911b0b1f 100644 --- a/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts +++ b/x-pack/plugins/apm/server/lib/rum_client/get_page_view_trends.ts @@ -51,6 +51,16 @@ export async function getPageViewTrends({ } : undefined, }, + ...(breakdownItem + ? { + topBreakdowns: { + terms: { + field: breakdownItem.fieldName, + size: 9, + }, + }, + } + : {}), }, }, }); @@ -59,25 +69,44 @@ export async function getPageViewTrends({ const response = await apmEventClient.search(params); + const { topBreakdowns } = response.aggregations ?? {}; + + // we are only displaying top 9 + const topItems: string[] = (topBreakdowns?.buckets ?? []).map( + ({ key }) => key as string + ); + const result = response.aggregations?.pageViews.buckets ?? []; - return result.map((bucket) => { - const { key: xVal, doc_count: bCount } = bucket; - const res: Record = { - x: xVal, - y: bCount, - }; - if ('breakdown' in bucket) { - const categoryBuckets = bucket.breakdown.buckets; - categoryBuckets.forEach(({ key, doc_count: docCount }) => { - if (key === 'Other') { - res[key + `(${breakdownItem?.name})`] = docCount; - } else { - res[key] = docCount; + return { + topItems, + items: result.map((bucket) => { + const { key: xVal, doc_count: bCount } = bucket; + const res: Record = { + x: xVal, + y: bCount, + }; + if ('breakdown' in bucket) { + let top9Count = 0; + const categoryBuckets = bucket.breakdown.buckets; + categoryBuckets.forEach(({ key, doc_count: docCount }) => { + if (topItems.includes(key as string)) { + if (res[key]) { + // if term is already in object, just add it to it + res[key] += docCount; + } else { + res[key] = docCount; + } + top9Count += docCount; + } + }); + // Top 9 plus others, get a diff from parent bucket total + if (bCount > top9Count) { + res.Other = bCount - top9Count; } - }); - } + } - return res; - }); + return res; + }), + }; } diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts new file mode 100644 index 0000000000000..85659eec90ff7 --- /dev/null +++ b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts @@ -0,0 +1,129 @@ +/* + * 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 rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('CSM page views', () => { + describe('when there is no data', () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?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); + expect(response.body).to.eql({ + cls: '0', + fid: '0.00', + lcp: '0.00', + tbt: '0.00', + fcp: 0, + lcpRanks: [0, 0, 100], + fidRanks: [0, 0, 100], + clsRanks: [0, 0, 100], + }); + }); + it('returns empty list with breakdowns', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?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&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + expect(response.body).to.eql({ + cls: '0', + fid: '0.00', + lcp: '0.00', + tbt: '0.00', + fcp: 0, + lcpRanks: [0, 0, 100], + fidRanks: [0, 0, 100], + clsRanks: [0, 0, 100], + }); + }); + }); + + 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 page views', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?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 { + "cls": "0.00", + "clsRanks": Array [ + 100, + 0, + 0, + ], + "fcp": 1072, + "fid": "1.35", + "fidRanks": Array [ + 0, + 0, + 100, + ], + "lcp": "1.27", + "lcpRanks": Array [ + 100, + 0, + 0, + ], + "tbt": 0, + } + `); + }); + it('returns page views', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?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&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "cls": "0.00", + "clsRanks": Array [ + 100, + 0, + 0, + ], + "fcp": 1072, + "fid": "1.35", + "fidRanks": Array [ + 0, + 0, + 100, + ], + "lcp": "1.27", + "lcpRanks": Array [ + 100, + 0, + 0, + ], + "tbt": 0, + } + `); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts index ae62253c62d81..a026f91a02cd7 100644 --- a/x-pack/test/apm_api_integration/trial/tests/index.ts +++ b/x-pack/test/apm_api_integration/trial/tests/index.ts @@ -35,6 +35,7 @@ export default function observabilityApiIntegrationTests({ loadTestFile }: FtrPr loadTestFile(require.resolve('./csm/csm_services.ts')); loadTestFile(require.resolve('./csm/web_core_vitals.ts')); loadTestFile(require.resolve('./csm/long_task_metrics.ts')); + loadTestFile(require.resolve('./csm/page_views.ts')); }); }); } From a72758015e763dc4ea9d7935e7d8f0679562209a Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 21 Sep 2020 19:32:34 +0200 Subject: [PATCH 2/4] update test --- .../trial/tests/csm/page_views.ts | 74 ++----------------- 1 file changed, 5 insertions(+), 69 deletions(-) diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts index 85659eec90ff7..ca5670d41d8ee 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts @@ -20,16 +20,7 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) ); expect(response.status).to.be(200); - expect(response.body).to.eql({ - cls: '0', - fid: '0.00', - lcp: '0.00', - tbt: '0.00', - fcp: 0, - lcpRanks: [0, 0, 100], - fidRanks: [0, 0, 100], - clsRanks: [0, 0, 100], - }); + expectSnapshot(response.body).toMatch(); }); it('returns empty list with breakdowns', async () => { const response = await supertest.get( @@ -37,16 +28,7 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) ); expect(response.status).to.be(200); - expect(response.body).to.eql({ - cls: '0', - fid: '0.00', - lcp: '0.00', - tbt: '0.00', - fcp: 0, - lcpRanks: [0, 0, 100], - fidRanks: [0, 0, 100], - clsRanks: [0, 0, 100], - }); + expectSnapshot(response.body).toMatch(); }); }); @@ -67,62 +49,16 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` - Object { - "cls": "0.00", - "clsRanks": Array [ - 100, - 0, - 0, - ], - "fcp": 1072, - "fid": "1.35", - "fidRanks": Array [ - 0, - 0, - 100, - ], - "lcp": "1.27", - "lcpRanks": Array [ - 100, - 0, - 0, - ], - "tbt": 0, - } - `); + expectSnapshot(response.body).toMatch(); }); - it('returns page views', async () => { + it('returns page views with breakdown', async () => { const response = await supertest.get( '/api/apm/rum-client/page-view-trends?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&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' ); expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` - Object { - "cls": "0.00", - "clsRanks": Array [ - 100, - 0, - 0, - ], - "fcp": 1072, - "fid": "1.35", - "fidRanks": Array [ - 0, - 0, - 100, - ], - "lcp": "1.27", - "lcpRanks": Array [ - 100, - 0, - 0, - ], - "tbt": 0, - } - `); + expectSnapshot(response.body).toMatch(); }); }); }); From dc6b09ac7e77f3731ac03317cd22574c4fc54eac Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 21 Sep 2020 19:34:09 +0200 Subject: [PATCH 3/4] Update --- .../tests/csm/__snapshots__/page_views.snap | 280 ++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap new file mode 100644 index 0000000000000..38b009fc73d34 --- /dev/null +++ b/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap @@ -0,0 +1,280 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`CSM page views when there is data returns page views 1`] = ` +Object { + "items": Array [ + Object { + "x": 1600149947000, + "y": 1, + }, + Object { + "x": 1600149957000, + "y": 0, + }, + Object { + "x": 1600149967000, + "y": 0, + }, + Object { + "x": 1600149977000, + "y": 0, + }, + Object { + "x": 1600149987000, + "y": 0, + }, + Object { + "x": 1600149997000, + "y": 0, + }, + Object { + "x": 1600150007000, + "y": 0, + }, + Object { + "x": 1600150017000, + "y": 0, + }, + Object { + "x": 1600150027000, + "y": 1, + }, + Object { + "x": 1600150037000, + "y": 0, + }, + Object { + "x": 1600150047000, + "y": 0, + }, + Object { + "x": 1600150057000, + "y": 0, + }, + Object { + "x": 1600150067000, + "y": 0, + }, + Object { + "x": 1600150077000, + "y": 1, + }, + Object { + "x": 1600150087000, + "y": 0, + }, + Object { + "x": 1600150097000, + "y": 0, + }, + Object { + "x": 1600150107000, + "y": 0, + }, + Object { + "x": 1600150117000, + "y": 0, + }, + Object { + "x": 1600150127000, + "y": 0, + }, + Object { + "x": 1600150137000, + "y": 0, + }, + Object { + "x": 1600150147000, + "y": 0, + }, + Object { + "x": 1600150157000, + "y": 0, + }, + Object { + "x": 1600150167000, + "y": 0, + }, + Object { + "x": 1600150177000, + "y": 1, + }, + Object { + "x": 1600150187000, + "y": 0, + }, + Object { + "x": 1600150197000, + "y": 0, + }, + Object { + "x": 1600150207000, + "y": 1, + }, + Object { + "x": 1600150217000, + "y": 0, + }, + Object { + "x": 1600150227000, + "y": 0, + }, + Object { + "x": 1600150237000, + "y": 1, + }, + ], + "topItems": Array [], +} +`; + +exports[`CSM page views when there is data returns page views with breakdown 1`] = ` +Object { + "items": Array [ + Object { + "Chrome": 1, + "x": 1600149947000, + "y": 1, + }, + Object { + "x": 1600149957000, + "y": 0, + }, + Object { + "x": 1600149967000, + "y": 0, + }, + Object { + "x": 1600149977000, + "y": 0, + }, + Object { + "x": 1600149987000, + "y": 0, + }, + Object { + "x": 1600149997000, + "y": 0, + }, + Object { + "x": 1600150007000, + "y": 0, + }, + Object { + "x": 1600150017000, + "y": 0, + }, + Object { + "Chrome": 1, + "x": 1600150027000, + "y": 1, + }, + Object { + "x": 1600150037000, + "y": 0, + }, + Object { + "x": 1600150047000, + "y": 0, + }, + Object { + "x": 1600150057000, + "y": 0, + }, + Object { + "x": 1600150067000, + "y": 0, + }, + Object { + "Chrome": 1, + "x": 1600150077000, + "y": 1, + }, + Object { + "x": 1600150087000, + "y": 0, + }, + Object { + "x": 1600150097000, + "y": 0, + }, + Object { + "x": 1600150107000, + "y": 0, + }, + Object { + "x": 1600150117000, + "y": 0, + }, + Object { + "x": 1600150127000, + "y": 0, + }, + Object { + "x": 1600150137000, + "y": 0, + }, + Object { + "x": 1600150147000, + "y": 0, + }, + Object { + "x": 1600150157000, + "y": 0, + }, + Object { + "x": 1600150167000, + "y": 0, + }, + Object { + "Chrome": 1, + "x": 1600150177000, + "y": 1, + }, + Object { + "x": 1600150187000, + "y": 0, + }, + Object { + "x": 1600150197000, + "y": 0, + }, + Object { + "Chrome Mobile": 1, + "x": 1600150207000, + "y": 1, + }, + Object { + "x": 1600150217000, + "y": 0, + }, + Object { + "x": 1600150227000, + "y": 0, + }, + Object { + "Chrome Mobile": 1, + "x": 1600150237000, + "y": 1, + }, + ], + "topItems": Array [ + "Chrome", + "Chrome Mobile", + ], +} +`; + +exports[`CSM page views when there is no data returns empty list 1`] = ` +Object { + "items": Array [], + "topItems": Array [], +} +`; + +exports[`CSM page views when there is no data returns empty list with breakdowns 1`] = ` +Object { + "items": Array [], + "topItems": Array [], +} +`; From 2318ab69d3283006a3246eac4712def861ff8979 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 21 Sep 2020 21:06:14 +0200 Subject: [PATCH 4/4] fix type --- .../components/app/RumDashboard/Charts/PageViewsChart.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx index 4200d48c9604a..904144dec6de9 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx @@ -33,7 +33,10 @@ import { ChartWrapper } from '../ChartWrapper'; import { I18LABELS } from '../translations'; interface Props { - data?: Array>; + data?: { + topItems: string[]; + items: Array>; + }; loading: boolean; } @@ -68,8 +71,7 @@ export function PageViewsChart({ data, loading }: Props) { }); }; - const breakdownAccessors = - data?.topItems?.length > 0 ? data?.topItems : ['y']; + const breakdownAccessors = data?.topItems?.length ? data?.topItems : ['y']; const [darkMode] = useUiSetting$('theme:darkMode');