This is an automated email from the ASF dual-hosted git repository.
michael-s-molina pushed a commit to branch 6.1
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/6.1 by this push:
new f8490fc9af7 fix(event-log): add event logging for embedded Superset
(#41938)
f8490fc9af7 is described below
commit f8490fc9af7118a00a7eceded5e8161bda9a6922
Author: Luiz Otavio <[email protected]>
AuthorDate: Mon Jul 13 15:23:05 2026 -0300
fix(event-log): add event logging for embedded Superset (#41938)
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
superset-frontend/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<LogEventData>({
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,