From 86b795cd3631353de0b208b1746e125d314332ab Mon Sep 17 00:00:00 2001
From: Mohamed Halat <halatmohamed@hotmail.com>
Date: Thu, 3 Apr 2025 06:37:52 -0400
Subject: [PATCH] feat(embedding-sdk): emit data-mask events through embedded
 sdk to iframe parent (#31331)

---
 superset-embedded-sdk/src/index.ts            | 23 ++++++++++++++-----
 .../src/components/UiConfigContext/index.tsx  |  4 ++++
 superset-frontend/src/embedded/index.tsx      | 19 ++++++++++++++-
 3 files changed, 39 insertions(+), 7 deletions(-)

diff --git a/superset-embedded-sdk/src/index.ts b/superset-embedded-sdk/src/index.ts
index 7bcf5dcd2e..3944c8cc4f 100644
--- a/superset-embedded-sdk/src/index.ts
+++ b/superset-embedded-sdk/src/index.ts
@@ -37,6 +37,7 @@ export type UiConfigType = {
   hideTitle?: boolean
   hideTab?: boolean
   hideChartControls?: boolean
+  emitDataMasks?: boolean
   filters?: {
     [key: string]: boolean | undefined
     visible?: boolean
@@ -73,11 +74,12 @@ export type Size = {
 }
 
 export type EmbeddedDashboard = {
-  getScrollSize: () => Promise<Size>
-  unmount: () => void
-  getDashboardPermalink: (anchor: string) => Promise<string>
-  getActiveTabs: () => Promise<string[]>
-}
+  getScrollSize: () => Promise<Size>;
+  unmount: () => void;
+  getDashboardPermalink: (anchor: string) => Promise<string>;
+  getActiveTabs: () => Promise<string[]>;
+  getDataMasks: (callbackFn: (dataMasks: any[]) => void) => void;
+};
 
 /**
  * Embeds a Superset dashboard into the page using an iframe.
@@ -117,6 +119,9 @@ export async function embedDashboard({
       if(dashboardUiConfig.hideChartControls) {
         configNumber += 8
       }
+      if (dashboardUiConfig.emitDataMasks) {
+        configNumber += 16
+      }
     }
     return configNumber
   }
@@ -204,12 +209,18 @@ export async function embedDashboard({
   const getScrollSize = () => ourPort.get<Size>('getScrollSize');
   const getDashboardPermalink = (anchor: string) =>
     ourPort.get<string>('getDashboardPermalink', { anchor });
-  const getActiveTabs = () => ourPort.get<string[]>('getActiveTabs')
+  const getActiveTabs = () => ourPort.get<string[]>('getActiveTabs');
+  const getDataMasks = (callbackFn: (dataMasks: any[]) => void) => {
+    ourPort.start();
+    ourPort.defineMethod("getDataMasks", callbackFn);
+  };
+
 
   return {
     getScrollSize,
     unmount,
     getDashboardPermalink,
     getActiveTabs,
+    getDataMasks,
   };
 }
diff --git a/superset-frontend/src/components/UiConfigContext/index.tsx b/superset-frontend/src/components/UiConfigContext/index.tsx
index 99c0a506a5..7843967669 100644
--- a/superset-frontend/src/components/UiConfigContext/index.tsx
+++ b/superset-frontend/src/components/UiConfigContext/index.tsx
@@ -26,6 +26,8 @@ interface UiConfigType {
   hideTab: boolean;
   hideNav: boolean;
   hideChartControls: boolean;
+  // Only used in superset-embedded-sdk to emit data masks to the parent window
+  emitDataMasks: boolean;
 }
 interface EmbeddedUiConfigProviderProps {
   children: JSX.Element;
@@ -36,6 +38,7 @@ export const UiConfigContext = createContext<UiConfigType>({
   hideTab: false,
   hideNav: false,
   hideChartControls: false,
+  emitDataMasks: false,
 });
 
 export const useUiConfig = () => useContext(UiConfigContext);
@@ -49,6 +52,7 @@ export const EmbeddedUiConfigProvider: FC<EmbeddedUiConfigProviderProps> = ({
     hideTab: (config & 2) !== 0,
     hideNav: (config & 4) !== 0,
     hideChartControls: (config & 8) !== 0,
+    emitDataMasks: (config & 16) !== 0,
   });
 
   return (
diff --git a/superset-frontend/src/embedded/index.tsx b/superset-frontend/src/embedded/index.tsx
index 5145052c8b..5331c20421 100644
--- a/superset-frontend/src/embedded/index.tsx
+++ b/superset-frontend/src/embedded/index.tsx
@@ -24,6 +24,7 @@ import Switchboard from '@superset-ui/switchboard';
 import getBootstrapData from 'src/utils/getBootstrapData';
 import setupClient from 'src/setup/setupClient';
 import setupPlugins from 'src/setup/setupPlugins';
+import { useUiConfig } from 'src/components/UiConfigContext';
 import { RootContextProviders } from 'src/views/RootContextProviders';
 import { store, USER_LOADED } from 'src/views/store';
 import ErrorBoundary from 'src/components/ErrorBoundary';
@@ -51,11 +52,27 @@ const LazyDashboardPage = lazy(
     ),
 );
 
+const EmbededLazyDashboardPage = () => {
+  const uiConfig = useUiConfig();
+
+  // Emit data mask changes to the parent window
+  if (uiConfig?.emitDataMasks) {
+    log('setting up Switchboard event emitter');
+
+    store.subscribe(() => {
+      const state = store.getState();
+      Switchboard.emit('getDataMasks', state.dataMask);
+    });
+  }
+
+  return <LazyDashboardPage idOrSlug={bootstrapData.embedded!.dashboard_id} />;
+};
+
 const EmbeddedRoute = () => (
   <Suspense fallback={<Loading />}>
     <RootContextProviders>
       <ErrorBoundary>
-        <LazyDashboardPage idOrSlug={bootstrapData.embedded!.dashboard_id} />
+        <EmbededLazyDashboardPage />
       </ErrorBoundary>
       <ToastContainer position="top" />
     </RootContextProviders>
-- 
GitLab