Repository: thoughtspot/visual-embed-sdk
Pull request #212: SCAL-239365
Author: login: yinstardev
Created: 2025-05-19T09:13:31Z | Merged: 2025-05-29T23:43:14Z



Diff:
diff --git a/src/embed/bodyless-conversation.spec.ts b/src/embed/bodyless-conversation.spec.ts
index a05779751..e48de7098 100644
--- a/src/embed/bodyless-conversation.spec.ts
+++ b/src/embed/bodyless-conversation.spec.ts
@@ -31,7 +31,7 @@ describe('SpotterAgentEmbed', () => {
         fetchMock.resetMocks();
     });
 
-    test('should render the bodyless conversation embed', async () => {
+    test('should render the SpotterAgent embed', async () => {
         fetchMock.mockResponses(
             JSON.stringify({
                 data: {
@@ -137,4 +137,311 @@ describe('SpotterAgentEmbed', () => {
         const errorResult = await spotterEmbed.sendMessage('userMessage');
         expect(errorResult.error instanceof Error).toBeTruthy();
     });
+
+    test('should apply containerClassName to the container element', async () => {
+        fetchMock.mockResponses(
+            JSON.stringify({
+                data: {
+                    ConvAssist__createConversation: {
+                        convId: 'conversationId',
+                        initialCtx: {
+                            type: 'TS_ANSWER',
+                            tsAnsCtx: {
+                                sessionId: 'sessionId',
+                                genNo: 1,
+                                stateKey: {
+                                    transactionId: 'transactionId',
+                                    generationNumber: 1,
+                                },
+                                worksheet: {
+                                    worksheetId: 'worksheetId',
+                                    worksheetName: 'GTM',
+                                },
+                            },
+                        },
+                    },
+                },
+            }),
+            JSON.stringify({
+                data: {
+                    ConvAssist__sendMessage: {
+                        responses: [
+                            {
+                                msgId: 'msgId',
+                                data: {
+                                    asstRespData: {
+                                        tool: 'TS_NLS',
+                                        asstRespText: '',
+                                        nlsAnsData: {
+                                            sageQuerySuggestions: [
+                                                {
+                                                    llmReasoning: {
+                                                        assumptions: 'You want the total [COL|sales] for [COL|item_type] [VAL|jackets] for [VAL|this year].',
+                                                        clarifications: '',
+                                                        interpretation: '',
+                                                        __typename: 'eureka_SageQuerySuggestion_LLMReasoning',
+                                                    },
+                                                    tokens: [
+                                                        'sum sales',
+                                                        "item type = 'jackets'",
+                                                        "date = 'this year'",
+                                                    ],
+                                                    tmlTokens: [
+                                                        'sum [sales]',
+                                                        "[date] = [date].'this year'",
+                                                        "[item type] = [item type].'jackets'",
+                                                    ],
+                                                    worksheetId: 'worksheetId',
+                                                    description: '',
+                                                    title: '',
+                                                    cached: false,
+                                                    sqlQuery: "SELECT SUM(sales) FROM __Sample_Retail_Apparel WHERE item_type = 'jackets' AND date = _this_year();",
+                                                    sessionId: 'sessionId',
+                                                    genNo: 2,
+                                                    formulaInfo: [],
+                                                    tmlPhrases: [],
+                                                    stateKey: {
+                                                        transactionId: 'transactionId',
+                                                        generationNumber: 1,
+                                                        __typename: 'sage_auto_complete_v2_ACStateKey',
+                                                    },
+                                                    __typename: 'eureka_SageQuerySuggestion',
+                                                },
+                                            ],
+                                            responseType: 'ANSWER',
+                                            __typename: 'convassist_nls_tool_NLSToolAsstRespData',
+                                        },
+                                        __typename: 'convassist_AsstResponseData',
+                                    },
+                                    __typename: 'convassist_MessageData',
+                                },
+                                type: 'ASST_RESPONSE',
+                                __typename: 'convassist_MessagePayload',
+                            },
+                        ],
+                        __typename: 'convassist_SendMessageResponse',
+                    },
+                },
+            }),
+        );
+
+        const viewConfig: SpotterAgentEmbedViewConfig = {
+            worksheetId: 'worksheetId',
+            containerClassName: 'custom-conversation-container',
+        };
+
+        const spotterAgentEmbed = new SpotterAgentEmbed(viewConfig);
+        const result = await spotterAgentEmbed.sendMessage('userMessage');
+        
+        // Verify that the container has the custom class name
+        expect(result.container.className).toBe('custom-conversation-container');
+        
+        // Also verify the iframe src is correct
+        const iframeSrc = getIFrameSrc(result.container);
+        expectUrlToHaveParamsWithValues(iframeSrc, {
+            sessionId: 'sessionId',
+            genNo: 2,
+            acSessionId: 'transactionId',
+            acGenNo: 1,
+        });
+    });
+
+    test('should not set className when containerClassName is not provided', async () => {
+        fetchMock.mockResponses(
+            JSON.stringify({
+                data: {
+                    ConvAssist__createConversation: {
+                        convId: 'conversationId',
+                        initialCtx: {
+                            type: 'TS_ANSWER',
+                            tsAnsCtx: {
+                                sessionId: 'sessionId',
+                                genNo: 1,
+                                stateKey: {
+                                    transactionId: 'transactionId',
+                                    generationNumber: 1,
+                                },
+                                worksheet: {
+                                    worksheetId: 'worksheetId',
+                                    worksheetName: 'GTM',
+                                },
+                            },
+                        },
+                    },
+                },
+            }),
+            JSON.stringify({
+                data: {
+                    ConvAssist__sendMessage: {
+                        responses: [
+                            {
+                                msgId: 'msgId',
+                                data: {
+                                    asstRespData: {
+                                        tool: 'TS_NLS',
+                                        asstRespText: '',
+                                        nlsAnsData: {
+                                            sageQuerySuggestions: [
+                                                {
+                                                    llmReasoning: {
+                                                        assumptions: 'You want the total [COL|sales] for [COL|item_type] [VAL|jackets] for [VAL|this year].',
+                                                        clarifications: '',
+                                                        interpretation: '',
+                                                        __typename: 'eureka_SageQuerySuggestion_LLMReasoning',
+                                                    },
+                                                    tokens: [
+                                                        'sum sales',
+                                                        "item type = 'jackets'",
+                                                        "date = 'this year'",
+                                                    ],
+                                                    tmlTokens: [
+                                                        'sum [sales]',
+                                                        "[date] = [date].'this year'",
+                                                        "[item type] = [item type].'jackets'",
+                                                    ],
+                                                    worksheetId: 'worksheetId',
+                                                    description: '',
+                                                    title: '',
+                                                    cached: false,
+                                                    sqlQuery: "SELECT SUM(sales) FROM __Sample_Retail_Apparel WHERE item_type = 'jackets' AND date = _this_year();",
+                                                    sessionId: 'sessionId',
+                                                    genNo: 2,
+                                                    formulaInfo: [],
+                                                    tmlPhrases: [],
+                                                    stateKey: {
+                                                        transactionId: 'transactionId',
+                                                        generationNumber: 1,
+                                                        __typename: 'sage_auto_complete_v2_ACStateKey',
+                                                    },
+                                                    __typename: 'eureka_SageQuerySuggestion',
+                                                },
+                                            ],
+                                            responseType: 'ANSWER',
+                                            __typename: 'convassist_nls_tool_NLSToolAsstRespData',
+                                        },
+                                        __typename: 'convassist_AsstResponseData',
+                                    },
+                                    __typename: 'convassist_MessageData',
+                                },
+                                type: 'ASST_RESPONSE',
+                                __typename: 'convassist_MessagePayload',
+                            },
+                        ],
+                        __typename: 'convassist_SendMessageResponse',
+                    },
+                },
+            }),
+        );
+
+        const viewConfig: SpotterAgentEmbedViewConfig = {
+            worksheetId: 'worksheetId',
+            // No containerClassName provided
+        };
+
+        const spotterAgentEmbed = new SpotterAgentEmbed(viewConfig);
+        const result = await spotterAgentEmbed.sendMessage('userMessage');
+        
+        // Verify that the container has no class name (empty string)
+        expect(result.container.className).toBe('');
+    });
+
+    test('should handle hideActions parameter correctly', async () => {
+        fetchMock.mockResponses(
+            JSON.stringify({
+                data: {
+                    ConvAssist__createConversation: {
+                        convId: 'conversationId',
+                        initialCtx: {
+                            type: 'TS_ANSWER',
+                            tsAnsCtx: {
+                                sessionId: 'sessionId',
+                                genNo: 1,
+                                stateKey: {
+                                    transactionId: 'transactionId',
+                                    generationNumber: 1,
+                                },
+                                worksheet: {
+                                    worksheetId: 'worksheetId',
+                                    worksheetName: 'GTM',
+                                },
+                            },
+                        },
+                    },
+                },
+            }),
+            JSON.stringify({
+                data: {
+                    ConvAssist__sendMessage: {
+                        responses: [
+                            {
+                                msgId: 'msgId',
+                                data: {
+                                    asstRespData: {
+                                        tool: 'TS_NLS',
+                                        asstRespText: '',
+                                        nlsAnsData: {
+                                            sageQuerySuggestions: [
+                                                {
+                                                    llmReasoning: {
+                                                        assumptions: 'You want the total [COL|sales] for [COL|item_type] [VAL|jackets] for [VAL|this year].',
+                                                        clarifications: '',
+                                                        interpretation: '',
+                                                        __typename: 'eureka_SageQuerySuggestion_LLMReasoning',
+                                                    },
+                                                    tokens: [
+                                                        'sum sales',
+                                                        "item type = 'jackets'",
+                                                        "date = 'this year'",
+                                                    ],
+                                                    tmlTokens: [
+                                                        'sum [sales]',
+                                                        "[date] = [date].'this year'",
+                                                        "[item type] = [item type].'jackets'",
+                                                    ],
+                                                    worksheetId: 'worksheetId',
+                                                    description: '',
+                                                    title: '',
+                                                    cached: false,
+                                                    sqlQuery: "SELECT SUM(sales) FROM __Sample_Retail_Apparel WHERE item_type = 'jackets' AND date = _this_year();",
+                                                    sessionId: 'sessionId',
+                                                    genNo: 2,
+                                                    formulaInfo: [],
+                                                    tmlPhrases: [],
+                                                    stateKey: {
+                                                        transactionId: 'transactionId',
+                                                        generationNumber: 1,
+                                                        __typename: 'sage_auto_complete_v2_ACStateKey',
+                                                    },
+                                                    __typename: 'eureka_SageQuerySuggestion',
+                                                },
+                                            ],
+                                            responseType: 'ANSWER',
+                                            __typename: 'convassist_nls_tool_NLSToolAsstRespData',
+                                        },
+                                        __typename: 'convassist_AsstResponseData',
+                                    },
+                                    __typename: 'convassist_MessageData',
+                                },
+                                type: 'ASST_RESPONSE',
+                                __typename: 'convassist_MessagePayload',
+                            },
+                        ],
+                        __typename: 'convassist_SendMessageResponse',
+                    },
+                },
+            }),
+        );
+
+        const viewConfig: SpotterAgentEmbedViewConfig = {
+            worksheetId: 'worksheetId',
+            hiddenActions: [Action.Download, Action.Save], // This should trigger the HideActions branch
+        };
+
+        const spotterAgentEmbed = new SpotterAgentEmbed(viewConfig);
+        const result = await spotterAgentEmbed.sendMessage('userMessage');
+        
+        // Verify the iframe src contains the hideActions parameter
+        const iframeSrc = getIFrameSrc(result.container);
+        expect(iframeSrc).toContain('hideAction');
+    });
 });
diff --git a/src/embed/bodyless-conversation.ts b/src/embed/bodyless-conversation.ts
index 20269cab6..12a4e4a29 100644
--- a/src/embed/bodyless-conversation.ts
+++ b/src/embed/bodyless-conversation.ts
@@ -13,6 +13,11 @@ export interface SpotterAgentEmbedViewConfig extends ViewConfig {
      * The ID of the worksheet to use for the conversation.
      */
     worksheetId: string;
+    
+    /**
+     * Optional CSS class name to add to the container div.
+     */
+    containerClassName?: string;
 }
 
 /**
@@ -111,6 +116,10 @@ export class SpotterAgentEmbed {
         }
 
         const container = document.createElement('div');
+        if (this.viewConfig.containerClassName) {
+            container.className = this.viewConfig.containerClassName;
+        }
+
         const embed = new ConversationMessage(container, {
             ...this.viewConfig,
             sessionId: data.sessionId,
diff --git a/src/react/all-types-export.ts b/src/react/all-types-export.ts
index c817e0bad..7d67b9f27 100644
--- a/src/react/all-types-export.ts
+++ b/src/react/all-types-export.ts
@@ -12,6 +12,7 @@ export {
     SpotterEmbed,
     ConversationEmbed,
     PreRenderedConversationEmbed,
+    SpotterAgentEmbed,
     useEmbedRef,
     useInit,
 } from './index';
diff --git a/src/react/index.spec.tsx b/src/react/index.spec.tsx
index 236deb022..56ca703b4 100644
--- a/src/react/index.spec.tsx
+++ b/src/react/index.spec.tsx
@@ -16,6 +16,7 @@ import {
 } from '../test/test-utils';
 import {
     SearchEmbed, AppEmbed, LiveboardEmbed, useEmbedRef, SearchBarEmbed, PreRenderedLiveboardEmbed,
+    SpotterAgentEmbed
 } from './index';
 import * as allExports from './index';
 import {
@@ -235,6 +236,161 @@ describe('React Components', () => {
         });
     });
 
+    describe('SpotterAgentEmbed', () => {
+        it('Should work as a React component integrating BodylessConversation', async () => {
+            const mockDiv = document.createElement('div');
+            
+            let conversationService: any = null;
+            
+            render(
+                <SpotterAgentEmbed
+                    ref={(instance: any) => {
+                        conversationService = instance;
+                        
+                        if (instance) {
+                            instance.sendMessage = jest.fn().mockResolvedValue({
+                                container: mockDiv
+                            });
+                        }
+                    }}
+                    worksheetId="test-worksheet-id"
+                />
+            );
+
+            expect(conversationService).not.toBeNull();
+            
+            if (conversationService) {
+                expect(typeof conversationService.sendMessage).toBe('function');
+                
+                const response = await conversationService.sendMessage("What are my sales this month?");
+                expect(response.container).toBe(mockDiv);
+            }
+        });
+
+        it('Should work as a React component with ref support', () => {
+            const mockSendMessage = jest.fn().mockResolvedValue({
+                container: document.createElement('div'),
+                viz: {}
+            });
+            
+            const TestComponent = () => {
+                const conversationRef = React.useRef(null);
+                
+                const handleClick = () => {
+                    if (conversationRef.current) {
+                        conversationRef.current.sendMessage = mockSendMessage;
+                        
+                        conversationRef.current.sendMessage("Test message");
+                    }
+                };
+                
+                return (
+                    <>
+                        <SpotterAgentEmbed
+                            ref={conversationRef}
+                            worksheetId="test-worksheet-id"
+                        />
+                        <button data-testid="test-button" onClick={handleClick}>
+                            Send Message
+                        </button>
+                    </>
+                );
+            };
+            
+            const { getByTestId } = render(<TestComponent />);
+            
+            fireEvent.click(getByTestId('test-button'));
+            
+            expect(mockSendMessage).toHaveBeenCalledWith("Test message");
+        });
+
+        it('Should work with the useEmbedRef hook', () => {
+            const mockSendMessage = jest.fn().mockResolvedValue({
+                container: document.createElement('div'),
+                viz: {}
+            });
+            
+            const TestComponent = () => {
+                const embedRef = useEmbedRef<typeof SpotterAgentEmbed>();
+                
+                const handleClick = () => {
+                    if (embedRef.current) {
+                        const service = embedRef.current as unknown as { 
+                            sendMessage: typeof mockSendMessage 
+                        };
+                        
+                        service.sendMessage = mockSendMessage;
+                        
+                        service.sendMessage("Test with useEmbedRef");
+                    }
+                };
+                
+                return (
+                    <>
+                        <SpotterAgentEmbed
+                            ref={embedRef}
+                            worksheetId="test-worksheet-id"
+                        />
+                        <button data-testid="use-embed-ref-button" onClick={handleClick}>
+                            Send with useEmbedRef
+                        </button>
+                    </>
+                );
+            };
+            
+            const { getByTestId } = render(<TestComponent />);
+            
+            fireEvent.click(getByTestId('use-embed-ref-button'));
+            
+            expect(mockSendMessage).toHaveBeenCalledWith("Test with useEmbedRef");
+        });
+
+        it('Should work with the className prop', async () => {
+            let capturedInstance: any = null;
+            
+            const TestComponent = () => {
+                const embedRef = useEmbedRef<typeof SpotterAgentEmbed>();
+                
+                React.useEffect(() => {
+                    capturedInstance = embedRef.current;
+                    
+                    if (capturedInstance) {
+                        const mockConversationService = {
+                            sendMessage: jest.fn().mockResolvedValue({
+                                data: {
+                                    sessionId: 'test-session',
+                                    genNo: 1,
+                                    stateKey: {
+                                        transactionId: 'test-transaction',
+                                        generationNumber: 1
+                                    }
+                                }
+                            })
+                        };
+                        (capturedInstance as any).conversationService = mockConversationService;
+                    }
+                }, []);
+                
+                return (
+                    <SpotterAgentEmbed
+                        ref={embedRef}
+                        worksheetId="test-worksheet-id"
+                        className="embedClass"
+                    />
+                );
+            };
+            
+            render(<TestComponent />);
+            
+            expect(capturedInstance).not.toBeNull();
+            
+            if (capturedInstance) {
+                const result = await capturedInstance.sendMessage("test");
+                expect(result.container.className).toBe("embedClass");
+            }
+        });
+    });
+
     describe('PreRenderedLiveboardEmbed', () => {
         it('should preRender the liveboard ', async () => {
             const preRenderId = 'tsEmbed-pre-render-wrapper-test';
@@ -285,7 +441,7 @@ describe('allExports', () => {
     it('should not have undefined exports', () => {
         Object.keys(allExports).forEach(
             (exportKey) => expect(
-                Boolean(allExports[exportKey]),
+                Boolean(allExports[exportKey as keyof typeof allExports]),
             )
                 .toBe(true),
         );
diff --git a/src/react/index.tsx b/src/react/index.tsx
index edc1d3725..aab094158 100644
--- a/src/react/index.tsx
+++ b/src/react/index.tsx
@@ -10,6 +10,7 @@ import { SearchEmbed as _SearchEmbed, SearchViewConfig } from '../embed/search';
 import { AppEmbed as _AppEmbed, AppViewConfig } from '../embed/app';
 import { LiveboardEmbed as _LiveboardEmbed, LiveboardViewConfig } from '../embed/liveboard';
 import { TsEmbed } from '../embed/ts-embed';
+import { SpotterAgentEmbed as _SpotterAgentEmbed, SpotterAgentEmbedViewConfig } from '../embed/bodyless-conversation';
 
 import { EmbedConfig, EmbedEvent, ViewConfig } from '../types';
 import { EmbedProps, getViewPropsAndListeners } from './util';
@@ -23,7 +24,7 @@ const componentFactory = <T extends typeof TsEmbed, U extends EmbedProps, V exte
     // Embed.preRender() method instead of the usual render method, and it will
     // not be destroyed when the component is unmounted.
     isPreRenderedComponent = false,
-) => React.forwardRef<InstanceType<T>, U>(
+ ) => React.forwardRef<InstanceType<T>, U>(
     (props: U, forwardedRef: React.MutableRefObject<InstanceType<T>>) => {
         const ref = React.useRef<HTMLDivElement>(null);
         const { className, style, ...embedProps } = props;
@@ -382,6 +383,64 @@ export const ConversationEmbed = componentFactory<
     ConversationViewConfig
 >(_ConversationEmbed);
 
+interface SpotterAgentEmbedProps extends EmbedProps, SpotterAgentEmbedViewConfig {}
+
+/**
+ * React component for SpotterAgent embed, which can be integrated inside
+ * chatbots or other conversational interfaces.
+ * @example
+ * ```tsx
+ * function SpotterAgent() {
+ *  const ref = useRef();
+ *  
+ *  const handleSendMessage = async () => {
+ *    const { container, error } = await ref.current.sendMessage('show me sales by region');
+ *    if (container) {
+ *      document.body.appendChild(container);
+ *    }
+ *  };
+ *  
+ *  return (
+ *    <div>
+ *      <SpotterAgentEmbed ref={ref} worksheetId="worksheetId" />
+ *      <button onClick={handleSendMessage}>Send Message</button>
+ *    </div>
+ *  );
+ * }
+ * ```
+ */
+export const SpotterAgentEmbed = React.forwardRef<_SpotterAgentEmbed, SpotterAgentEmbedProps>((props, ref) => {
+  const { className, ...restProps } = props;
+  const serviceRef = useRef<_SpotterAgentEmbed | null>(null);
+  
+  useDeepCompareEffect(() => {
+    if (serviceRef.current) {
+      serviceRef.current = null;
+    }
+    
+    const configProps = {
+      ...restProps,
+      ...(className ? { containerClassName: className } : {})
+    };
+    
+    serviceRef.current = new _SpotterAgentEmbed(configProps);
+    
+    if (ref) {
+      if (typeof ref === 'function') {
+        ref(serviceRef.current);
+      } else {
+        ref.current = serviceRef.current;
+      }
+    }
+    
+    return () => {
+      serviceRef.current = null;
+    };
+  }, [props]);
+  
+  return null;
+});
+
 /**
  * React component for PreRendered Conversation embed.
  *
@@ -411,6 +470,7 @@ type EmbedComponent = typeof SearchEmbed
     | typeof LiveboardEmbed
     | typeof SearchBarEmbed
     | typeof SageEmbed
+    | typeof SpotterAgentEmbed
     | typeof SpotterEmbed
     | typeof ConversationEmbed;
 


Reviews:
user: login: jbhanu-thoughtspot
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-29T02:45:48Z
user: login: yinstardev
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-29T03:07:33Z
user: login: jbhanu-thoughtspot
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-29T18:21:34Z

Review comments:
diff_hunk: @@ -23,6 +25,7 @@ const componentFactory = <T extends typeof TsEmbed, U extends EmbedProps, V exte
     // Embed.preRender() method instead of the usual render method, and it will
     // not be destroyed when the component is unmounted.
     isPreRenderedComponent = false,
+    isBodyless = false,
 ) => React.forwardRef<InstanceType<T>, U>(
     (props: U, forwardedRef: React.MutableRefObject<InstanceType<T>>) => {
         const ref = React.useRef<HTMLDivElement>(null);
path: src/react/index.tsx
user: login: jbhanu-thoughtspot
body: Let's just initiate the ref with that empty div instead of creating inside that useDeepEffect
created_at: 2025-05-29T02:44:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,6 +25,7 @@ const componentFactory = <T extends typeof TsEmbed, U extends EmbedProps, V exte
     // Embed.preRender() method instead of the usual render method, and it will
     // not be destroyed when the component is unmounted.
     isPreRenderedComponent = false,
+    isBodyless = false,
 ) => React.forwardRef<InstanceType<T>, U>(
     (props: U, forwardedRef: React.MutableRefObject<InstanceType<T>>) => {
         const ref = React.useRef<HTMLDivElement>(null);
path: src/react/index.tsx
user: login: yinstardev
body: Removed the extra logic not needed. 
created_at: 2025-05-29T03:07:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: mnk-blr
created_at: 2025-05-19T09:13:40Z
body: ![](https://res.cloudinary.com/snyk/image/upload/r-d/scm-platform/snyk-pull-requests/pr-banner-default.svg)

### :tada: **Snyk checks have passed. No issues have been found so far.**

:white_check_mark: **security/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/thoughtspot/pr-checks/20ccfc32-8f03-42fc-a255-428f977912ac?refs=08fda3598eee526902080cca9e2a2ac5fc47ea9f&source=prChecksComment)

:white_check_mark: **license/snyk** check is complete. No issues have been found. [(View Details)](https://app.snyk.io/org/thoughtspot/pr-checks/20ccfc32-8f03-42fc-a255-428f977912ac/license?refs=08fda3598eee526902080cca9e2a2ac5fc47ea9f&source=prChecksComment)



author_association: COLLABORATOR
user: login: pkg-pr-new[bot]
created_at: 2025-05-19T09:24:23Z
body: 
[Open in StackBlitz](https://pkg.pr.new/template/913329a6-c6da-4079-8eaa-bc0dd8e99d34)


```
npm i https://pkg.pr.new/thoughtspot/visual-embed-sdk/@thoughtspot/visual-embed-sdk@212
```


_commit: <a href="https://github.com/thoughtspot/visual-embed-sdk/runs/43089012354"><code>08fda35</code></a>_

author_association: NONE