diff --git a/src/core/packages/notifications/browser-internal/src/toasts/toasts_api.test.ts b/src/core/packages/notifications/browser-internal/src/toasts/toasts_api.test.ts index 253c6376eb9b1..ecbfa285fd8e6 100644 --- a/src/core/packages/notifications/browser-internal/src/toasts/toasts_api.test.ts +++ b/src/core/packages/notifications/browser-internal/src/toasts/toasts_api.test.ts @@ -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' }, + }); }); }); diff --git a/src/core/packages/notifications/browser-internal/src/toasts/toasts_api.tsx b/src/core/packages/notifications/browser-internal/src/toasts/toasts_api.tsx index ece936f0cf6ce..bfa3c130171e3 100644 --- a/src/core/packages/notifications/browser-internal/src/toasts/toasts_api.tsx +++ b/src/core/packages/notifications/browser-internal/src/toasts/toasts_api.tsx @@ -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',