[8.x] [Perfomance] Track time range picker with onPageReady function (#202889) (#204564)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Perfomance] Track time range picker with `onPageReady` function
(#202889)](https://github.com/elastic/kibana/pull/202889)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Katerina","email":"aikaterini.patticha@elastic.co"},"sourceCommit":{"committedDate":"2024-12-13T13:11:44Z","message":"[Perfomance]
Track time range picker with `onPageReady` function (#202889)\n\n##
Summary\r\n\r\ncloses
https://github.com/elastic/observability-dev/issues/3377\r\n## Metrics
\r\n#### `meta.query_range_secs` - The duration of the selected time
range\r\nin seconds.\r\n#### `meta.query_offset_secs` - The offset from
\"now\" to the\r\n'rangeTo'/end' time picker value in
seconds.\r\n\r\n____\r\n\r\nExtend the `onPageReady` function to support
date ranges in the meta\r\nfield. The function should compute the query
range in seconds based on\r\nthe provided time range and report it to
telemetry as\r\nmeta.query_range_secs.\r\n\r\n\r\n\r\n\r\nIf the
`rangeTo` is different from 'now', calculate the offset. \r\n- A
negative offset indicates that the rangeTo is in the past, \r\n- a
positive offset means it is in the future, \r\n- and zero indicates that
the rangeTo is exactly 'now'.\" \r\n\r\n\r\n\r\n### How to
instrument\r\nTo report the selected time range, pass the `rangeFrom`
and `rangeTo` . \r\n> Failing to pass the correct type will result in TS
error.\r\n\r\n\r\nThen, use this data when invoking
onPageReady:\r\n```\r\n onPageReady({\r\n meta: { rangeFrom, rangeTo
},\r\n });\r\n```\r\n\r\n### Analysis \r\n\r\nMeta is flatten field. In
order to aggregate the data it's necessary to\r\ncreate a run time
field. You can add a field in the\r\n\r\n1. select data view
(`ebt-kibana-*-performance-metrics`) \r\n2. Add a new field\r\n3. Type
double\r\n4. Set value \r\n\r\n`query_range_secs`\r\n```\r\ndef meta =
doc[“meta”].size();\r\nif (meta > 0) {\r\n def range =
doc[“meta.query_range_secs”].size();\r\n if (range > 0) {\r\n // Emit
the value of ‘meta.target’\r\n
emit(Double.parseDouble(doc[“meta.query_range_secs”].value));\r\n
}\r\n}\r\n\r\n```\r\n\r\n`query_offset_secs` \r\n```\r\n\r\ndef meta =
doc[“meta”].size();\r\nif (meta > 0) {\r\n def offset =
doc[“meta.query_offset_secs”].size();\r\n if (offset > 0) {\r\n \r\n
emit(Double.parseDouble(doc[“meta.query_offset_secs”].value));\r\n
}\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n###
Examples\r\n\r\n\r\n<img width=\"1478\" alt=\"Screenshot 2024-12-09 at
19 51
32\"\r\nsrc=\"https://github.com/user-attachments/assets/72f796e1-4f20-487f-b62a-b6a4aead9a4a\">\r\n\r\n<img
width=\"1478\" alt=\"Screenshot 2024-12-09 at 19 56
08\"\r\nsrc=\"https://github.com/user-attachments/assets/c278dc3b-e6f3-47ed-9c90-954d71b59161\">\r\n\r\n<img
width=\"1478\" alt=\"Screenshot 2024-12-09 at 19 53 45
1\"\r\nsrc=\"https://github.com/user-attachments/assets/ef42ecef-48cd-4396-9f5d-c971098d5219\">\r\n\r\n\r\n\r\n\r\n\r\n###
Notes\r\n- Instrumented only 2 solutions as an example (dataset and apm
services)\r\n\r\n### TODO\r\n- [x] Update documentation
-\r\nhttps://github.com/elastic/kibana/pull/204179\r\n- [ ] Update
dashboards (create a runtime field) \r\n- [x] Track offset ( we need to
know if the user selected now or
now)\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"bd1c00fd65848ff27a1bace14363c5ab326c491d","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport
missing","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-infra_services"],"number":202889,"url":"https://github.com/elastic/kibana/pull/202889","mergeCommit":{"message":"[Perfomance]
Track time range picker with `onPageReady` function (#202889)\n\n##
Summary\r\n\r\ncloses
https://github.com/elastic/observability-dev/issues/3377\r\n## Metrics
\r\n#### `meta.query_range_secs` - The duration of the selected time
range\r\nin seconds.\r\n#### `meta.query_offset_secs` - The offset from
\"now\" to the\r\n'rangeTo'/end' time picker value in
seconds.\r\n\r\n____\r\n\r\nExtend the `onPageReady` function to support
date ranges in the meta\r\nfield. The function should compute the query
range in seconds based on\r\nthe provided time range and report it to
telemetry as\r\nmeta.query_range_secs.\r\n\r\n\r\n\r\n\r\nIf the
`rangeTo` is different from 'now', calculate the offset. \r\n- A
negative offset indicates that the rangeTo is in the past, \r\n- a
positive offset means it is in the future, \r\n- and zero indicates that
the rangeTo is exactly 'now'.\" \r\n\r\n\r\n\r\n### How to
instrument\r\nTo report the selected time range, pass the `rangeFrom`
and `rangeTo` . \r\n> Failing to pass the correct type will result in TS
error.\r\n\r\n\r\nThen, use this data when invoking
onPageReady:\r\n```\r\n onPageReady({\r\n meta: { rangeFrom, rangeTo
},\r\n });\r\n```\r\n\r\n### Analysis \r\n\r\nMeta is flatten field. In
order to aggregate the data it's necessary to\r\ncreate a run time
field. You can add a field in the\r\n\r\n1. select data view
(`ebt-kibana-*-performance-metrics`) \r\n2. Add a new field\r\n3. Type
double\r\n4. Set value \r\n\r\n`query_range_secs`\r\n```\r\ndef meta =
doc[“meta”].size();\r\nif (meta > 0) {\r\n def range =
doc[“meta.query_range_secs”].size();\r\n if (range > 0) {\r\n // Emit
the value of ‘meta.target’\r\n
emit(Double.parseDouble(doc[“meta.query_range_secs”].value));\r\n
}\r\n}\r\n\r\n```\r\n\r\n`query_offset_secs` \r\n```\r\n\r\ndef meta =
doc[“meta”].size();\r\nif (meta > 0) {\r\n def offset =
doc[“meta.query_offset_secs”].size();\r\n if (offset > 0) {\r\n \r\n
emit(Double.parseDouble(doc[“meta.query_offset_secs”].value));\r\n
}\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n###
Examples\r\n\r\n\r\n<img width=\"1478\" alt=\"Screenshot 2024-12-09 at
19 51
32\"\r\nsrc=\"https://github.com/user-attachments/assets/72f796e1-4f20-487f-b62a-b6a4aead9a4a\">\r\n\r\n<img
width=\"1478\" alt=\"Screenshot 2024-12-09 at 19 56
08\"\r\nsrc=\"https://github.com/user-attachments/assets/c278dc3b-e6f3-47ed-9c90-954d71b59161\">\r\n\r\n<img
width=\"1478\" alt=\"Screenshot 2024-12-09 at 19 53 45
1\"\r\nsrc=\"https://github.com/user-attachments/assets/ef42ecef-48cd-4396-9f5d-c971098d5219\">\r\n\r\n\r\n\r\n\r\n\r\n###
Notes\r\n- Instrumented only 2 solutions as an example (dataset and apm
services)\r\n\r\n### TODO\r\n- [x] Update documentation
-\r\nhttps://github.com/elastic/kibana/pull/204179\r\n- [ ] Update
dashboards (create a runtime field) \r\n- [x] Track offset ( we need to
know if the user selected now or
now)\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"bd1c00fd65848ff27a1bace14363c5ab326c491d"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/202889","number":202889,"mergeCommit":{"message":"[Perfomance]
Track time range picker with `onPageReady` function (#202889)\n\n##
Summary\r\n\r\ncloses
https://github.com/elastic/observability-dev/issues/3377\r\n## Metrics
\r\n#### `meta.query_range_secs` - The duration of the selected time
range\r\nin seconds.\r\n#### `meta.query_offset_secs` - The offset from
\"now\" to the\r\n'rangeTo'/end' time picker value in
seconds.\r\n\r\n____\r\n\r\nExtend the `onPageReady` function to support
date ranges in the meta\r\nfield. The function should compute the query
range in seconds based on\r\nthe provided time range and report it to
telemetry as\r\nmeta.query_range_secs.\r\n\r\n\r\n\r\n\r\nIf the
`rangeTo` is different from 'now', calculate the offset. \r\n- A
negative offset indicates that the rangeTo is in the past, \r\n- a
positive offset means it is in the future, \r\n- and zero indicates that
the rangeTo is exactly 'now'.\" \r\n\r\n\r\n\r\n### How to
instrument\r\nTo report the selected time range, pass the `rangeFrom`
and `rangeTo` . \r\n> Failing to pass the correct type will result in TS
error.\r\n\r\n\r\nThen, use this data when invoking
onPageReady:\r\n```\r\n onPageReady({\r\n meta: { rangeFrom, rangeTo
},\r\n });\r\n```\r\n\r\n### Analysis \r\n\r\nMeta is flatten field. In
order to aggregate the data it's necessary to\r\ncreate a run time
field. You can add a field in the\r\n\r\n1. select data view
(`ebt-kibana-*-performance-metrics`) \r\n2. Add a new field\r\n3. Type
double\r\n4. Set value \r\n\r\n`query_range_secs`\r\n```\r\ndef meta =
doc[“meta”].size();\r\nif (meta > 0) {\r\n def range =
doc[“meta.query_range_secs”].size();\r\n if (range > 0) {\r\n // Emit
the value of ‘meta.target’\r\n
emit(Double.parseDouble(doc[“meta.query_range_secs”].value));\r\n
}\r\n}\r\n\r\n```\r\n\r\n`query_offset_secs` \r\n```\r\n\r\ndef meta =
doc[“meta”].size();\r\nif (meta > 0) {\r\n def offset =
doc[“meta.query_offset_secs”].size();\r\n if (offset > 0) {\r\n \r\n
emit(Double.parseDouble(doc[“meta.query_offset_secs”].value));\r\n
}\r\n}\r\n\r\n```\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n###
Examples\r\n\r\n\r\n<img width=\"1478\" alt=\"Screenshot 2024-12-09 at
19 51
32\"\r\nsrc=\"https://github.com/user-attachments/assets/72f796e1-4f20-487f-b62a-b6a4aead9a4a\">\r\n\r\n<img
width=\"1478\" alt=\"Screenshot 2024-12-09 at 19 56
08\"\r\nsrc=\"https://github.com/user-attachments/assets/c278dc3b-e6f3-47ed-9c90-954d71b59161\">\r\n\r\n<img
width=\"1478\" alt=\"Screenshot 2024-12-09 at 19 53 45
1\"\r\nsrc=\"https://github.com/user-attachments/assets/ef42ecef-48cd-4396-9f5d-c971098d5219\">\r\n\r\n\r\n\r\n\r\n\r\n###
Notes\r\n- Instrumented only 2 solutions as an example (dataset and apm
services)\r\n\r\n### TODO\r\n- [x] Update documentation
-\r\nhttps://github.com/elastic/kibana/pull/204179\r\n- [ ] Update
dashboards (create a runtime field) \r\n- [x] Track offset ( we need to
know if the user selected now or
now)\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"bd1c00fd65848ff27a1bace14363c5ab326c491d"}}]}]
BACKPORT-->
This commit is contained in:
Katerina 2024-12-17 16:17:58 +02:00 committed by GitHub
parent 16f4187ab3
commit 64de0709de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 398 additions and 58 deletions

