From 329ec2624a514afa0c042800a00341fdc811e068 Mon Sep 17 00:00:00 2001 From: Marco Schaefer <47627413+codecapitano@users.noreply.github.com> Date: Fri, 5 Jan 2024 14:47:32 +0100 Subject: [PATCH] fix: Faro usage issue (#80033) --- .../GrafanaJavascriptAgentBackend.test.ts | 205 ++---------------- .../GrafanaJavascriptAgentBackend.ts | 15 +- 2 files changed, 27 insertions(+), 193 deletions(-) diff --git a/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.test.ts b/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.test.ts index d2da4ba6b8a..dfb6d424324 100644 --- a/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.test.ts +++ b/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.test.ts @@ -1,12 +1,11 @@ import { BuildInfo } from '@grafana/data'; import { GrafanaEdition } from '@grafana/data/src/types/config'; -import { BaseTransport, Instrumentation, InternalLoggerLevel } from '@grafana/faro-core'; +import { Instrumentation } from '@grafana/faro-core'; import * as faroWebSdkModule from '@grafana/faro-web-sdk'; import { FetchTransport, initializeFaro } from '@grafana/faro-web-sdk'; -import { EchoEventType, EchoMeta } from '@grafana/runtime'; +import { EchoSrvTransport } from './EchoSrvTransport'; import { GrafanaJavascriptAgentBackend, GrafanaJavascriptAgentBackendOptions } from './GrafanaJavascriptAgentBackend'; -import { GrafanaJavascriptAgentEchoEvent } from './types'; describe('GrafanaJavascriptAgentEchoBackend', () => { beforeEach(() => { @@ -44,20 +43,16 @@ describe('GrafanaJavascriptAgentEchoBackend', () => { it('will set up FetchTransport if customEndpoint is provided', () => { // arrange - jest.spyOn(faroWebSdkModule, 'initializeFaro').mockReturnValueOnce({ - ...faroWebSdkModule.faro, - api: { - ...faroWebSdkModule.faro.api, - setUser: jest.fn(), - }, - }); + const constructorSpy = jest.spyOn(faroWebSdkModule, 'FetchTransport'); //act - const backend = new GrafanaJavascriptAgentBackend(options); + new GrafanaJavascriptAgentBackend(options); //assert - expect(backend.transports.length).toEqual(1); - expect(backend.transports[0]).toBeInstanceOf(FetchTransport); + expect(constructorSpy).toHaveBeenCalledTimes(1); + expect(faroWebSdkModule.faro.transports.transports.length).toEqual(2); + expect(faroWebSdkModule.faro.transports.transports[0]).toBeInstanceOf(EchoSrvTransport); + expect(faroWebSdkModule.faro.transports.transports[1]).toBeInstanceOf(FetchTransport); }); it('will initialize GrafanaJavascriptAgent and set user', () => { @@ -76,71 +71,20 @@ describe('GrafanaJavascriptAgentEchoBackend', () => { warn: jest.fn(), error: jest.fn(), }; - const mockedAgent = () => { - return { - api: { - setUser: mockedSetUser, - pushLog: jest.fn(), - callOriginalConsoleMethod: jest.fn(), - pushError: jest.fn(), - pushMeasurement: jest.fn(), - pushTraces: jest.fn(), - pushEvent: jest.fn(), - initOTEL: jest.fn(), - getOTEL: jest.fn(), - getTraceContext: jest.fn(), - changeStacktraceParser: jest.fn(), - getStacktraceParser: jest.fn(), - isOTELInitialized: jest.fn(), - setSession: jest.fn(), - getSession: jest.fn(), - resetUser: jest.fn(), - resetSession: jest.fn(), - setView: jest.fn(), - getView: jest.fn(), - }, - config: { - globalObjectKey: '', - preventGlobalExposure: false, - transports: [], - instrumentations: mockedInstrumentationsForConfig, - metas: [], - parseStacktrace: jest.fn(), - app: jest.fn(), - paused: false, - dedupe: true, - isolate: false, - internalLoggerLevel: InternalLoggerLevel.ERROR, - unpatchedConsole: { ...console }, - }, - metas: { - add: jest.fn(), - remove: jest.fn(), - value: {}, - addListener: jest.fn(), - removeListener: jest.fn(), - }, - transports: { - add: jest.fn(), - execute: jest.fn(), - transports: [], - pause: jest.fn(), - unpause: jest.fn(), - addBeforeSendHooks: jest.fn(), - addIgnoreErrorsPatterns: jest.fn(), - getBeforeSendHooks: jest.fn(), - isPaused: jest.fn(), - remove: jest.fn(), - removeBeforeSendHooks: jest.fn(), - }, - pause: jest.fn(), - unpause: jest.fn(), - instrumentations: mockedInstrumentations, - internalLogger: mockedInternalLogger, - unpatchedConsole: { ...console }, - }; - }; - jest.mocked(initializeFaro).mockImplementation(mockedAgent); + + jest.spyOn(faroWebSdkModule, 'initializeFaro').mockReturnValueOnce({ + ...faroWebSdkModule.faro, + api: { + ...faroWebSdkModule.faro.api, + setUser: mockedSetUser, + }, + config: { + ...faroWebSdkModule.faro.config, + instrumentations: mockedInstrumentationsForConfig, + }, + instrumentations: mockedInstrumentations, + internalLogger: mockedInternalLogger, + }); //act new GrafanaJavascriptAgentBackend(options); @@ -156,111 +100,6 @@ describe('GrafanaJavascriptAgentEchoBackend', () => { }); }); - it('will forward events to transports', () => { - //arrange - const mockedSetUser = jest.fn(); - const mockedInstrumentationsForConfig: Instrumentation[] = []; - const mockedInstrumentations = { - add: jest.fn(), - instrumentations: mockedInstrumentationsForConfig, - remove: jest.fn(), - }; - const mockedInternalLogger = { - prefix: 'Faro', - debug: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - }; - const mockedAgent = () => { - return { - api: { - setUser: mockedSetUser, - pushLog: jest.fn(), - callOriginalConsoleMethod: jest.fn(), - pushError: jest.fn(), - pushMeasurement: jest.fn(), - pushTraces: jest.fn(), - pushEvent: jest.fn(), - initOTEL: jest.fn(), - getOTEL: jest.fn(), - getTraceContext: jest.fn(), - changeStacktraceParser: jest.fn(), - getStacktraceParser: jest.fn(), - isOTELInitialized: jest.fn(), - setSession: jest.fn(), - getSession: jest.fn(), - resetUser: jest.fn(), - resetSession: jest.fn(), - setView: jest.fn(), - getView: jest.fn(), - }, - config: { - globalObjectKey: '', - preventGlobalExposure: false, - transports: [], - instrumentations: mockedInstrumentationsForConfig, - metas: [], - parseStacktrace: jest.fn(), - app: jest.fn(), - paused: false, - dedupe: true, - isolate: false, - internalLoggerLevel: InternalLoggerLevel.ERROR, - unpatchedConsole: { ...console }, - }, - metas: { - add: jest.fn(), - remove: jest.fn(), - value: {}, - addListener: jest.fn(), - removeListener: jest.fn(), - }, - transports: { - add: jest.fn(), - execute: jest.fn(), - transports: [], - pause: jest.fn(), - unpause: jest.fn(), - addBeforeSendHooks: jest.fn(), - addIgnoreErrorsPatterns: jest.fn(), - getBeforeSendHooks: jest.fn(), - isPaused: jest.fn(), - remove: jest.fn(), - removeBeforeSendHooks: jest.fn(), - }, - pause: jest.fn(), - unpause: jest.fn(), - instrumentations: mockedInstrumentations, - internalLogger: mockedInternalLogger, - unpatchedConsole: { ...console }, - }; - }; - - jest.mocked(initializeFaro).mockImplementation(mockedAgent); - const backend = new GrafanaJavascriptAgentBackend({ - ...options, - preventGlobalExposure: true, - }); - - backend.transports = [ - /* eslint-disable */ - { send: jest.fn() } as unknown as BaseTransport, - { send: jest.fn() } as unknown as BaseTransport, - ]; - const event: GrafanaJavascriptAgentEchoEvent = { - type: EchoEventType.GrafanaJavascriptAgent, - payload: { foo: 'bar' } as unknown as GrafanaJavascriptAgentEchoEvent, - meta: {} as unknown as EchoMeta, - }; - /* eslint-enable */ - backend.addEvent(event); - backend.transports.forEach((transport) => { - expect(transport.send).toHaveBeenCalledTimes(1); - expect(transport.send).toHaveBeenCalledWith(event.payload); - }); - }); - //@FIXME - make integration test work // it('integration test with EchoSrv and GrafanaJavascriptAgent', async () => { diff --git a/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.ts b/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.ts index 1ac479de07b..f454486f5be 100644 --- a/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.ts +++ b/public/app/core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend.ts @@ -29,16 +29,15 @@ export class GrafanaJavascriptAgentBackend { supportedEvents = [EchoEventType.GrafanaJavascriptAgent]; private faroInstance; - transports: BaseTransport[]; constructor(public options: GrafanaJavascriptAgentBackendOptions) { // configure instrumentations. const instrumentations: Instrumentation[] = []; - this.transports = []; + const transports: BaseTransport[] = [new EchoSrvTransport()]; if (options.customEndpoint) { - this.transports.push(new FetchTransport({ url: options.customEndpoint, apiKey: options.apiKey })); + transports.push(new FetchTransport({ url: options.customEndpoint, apiKey: options.apiKey })); } if (options.errorInstrumentalizationEnabled) { @@ -63,7 +62,7 @@ export class GrafanaJavascriptAgentBackend environment: options.buildInfo.env, }, instrumentations, - transports: [new EchoSrvTransport()], + transports, ignoreErrors: [ 'ResizeObserver loop limit exceeded', 'ResizeObserver loop completed', @@ -71,9 +70,6 @@ export class GrafanaJavascriptAgentBackend ], sessionTracking: { persistent: true, - generateSessionId() { - return (Math.random() + 1).toString(36).substring(2); - }, }, batching: { sendTimeout: 1000, @@ -91,9 +87,8 @@ export class GrafanaJavascriptAgentBackend } } - addEvent = (e: EchoEvent) => { - this.transports.forEach((t) => t.send(e.payload)); - }; + // noop because the EchoSrvTransport registered in Faro will already broadcast all signals emitted by the Faro API + addEvent = (e: EchoEvent) => {}; // backend will log events to stdout, and at least in case of hosted grafana they will be // ingested into Loki. Due to Loki limitations logs cannot be backdated,