[9.0] Track toast errors using apm-rum (#217948) (#218581)

# Backport

This will backport the following commits from `main` to `9.0`:
- [Track toast errors using apm-rum
(#217948)](https://github.com/elastic/kibana/pull/217948)

<!--- Backport version: 9.6.6 -->

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

<!--BACKPORT [{"author":{"name":"Maryam
Saeidi","email":"maryam.saeidi@elastic.co"},"sourceCommit":{"committedDate":"2025-04-15T07:49:11Z","message":"Track
toast errors using apm-rum (#217948)\n\nCloses
https://github.com/elastic/observability-dev/issues/4022\n\n##
Summary\n\nIn this PR, we are capturing toast errors using
apm-rum:\n\n\nhttps://github.com/user-attachments/assets/b61529f9-ab8e-4171-9042-0884e11eb385\n\n\nErrorType
is available in labels which this feature was added to the rum\nagent in
this\n[PR](https://github.com/elastic/apm-agent-rum-js/pull/1594).\n\n\n###
🧪 How to test\n\nAdd the following to your kibana.yml
file:\n\n```\nelastic.apm.active:
true\nelastic.apm.transactionSampleRate: 1.0\nelastic.apm.environment:
yourName <-- Change to your name\n```\n<details>\n<summary>Throw a toast
error</summary>\n\nAdd this code to a page as
[alerts\npage](https://github.com/elastic/kibana/blob/main/x-pack/solutions/observability/plugins/observability/public/pages/alerts/alerts.tsx)\nand
visit
http://localhost:5601/kibana/app/observability/alerts\n\n```\nuseEffect(()
=> {\n const error = new Error('Mary test error > toasts.addError');\n
toasts.addError(error, { title: 'Testing error toast', toastMessage:
error.message });\n toasts.addDanger('Testing danger toast');\n },
[]);\n```\n\n</details>\n\nThen
visit\n[kibana-cloud-apm.elastic.dev](https://kibana-cloud-apm.elastic.dev/app/apm/services/kibana-frontend/errors?comparisonEnabled=true&environment=ENVIRONMENT_ALL&kuery=&latencyAggregationType=avg&offset=1d&rangeFrom=now-1h&rangeTo=now&serviceGroup=&transactionType=page-load)\nfiltered
for `yourName` in the
environment.","sha":"ae9e5d679ce1ace883ec7bae432b46f9c77f2758","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:version","v9.1.0","v8.19.0","v9.0.1"],"title":"Track
toast errors using
apm-rum","number":217948,"url":"https://github.com/elastic/kibana/pull/217948","mergeCommit":{"message":"Track
toast errors using apm-rum (#217948)\n\nCloses
https://github.com/elastic/observability-dev/issues/4022\n\n##
Summary\n\nIn this PR, we are capturing toast errors using
apm-rum:\n\n\nhttps://github.com/user-attachments/assets/b61529f9-ab8e-4171-9042-0884e11eb385\n\n\nErrorType
is available in labels which this feature was added to the rum\nagent in
this\n[PR](https://github.com/elastic/apm-agent-rum-js/pull/1594).\n\n\n###
🧪 How to test\n\nAdd the following to your kibana.yml
file:\n\n```\nelastic.apm.active:
true\nelastic.apm.transactionSampleRate: 1.0\nelastic.apm.environment:
yourName <-- Change to your name\n```\n<details>\n<summary>Throw a toast
error</summary>\n\nAdd this code to a page as
[alerts\npage](https://github.com/elastic/kibana/blob/main/x-pack/solutions/observability/plugins/observability/public/pages/alerts/alerts.tsx)\nand
visit
http://localhost:5601/kibana/app/observability/alerts\n\n```\nuseEffect(()
=> {\n const error = new Error('Mary test error > toasts.addError');\n
toasts.addError(error, { title: 'Testing error toast', toastMessage:
error.message });\n toasts.addDanger('Testing danger toast');\n },
[]);\n```\n\n</details>\n\nThen
visit\n[kibana-cloud-apm.elastic.dev](https://kibana-cloud-apm.elastic.dev/app/apm/services/kibana-frontend/errors?comparisonEnabled=true&environment=ENVIRONMENT_ALL&kuery=&latencyAggregationType=avg&offset=1d&rangeFrom=now-1h&rangeTo=now&serviceGroup=&transactionType=page-load)\nfiltered
for `yourName` in the
environment.","sha":"ae9e5d679ce1ace883ec7bae432b46f9c77f2758"}},"sourceBranch":"main","suggestedTargetBranches":["9.0"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/217948","number":217948,"mergeCommit":{"message":"Track
toast errors using apm-rum (#217948)\n\nCloses
https://github.com/elastic/observability-dev/issues/4022\n\n##
Summary\n\nIn this PR, we are capturing toast errors using
apm-rum:\n\n\nhttps://github.com/user-attachments/assets/b61529f9-ab8e-4171-9042-0884e11eb385\n\n\nErrorType
is available in labels which this feature was added to the rum\nagent in
this\n[PR](https://github.com/elastic/apm-agent-rum-js/pull/1594).\n\n\n###
🧪 How to test\n\nAdd the following to your kibana.yml
file:\n\n```\nelastic.apm.active:
true\nelastic.apm.transactionSampleRate: 1.0\nelastic.apm.environment:
yourName <-- Change to your name\n```\n<details>\n<summary>Throw a toast
error</summary>\n\nAdd this code to a page as
[alerts\npage](https://github.com/elastic/kibana/blob/main/x-pack/solutions/observability/plugins/observability/public/pages/alerts/alerts.tsx)\nand
visit
http://localhost:5601/kibana/app/observability/alerts\n\n```\nuseEffect(()
=> {\n const error = new Error('Mary test error > toasts.addError');\n
toasts.addError(error, { title: 'Testing error toast', toastMessage:
error.message });\n toasts.addDanger('Testing danger toast');\n },
[]);\n```\n\n</details>\n\nThen
visit\n[kibana-cloud-apm.elastic.dev](https://kibana-cloud-apm.elastic.dev/app/apm/services/kibana-frontend/errors?comparisonEnabled=true&environment=ENVIRONMENT_ALL&kuery=&latencyAggregationType=avg&offset=1d&rangeFrom=now-1h&rangeTo=now&serviceGroup=&transactionType=page-load)\nfiltered
for `yourName` in the
environment.","sha":"ae9e5d679ce1ace883ec7bae432b46f9c77f2758"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.0","label":"v9.0.1","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"url":"https://github.com/elastic/kibana/pull/218531","number":218531,"branch":"8.19","state":"MERGED","mergeCommit":{"sha":"caa2fbb25501b65fa704c8f9cf2821cc4e18ea34","message":"[8.19]
Track toast errors using apm-rum (#217948) (#218531)\n\n#
Backport\n\nThis will backport the following commits from `main` to
`8.19`:\n- [Track toast errors using
apm-rum\n(#217948)](https://github.com/elastic/kibana/pull/217948)\n\n\n\n###
Questions ?\nPlease refer to the [Backport
tool\ndocumentation](https://github.com/sorenlouv/backport)\n\n"}}]}]
BACKPORT-->
This commit is contained in:
Maryam Saeidi 2025-04-17 19:42:06 +02:00 committed by GitHub
parent 99531c2900
commit c725d4beff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 52 additions and 2 deletions

View file

@ -11,12 +11,19 @@ import { firstValueFrom } from 'rxjs';
import { ToastsApi } from './toasts_api';
import { apm } from '@elastic/apm-rum';
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
import { i18nServiceMock } from '@kbn/core-i18n-browser-mocks';
import { themeServiceMock } from '@kbn/core-theme-browser-mocks';
import { userProfileServiceMock } from '@kbn/core-user-profile-browser-mocks';
jest.mock('@elastic/apm-rum', () => ({
apm: {
captureError: jest.fn(),
},
}));
async function getCurrentToasts(toasts: ToastsApi) {
return await firstValueFrom(toasts.get$());
}
@ -213,6 +220,9 @@ describe('#addDanger()', () => {
it('adds a danger toast', async () => {
const toasts = new ToastsApi(toastDeps());
expect(toasts.addDanger({})).toHaveProperty('color', 'danger');
expect(apm.captureError).toBeCalledWith('No title or text is provided.', {
labels: { errorType: 'ToastDanger' },
});
});
it('returns the created toast', async () => {
@ -220,12 +230,18 @@ describe('#addDanger()', () => {
const toast = toasts.addDanger({});
const currentToasts = await getCurrentToasts(toasts);
expect(currentToasts[0]).toBe(toast);
expect(apm.captureError).toBeCalledWith('No title or text is provided.', {
labels: { errorType: 'ToastDanger' },
});
});
it('fallbacks to default values for undefined properties', async () => {
const toasts = new ToastsApi(toastDeps());
const toast = toasts.addDanger({ title: 'foo', toastLifeTimeMs: undefined });
expect(toast.toastLifeTimeMs).toEqual(10000);
expect(apm.captureError).toBeCalledWith('foo', {
labels: { errorType: 'ToastDanger' },
});
});
});
@ -233,16 +249,24 @@ describe('#addError', () => {
it('adds an error toast', async () => {
const toasts = new ToastsApi(toastDeps());
toasts.start(startDeps());
const toast = toasts.addError(new Error('unexpected error'), { title: 'Something went wrong' });
const error = new Error('unexpected error');
const toast = toasts.addError(error, { title: 'Something went wrong' });
expect(toast).toHaveProperty('color', 'danger');
expect(toast).toHaveProperty('title', 'Something went wrong');
expect(apm.captureError).toBeCalledWith(error, {
labels: { errorType: 'ToastError' },
});
});
it('returns the created toast', async () => {
const toasts = new ToastsApi(toastDeps());
toasts.start(startDeps());
const toast = toasts.addError(new Error('unexpected error'), { title: 'Something went wrong' });
const error = new Error('unexpected error');
const toast = toasts.addError(error, { title: 'Something went wrong' });
const currentToasts = await getCurrentToasts(toasts);
expect(currentToasts[0]).toBe(toast);
expect(apm.captureError).toBeCalledWith(error, {
labels: { errorType: 'ToastError' },
});
});
});

View file

@ -11,6 +11,7 @@ import React from 'react';
import * as Rx from 'rxjs';
import { omitBy, isUndefined } from 'lodash';
import { apm } from '@elastic/apm-rum';
import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser';
import type { I18nStart } from '@kbn/core-i18n-browser';
import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
@ -37,6 +38,24 @@ const normalizeToast = (toastOrTitle: ToastInput): ToastInputFields => {
return omitBy(toastOrTitle, isUndefined);
};
const getToastTitleOrText = (toastOrTitle: ToastInput): string => {
if (typeof toastOrTitle === 'string') {
return toastOrTitle;
} else if (typeof toastOrTitle.title === 'string') {
return toastOrTitle.title;
} else if (typeof toastOrTitle.text === 'string') {
return toastOrTitle.text;
}
return 'No title or text is provided.';
};
const getApmLabels = (errorType: 'ToastError' | 'ToastDanger') => {
return {
errorType,
};
};
interface StartDeps {
analytics: AnalyticsServiceStart;
overlays: OverlayStart;
@ -158,6 +177,10 @@ export class ToastsApi implements IToasts {
* @returns a {@link Toast}
*/
public addDanger(toastOrTitle: ToastInput, options?: ToastOptions) {
const toastTitle = getToastTitleOrText(toastOrTitle);
apm.captureError(toastTitle, {
labels: getApmLabels('ToastDanger'),
});
return this.add({
color: 'danger',
iconType: 'error',
@ -175,6 +198,9 @@ export class ToastsApi implements IToasts {
* @returns a {@link Toast}
*/
public addError(error: Error, options: ErrorToastOptions) {
apm.captureError(error, {
labels: getApmLabels('ToastError'),
});
const message = options.toastMessage || error.message;
return this.add({
color: 'danger',