View file

@ -128,4 +128,31 @@ describe('trackPerformanceMeasureEntries', () => {
value1: 'value1',
});
});
test('reports an analytics event with query metadata', () => {
setupMockPerformanceObserver([
{
name: '/',
entryType: 'measure',
startTime: 100,
duration: 1000,
detail: {
eventName: 'kibana:plugin_render_time',
type: 'kibana:performance',
meta: {
queryRangeSecs: 86400,
queryOffsetSecs: 0,
},
},
},
]);
trackPerformanceMeasureEntries(analyticsClientMock, true);
expect(analyticsClientMock.reportEvent).toHaveBeenCalledTimes(1);
expect(analyticsClientMock.reportEvent).toHaveBeenCalledWith('performance_metric', {
duration: 1000,
eventName: 'kibana:plugin_render_time',
meta: { target: '/', query_range_secs: 86400, query_offset_secs: 0 },
});
});
});

View file

@ -27,6 +27,7 @@ export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDev
if (entry.entryType === 'measure' && entry.detail?.type === 'kibana:performance') {
const target = entry?.name;
const duration = entry.duration;
const meta = entry.detail?.meta;
const customMetrics = Object.keys(entry.detail?.customMetrics ?? {}).reduce(
(acc, metric) => {
if (ALLOWED_CUSTOM_METRICS_KEYS_VALUES.includes(metric)) {
@ -72,6 +73,8 @@ export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDev
...customMetrics,
meta: {
target,
query_range_secs: meta?.queryRangeSecs,
query_offset_secs: meta?.queryOffsetSecs,
},
});
} catch (error) {

View file

@ -25,6 +25,7 @@ SHARED_DEPS = [
"@npm//@elastic/apm-rum-core",
"@npm//react",
"@npm//react-router-dom",
"//packages/kbn-timerange"
]
js_library(

View file

@ -0,0 +1,67 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import {
getDateRange,
getOffsetFromNowInSeconds,
getTimeDifferenceInSeconds,
} from '@kbn/timerange';
import { perfomanceMarkers } from '../../performance_markers';
import { EventData } from '../performance_context';
interface PerformanceMeta {
queryRangeSecs: number;
queryOffsetSecs: number;
}
export function measureInteraction() {
performance.mark(perfomanceMarkers.startPageChange);
const trackedRoutes: string[] = [];
return {
/**
* Marks the end of the page ready state and measures the performance between the start of the page change and the end of the page ready state.
* @param pathname - The pathname of the page.
* @param customMetrics - Custom metrics to be included in the performance measure.
*/
pageReady(pathname: string, eventData?: EventData) {
let performanceMeta: PerformanceMeta | undefined;
performance.mark(perfomanceMarkers.endPageReady);
if (eventData?.meta) {
const { rangeFrom, rangeTo } = eventData.meta;
// Convert the date range to epoch timestamps (in milliseconds)
const dateRangesInEpoch = getDateRange({
from: rangeFrom,
to: rangeTo,
});
performanceMeta = {
queryRangeSecs: getTimeDifferenceInSeconds(dateRangesInEpoch),
queryOffsetSecs:
rangeTo === 'now' ? 0 : getOffsetFromNowInSeconds(dateRangesInEpoch.endDate),
};
}
if (!trackedRoutes.includes(pathname)) {
performance.measure(pathname, {
detail: {
eventName: 'kibana:plugin_render_time',
type: 'kibana:performance',
customMetrics: eventData?.customMetrics,
meta: performanceMeta,
},
start: perfomanceMarkers.startPageChange,
end: perfomanceMarkers.endPageReady,
});
trackedRoutes.push(pathname);
}
},
};
}

View file

@ -0,0 +1,161 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { measureInteraction } from '.';
import { perfomanceMarkers } from '../../performance_markers';
describe('measureInteraction', () => {
afterAll(() => {
jest.restoreAllMocks();
});
beforeEach(() => {
jest.clearAllMocks();
performance.mark = jest.fn();
performance.measure = jest.fn();
});
it('should mark the start of the page change', () => {
measureInteraction();
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.startPageChange);
});
it('should mark the end of the page ready state and measure performance', () => {
const interaction = measureInteraction();
const pathname = '/test-path';
interaction.pageReady(pathname);
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady);
expect(performance.measure).toHaveBeenCalledWith(pathname, {
detail: {
eventName: 'kibana:plugin_render_time',
type: 'kibana:performance',
},
start: perfomanceMarkers.startPageChange,
end: perfomanceMarkers.endPageReady,
});
});
it('should include custom metrics and meta in the performance measure', () => {
const interaction = measureInteraction();
const pathname = '/test-path';
const eventData = {
customMetrics: { key1: 'foo-metric', value1: 100 },
meta: { rangeFrom: 'now-15m', rangeTo: 'now' },
};
interaction.pageReady(pathname, eventData);
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady);
expect(performance.measure).toHaveBeenCalledWith(pathname, {
detail: {
eventName: 'kibana:plugin_render_time',
type: 'kibana:performance',
customMetrics: eventData.customMetrics,
meta: {
queryRangeSecs: 900,
queryOffsetSecs: 0,
},
},
end: 'end::pageReady',
start: 'start::pageChange',
});
});
it('should handle absolute date format correctly', () => {
const interaction = measureInteraction();
const pathname = '/test-path';
jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z
const eventData = {
meta: { rangeFrom: '2024-12-09T00:00:00Z', rangeTo: '2024-12-09T00:30:00Z' },
};
interaction.pageReady(pathname, eventData);
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady);
expect(performance.measure).toHaveBeenCalledWith(pathname, {
detail: {
eventName: 'kibana:plugin_render_time',
type: 'kibana:performance',
customMetrics: undefined,
meta: {
queryRangeSecs: 1800,
queryOffsetSecs: 0,
},
},
end: 'end::pageReady',
start: 'start::pageChange',
});
});
it('should handle negative offset when rangeTo is in the past', () => {
const interaction = measureInteraction();
const pathname = '/test-path';
jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z
const eventData = {
meta: { rangeFrom: '2024-12-08T00:00:00Z', rangeTo: '2024-12-09T00:00:00Z' },
};
interaction.pageReady(pathname, eventData);
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady);
expect(performance.measure).toHaveBeenCalledWith(pathname, {
detail: {
eventName: 'kibana:plugin_render_time',
type: 'kibana:performance',
customMetrics: undefined,
meta: {
queryRangeSecs: 86400,
queryOffsetSecs: -1800,
},
},
end: 'end::pageReady',
start: 'start::pageChange',
});
});
it('should handle positive offset when rangeTo is in the future', () => {
const interaction = measureInteraction();
const pathname = '/test-path';
jest.spyOn(global.Date, 'now').mockReturnValue(1733704200000); // 2024-12-09T00:30:00Z
const eventData = {
meta: { rangeFrom: '2024-12-08T01:00:00Z', rangeTo: '2024-12-09T01:00:00Z' },
};
interaction.pageReady(pathname, eventData);
expect(performance.mark).toHaveBeenCalledWith(perfomanceMarkers.endPageReady);
expect(performance.measure).toHaveBeenCalledWith(pathname, {
detail: {
eventName: 'kibana:plugin_render_time',
type: 'kibana:performance',
customMetrics: undefined,
meta: {
queryRangeSecs: 86400,
queryOffsetSecs: 1800,
},
},
end: 'end::pageReady',
start: 'start::pageChange',
});
});
it('should not measure the same route twice', () => {
const interaction = measureInteraction();
const pathname = '/test-path';
interaction.pageReady(pathname);
interaction.pageReady(pathname);
expect(performance.measure).toHaveBeenCalledTimes(1);
});
});

