From f8490fc9af7118a00a7eceded5e8161bda9a6922 Mon Sep 17 00:00:00 2001 From: Luiz Otavio <45200344+luizotavio32@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:23:05 -0300 Subject: [PATCH] fix(event-log): add event logging for embedded Superset (#41938) Co-authored-by: Claude Opus 4.8 --- .../src/middleware/logger.test.ts | 32 +++++++++++++++++++ .../src/middleware/loggerMiddleware.ts | 23 ++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/superset-frontend/src/middleware/logger.test.ts b/superset-frontend/src/middleware/logger.test.ts index 5b989e21713..9b23587530b 100644 --- a/superset-frontend/src/middleware/logger.test.ts +++ b/superset-frontend/src/middleware/logger.test.ts @@ -142,6 +142,38 @@ describe('logger middleware', () => { } }); + const getSourceForPath = (href: string): string | undefined => { + const originalHref = window.location.href; + Object.defineProperty(window, 'location', { + value: { href }, + writable: true, + }); + try { + (logger as Function)(mockStore)(next)(action); + jest.advanceTimersByTime(2000); + const { events } = postStub.mock.calls[0][0].postPayload; + return events[0].source; + } finally { + Object.defineProperty(window, 'location', { + value: { href: originalHref }, + writable: true, + }); + } + }; + + test.each([ + ['/dashboard/123/embedded/', 'embedded_dashboard'], + ['/embedded/abc-def-uuid/', 'embedded_dashboard'], + // React Router also matches these routes without a trailing slash + ['/dashboard/123/embedded', 'embedded_dashboard'], + ['/embedded/abc-def-uuid', 'embedded_dashboard'], + ['/dashboard/123/', 'dashboard'], + // slug is literally "embedded" - must not be treated as embedded + ['/dashboard/embedded/', 'dashboard'], + ])('classifies %s as source "%s"', (path, expectedSource) => { + expect(getSourceForPath(`http://localhost${path}`)).toBe(expectedSource); + }); + test('should debounce a few log requests to one', () => { (logger as Function)(mockStore)(next)(action); (logger as Function)(mockStore)(next)(action); diff --git a/superset-frontend/src/middleware/loggerMiddleware.ts b/superset-frontend/src/middleware/loggerMiddleware.ts index 55c476f9bc2..af67db08d2b 100644 --- a/superset-frontend/src/middleware/loggerMiddleware.ts +++ b/superset-frontend/src/middleware/loggerMiddleware.ts @@ -33,7 +33,12 @@ import { ensureAppRoot } from '../utils/pathUtils'; import type { DashboardInfo, DashboardLayoutState } from '../dashboard/types'; import type { QueryEditor } from '../SqlLab/types'; -type LogEventSource = 'dashboard' | 'explore' | 'sqlLab' | 'slice'; +type LogEventSource = + | 'dashboard' + | 'embedded_dashboard' + | 'explore' + | 'sqlLab' + | 'slice'; interface LogEventData { source?: LogEventSource; @@ -99,7 +104,7 @@ const sendBeacon = (events: LogEventData[]): void => { const [firstEvent] = events; const { source, source_id } = firstEvent; // backend logs treat these request params as first-class citizens - if (source === 'dashboard') { + if (source === 'dashboard' || source === 'embedded_dashboard') { endpoint += `&dashboard_id=${source_id}`; } else if (source === 'slice') { endpoint += `&slice_id=${source_id}`; @@ -131,6 +136,12 @@ const logMessageQueue = new DebouncedMessageQueue({ delayThreshold: 1000, }); +// Embedded dashboards are served from `/dashboard/:idOrSlug/embedded/` and +// `/embedded/:uuid/`. Matching these specific shapes avoids false positives +// for regular dashboards (e.g. a dashboard whose slug is "embedded"). +const EMBEDDED_ROUTE_REGEX = + /\/dashboard\/[^/]+\/embedded\/?|\/embedded\/[^/]+\/?/; + let lastEventId: string | number = 0; const loggerMiddleware: Middleware< @@ -162,9 +173,13 @@ const loggerMiddleware: Middleware< } const path = navPath || window?.location?.href; - if (dashboardInfo?.id && path?.includes('/dashboard/')) { + // Match the actual embedded route patterns (`/dashboard/:idOrSlug/embedded/` + // and `/embedded/:uuid/`) rather than any URL containing "/embedded/", which + // would misclassify a regular dashboard whose slug is "embedded". + const isEmbedded = EMBEDDED_ROUTE_REGEX.test(path ?? ''); + if (dashboardInfo?.id && (path?.includes('/dashboard/') || isEmbedded)) { logMetadata = { - source: 'dashboard', + source: isEmbedded ? 'embedded_dashboard' : 'dashboard', source_id: dashboardInfo.id, dashboard_id: dashboardInfo.id, ...logMetadata,