View file

@ -10,38 +10,19 @@
import React, { useMemo, useState } from 'react';
import { afterFrame } from '@elastic/apm-rum-core';
import { useLocation } from 'react-router-dom';
import { perfomanceMarkers } from '../performance_markers';
import { PerformanceApi, PerformanceContext } from './use_performance_context';
import { PerformanceMetricEvent } from '../../performance_metric_events';
import { measureInteraction } from './measure_interaction';
export type CustomMetrics = Omit<PerformanceMetricEvent, 'eventName' | 'meta' | 'duration'>;
function measureInteraction() {
performance.mark(perfomanceMarkers.startPageChange);
const trackedRoutes: string[] = [];
return {
/**
* Marks the end of the page ready state and measures the performance between the start of the page change and the end of the page ready state.
* @param pathname - The pathname of the page.
* @param customMetrics - Custom metrics to be included in the performance measure.
*/
pageReady(pathname: string, customMetrics?: CustomMetrics) {
performance.mark(perfomanceMarkers.endPageReady);
if (!trackedRoutes.includes(pathname)) {
performance.measure(pathname, {
detail: {
eventName: 'kibana:plugin_render_time',
type: 'kibana:performance',
customMetrics,
},
start: perfomanceMarkers.startPageChange,
end: perfomanceMarkers.endPageReady,
});
trackedRoutes.push(pathname);
}
},
};
export interface Meta {
rangeFrom: string;
rangeTo: string;
}
export interface EventData {
customMetrics?: CustomMetrics;
meta?: Meta;
}
export function PerformanceContextProvider({ children }: { children: React.ReactElement }) {
@ -61,9 +42,9 @@ export function PerformanceContextProvider({ children }: { children: React.React
const api = useMemo<PerformanceApi>(
() => ({
onPageReady(customMetrics) {
onPageReady(eventData) {
if (isRendered) {
interaction.pageReady(location.pathname, customMetrics);
interaction.pageReady(location.pathname, eventData);
}
},
}),

View file

@ -8,18 +8,22 @@
*/
import { useEffect, useState } from 'react';
import { CustomMetrics } from './performance_context';
import type { CustomMetrics, Meta } from './performance_context';
import { usePerformanceContext } from '../../..';
export const usePageReady = (state: { customMetrics?: CustomMetrics; isReady: boolean }) => {
export const usePageReady = (state: {
customMetrics?: CustomMetrics;
isReady: boolean;
meta?: Meta;
}) => {
const { onPageReady } = usePerformanceContext();
const [isReported, setIsReported] = useState(false);
useEffect(() => {
if (state.isReady && !isReported) {
onPageReady(state.customMetrics);
onPageReady({ customMetrics: state.customMetrics, meta: state.meta });
setIsReported(true);
}
}, [isReported, onPageReady, state.customMetrics, state.isReady]);
}, [isReported, onPageReady, state.customMetrics, state.isReady, state.meta]);
};

View file

@ -8,14 +8,13 @@
*/
import { createContext, useContext } from 'react';
import { CustomMetrics } from './performance_context';
import type { EventData } from './performance_context';
export interface PerformanceApi {
/**
* Marks the end of the page ready state and measures the performance between the start of the page change and the end of the page ready state.
* @param customMetrics - Custom metrics to be included in the performance measure.
* @param eventData - Data to send with the performance measure, conforming the structure of a {@link EventData}.
*/
onPageReady(customMetrics?: CustomMetrics): void;
onPageReady(eventData?: EventData): void;
}
export const PerformanceContext = createContext<PerformanceApi | undefined>(undefined);

View file

@ -5,8 +5,6 @@
"types": ["jest", "node"]
},
"include": ["**/*.ts", "**/*.tsx"],
"kbn_references": [
"@kbn/logging-mocks"
],
"kbn_references": ["@kbn/logging-mocks", "@kbn/timerange"],
"exclude": ["target/**/*"]
}

View file

@ -0,0 +1,28 @@
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
SRCS = glob(
[
"**/*.ts",
"**/*.js",
],
exclude = [
"**/*.config.js",
"**/*.mock.*",
"**/*.test.*",
"**/*.stories.*",
"**/__snapshots__/**",
"**/integration_tests/**",
"**/mocks/**",
"**/scripts/**",
"**/storybook/**",
"**/test_fixtures/**",
"**/test_helpers/**",
],
)
js_library(
name = "kbn-io-ts-utils",
package_name = "@kbn/io-ts-utils",
srcs = ["package.json"] + SRCS,
visibility = ["//visibility:public"],
)

View file

@ -0,0 +1,28 @@
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
SRCS = glob(
[
"**/*.ts",
"**/*.js",
],
exclude = [
"**/*.config.js",
"**/*.mock.*",
"**/*.test.*",
"**/*.stories.*",
"**/__snapshots__/**",
"**/integration_tests/**",
"**/mocks/**",
"**/scripts/**",
"**/storybook/**",
"**/test_fixtures/**",
"**/test_helpers/**",
],
)
js_library(
name = "kbn-timerange",
package_name = "@kbn/timerange",
srcs = ["package.json"] + SRCS,
visibility = ["//visibility:public"],
)

View file

@ -7,4 +7,9 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export { getDateRange, getDateISORange } from './src';
export {
getDateRange,
getDateISORange,
getOffsetFromNowInSeconds,
getTimeDifferenceInSeconds,
} from './src';

View file

@ -1,9 +1,7 @@
{
"type": "shared-common",
"id": "@kbn/timerange",
"owner": [
"@elastic/obs-ux-logs-team"
],
"group": "observability",
"visibility": "private"
}
"owner": ["@elastic/obs-ux-logs-team"],
"group": "platform",
"visibility": "shared"
}

View file

@ -52,3 +52,24 @@ export function getDateISORange({ from, to }: { from: string; to: string }) {
endDate,
};
}
export function getTimeDifferenceInSeconds({
startDate,
endDate,
}: {
startDate: number;
endDate: number;
}): number {
if (!startDate || !endDate || startDate > endDate) {
throw new Error(`Invalid Dates: from: ${startDate}, to: ${endDate}`);
}
const rangeInSeconds = (endDate - startDate) / 1000;
return Math.round(rangeInSeconds);
}
export function getOffsetFromNowInSeconds(epochDate: number) {
const now = Date.now();
return Math.round((epochDate - now) / 1000);
}

View file

@ -180,6 +180,9 @@ export function ServiceInventory() {
const [renderedItems, setRenderedItems] = useState<ServiceListItem[]>([]);
const mainStatisticsFetch = useServicesMainStatisticsFetcher(debouncedSearchQuery);
const { mainStatisticsData, mainStatisticsStatus } = mainStatisticsFetch;
const {
query: { rangeFrom, rangeTo },
} = useApmParams('/services');
const displayHealthStatus = mainStatisticsData.items.some((item) => 'healthStatus' in item);
@ -285,9 +288,11 @@ export function ServiceInventory() {
mainStatisticsStatus === FETCH_STATUS.SUCCESS &&
comparisonFetch.status === FETCH_STATUS.SUCCESS
) {
onPageReady();
onPageReady({
meta: { rangeFrom, rangeTo },
});
}
}, [mainStatisticsStatus, comparisonFetch.status, onPageReady]);
}, [mainStatisticsStatus, comparisonFetch.status, onPageReady, rangeFrom, rangeTo]);
return (
<>

View file

@ -186,7 +186,9 @@ export function ServiceMap({
}
if (status === FETCH_STATUS.SUCCESS) {
onPageReady();
onPageReady({
meta: { rangeFrom: start, rangeTo: end },
});
}
return (

View file

@ -135,15 +135,20 @@ export function TraceList({ response }: Props) {
const { data: { items } = { items: [] }, status } = response;
const { onPageReady } = usePerformanceContext();
const { query } = useApmParams('/traces');
const {
query,
query: { rangeFrom, rangeTo },
} = useApmParams('/traces');
const traceListColumns = useMemo(() => getTraceListColumns({ query }), [query]);
useEffect(() => {
if (status === FETCH_STATUS.SUCCESS) {
onPageReady();
onPageReady({
meta: { rangeFrom, rangeTo },
});
}
}, [status, onPageReady]);
}, [status, onPageReady, rangeFrom, rangeTo]);
return (
<ManagedTable

View file

@ -8,7 +8,6 @@
import React from 'react';
import { euiThemeVars } from '@kbn/ui-theme';
import { css } from '@emotion/react';
import {
EuiFlexGroup,
EuiPanel,
@ -30,9 +29,11 @@ import {
summaryPanelQualityTooltipText,
} from '../../../../common/translations';
import { mapPercentagesToQualityCounts } from '../../quality_indicator';
import { useDatasetQualityFilters } from '../../../hooks/use_dataset_quality_filters';
export function DatasetsQualityIndicators() {
const { onPageReady } = usePerformanceContext();
const { timeRange } = useDatasetQualityFilters();
const {
datasetsQuality,
isDatasetsQualityLoading,
@ -46,10 +47,16 @@ export function DatasetsQualityIndicators() {
if (!isDatasetsQualityLoading && (numberOfDatasets || numberOfDocuments)) {
onPageReady({
key1: 'datasets',
value1: numberOfDatasets,
key2: 'documents',
value2: numberOfDocuments,
customMetrics: {
key1: 'datasets',
value1: numberOfDatasets,
key2: 'documents',
value2: numberOfDocuments,
},
meta: {
rangeFrom: timeRange.from,
rangeTo: timeRange.to,
},
});
}