@thoughtspot/visual-embed-sdk 1.50.0 → 1.50.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/package.json +1 -1
- package/cjs/src/css-variables.d.ts +59 -0
- package/cjs/src/css-variables.d.ts.map +1 -1
- package/cjs/src/embed/app.d.ts +31 -2
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js +13 -2
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/app.spec.js +101 -0
- package/cjs/src/embed/app.spec.js.map +1 -1
- package/cjs/src/embed/conversation.d.ts +26 -0
- package/cjs/src/embed/conversation.d.ts.map +1 -1
- package/cjs/src/embed/conversation.js +12 -2
- package/cjs/src/embed/conversation.js.map +1 -1
- package/cjs/src/embed/conversation.spec.js +23 -0
- package/cjs/src/embed/conversation.spec.js.map +1 -1
- package/cjs/src/embed/events.spec.js +593 -0
- package/cjs/src/embed/events.spec.js.map +1 -1
- package/cjs/src/embed/host-events.spec.js +106 -0
- package/cjs/src/embed/host-events.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/contracts.d.ts +36 -14
- package/cjs/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/contracts.js +10 -1
- package/cjs/src/embed/hostEventClient/contracts.js.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.d.ts +1 -0
- package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.js +8 -0
- package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.spec.js +209 -0
- package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/utils.d.ts +2 -0
- package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/utils.js +32 -2
- package/cjs/src/embed/hostEventClient/utils.js.map +1 -1
- package/cjs/src/embed/hostEventClient/utils.spec.js +178 -0
- package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -1
- package/cjs/src/embed/liveboard.d.ts +6 -0
- package/cjs/src/embed/liveboard.d.ts.map +1 -1
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/spotter-viz-utils.d.ts +34 -0
- package/cjs/src/embed/spotter-viz-utils.d.ts.map +1 -1
- package/cjs/src/embed/spotter-viz-utils.js.map +1 -1
- package/cjs/src/embed/spotter-viz-utils.spec.js +17 -0
- package/cjs/src/embed/spotter-viz-utils.spec.js.map +1 -1
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +14 -7
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/embed/ts-embed.spec.js +146 -0
- package/cjs/src/embed/ts-embed.spec.js.map +1 -1
- package/cjs/src/errors.d.ts +1 -0
- package/cjs/src/errors.d.ts.map +1 -1
- package/cjs/src/errors.js +1 -0
- package/cjs/src/errors.js.map +1 -1
- package/cjs/src/index.d.ts +2 -2
- package/cjs/src/index.d.ts.map +1 -1
- package/cjs/src/index.js +3 -2
- package/cjs/src/index.js.map +1 -1
- package/cjs/src/react/util.spec.js +251 -0
- package/cjs/src/react/util.spec.js.map +1 -1
- package/cjs/src/types.d.ts +119 -2
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +97 -0
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils/processData.spec.js +93 -0
- package/cjs/src/utils/processData.spec.js.map +1 -1
- package/cjs/src/utils/processTrigger.spec.js +61 -0
- package/cjs/src/utils/processTrigger.spec.js.map +1 -1
- package/dist/{index-B5xoN6Cp.js → index-fCne7kmU.js} +1 -1
- package/dist/src/css-variables.d.ts +59 -0
- package/dist/src/css-variables.d.ts.map +1 -1
- package/dist/src/embed/app.d.ts +31 -2
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/conversation.d.ts +26 -0
- package/dist/src/embed/conversation.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/contracts.d.ts +36 -14
- package/dist/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/host-event-client.d.ts +1 -0
- package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/utils.d.ts +2 -0
- package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -1
- package/dist/src/embed/liveboard.d.ts +6 -0
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/embed/spotter-viz-utils.d.ts +34 -0
- package/dist/src/embed/spotter-viz-utils.d.ts.map +1 -1
- package/dist/src/embed/ts-embed.d.ts.map +1 -1
- package/dist/src/errors.d.ts +1 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/types.d.ts +119 -2
- package/dist/src/types.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +389 -84
- package/dist/tsembed-react.js +388 -83
- package/dist/tsembed.es.js +390 -85
- package/dist/tsembed.js +388 -83
- package/dist/visual-embed-sdk-react-full.d.ts +302 -17
- package/dist/visual-embed-sdk-react.d.ts +302 -17
- package/dist/visual-embed-sdk.d.ts +306 -17
- package/lib/package.json +1 -1
- package/lib/src/css-variables.d.ts +59 -0
- package/lib/src/css-variables.d.ts.map +1 -1
- package/lib/src/embed/app.d.ts +31 -2
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js +13 -2
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/app.spec.js +101 -0
- package/lib/src/embed/app.spec.js.map +1 -1
- package/lib/src/embed/conversation.d.ts +26 -0
- package/lib/src/embed/conversation.d.ts.map +1 -1
- package/lib/src/embed/conversation.js +11 -1
- package/lib/src/embed/conversation.js.map +1 -1
- package/lib/src/embed/conversation.spec.js +24 -1
- package/lib/src/embed/conversation.spec.js.map +1 -1
- package/lib/src/embed/events.spec.js +593 -0
- package/lib/src/embed/events.spec.js.map +1 -1
- package/lib/src/embed/host-events.spec.js +107 -1
- package/lib/src/embed/host-events.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/contracts.d.ts +36 -14
- package/lib/src/embed/hostEventClient/contracts.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/contracts.js +9 -0
- package/lib/src/embed/hostEventClient/contracts.js.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.d.ts +1 -0
- package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.js +9 -1
- package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.spec.js +209 -0
- package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/utils.d.ts +2 -0
- package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/utils.js +28 -1
- package/lib/src/embed/hostEventClient/utils.js.map +1 -1
- package/lib/src/embed/hostEventClient/utils.spec.js +179 -1
- package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -1
- package/lib/src/embed/liveboard.d.ts +6 -0
- package/lib/src/embed/liveboard.d.ts.map +1 -1
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/spotter-viz-utils.d.ts +34 -0
- package/lib/src/embed/spotter-viz-utils.d.ts.map +1 -1
- package/lib/src/embed/spotter-viz-utils.js.map +1 -1
- package/lib/src/embed/spotter-viz-utils.spec.js +17 -0
- package/lib/src/embed/spotter-viz-utils.spec.js.map +1 -1
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +14 -7
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +146 -0
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/errors.d.ts +1 -0
- package/lib/src/errors.d.ts.map +1 -1
- package/lib/src/errors.js +1 -0
- package/lib/src/errors.js.map +1 -1
- package/lib/src/index.d.ts +2 -2
- package/lib/src/index.d.ts.map +1 -1
- package/lib/src/index.js +2 -2
- package/lib/src/index.js.map +1 -1
- package/lib/src/react/util.spec.js +251 -0
- package/lib/src/react/util.spec.js.map +1 -1
- package/lib/src/types.d.ts +119 -2
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +97 -0
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/processData.spec.js +93 -0
- package/lib/src/utils/processData.spec.js.map +1 -1
- package/lib/src/utils/processTrigger.spec.js +61 -0
- package/lib/src/utils/processTrigger.spec.js.map +1 -1
- package/lib/src/visual-embed-sdk.d.ts +306 -17
- package/package.json +1 -1
- package/src/css-variables.ts +73 -0
- package/src/embed/app.spec.ts +140 -0
- package/src/embed/app.ts +38 -2
- package/src/embed/conversation.spec.ts +32 -1
- package/src/embed/conversation.ts +29 -0
- package/src/embed/events.spec.ts +705 -1
- package/src/embed/host-events.spec.ts +124 -0
- package/src/embed/hostEventClient/contracts.ts +41 -14
- package/src/embed/hostEventClient/host-event-client.spec.ts +326 -0
- package/src/embed/hostEventClient/host-event-client.ts +15 -0
- package/src/embed/hostEventClient/utils.spec.ts +204 -0
- package/src/embed/hostEventClient/utils.ts +37 -10
- package/src/embed/liveboard.ts +6 -0
- package/src/embed/spotter-viz-utils.spec.ts +17 -0
- package/src/embed/spotter-viz-utils.ts +34 -0
- package/src/embed/ts-embed.spec.ts +186 -0
- package/src/embed/ts-embed.ts +12 -4
- package/src/errors.ts +1 -0
- package/src/index.ts +2 -1
- package/src/react/util.spec.tsx +255 -0
- package/src/types.ts +119 -0
- package/src/utils/processData.spec.ts +97 -0
- package/src/utils/processTrigger.spec.ts +91 -0
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
init,
|
|
8
8
|
AuthType,
|
|
9
9
|
LiveboardEmbed,
|
|
10
|
+
AppEmbed,
|
|
10
11
|
HostEvent,
|
|
11
12
|
EmbedErrorCodes,
|
|
12
13
|
} from '../index';
|
|
@@ -1633,3 +1634,126 @@ describe('context parameter forwarded in postMessage payload', () => {
|
|
|
1633
1634
|
});
|
|
1634
1635
|
});
|
|
1635
1636
|
});
|
|
1637
|
+
|
|
1638
|
+
// ---------------------------------------------------------------------------
|
|
1639
|
+
// SetVisibleVizs — array vizId injection (NOT in the parametrized arrayEvents)
|
|
1640
|
+
// ---------------------------------------------------------------------------
|
|
1641
|
+
describe('HostEvent.SetVisibleVizs — array payload vizId injection', () => {
|
|
1642
|
+
test('vizId is added as a non-indexed property on the array — array.length unchanged', async () => {
|
|
1643
|
+
mockMessageChannel();
|
|
1644
|
+
const { lb, iframe } = await renderLiveboard({ vizId });
|
|
1645
|
+
const arrayPayload = ['viz1', 'viz2'];
|
|
1646
|
+
await executeAfterWait(() => {
|
|
1647
|
+
lb.trigger(HostEvent.SetVisibleVizs, arrayPayload as any);
|
|
1648
|
+
const call = (iframe.contentWindow.postMessage as jest.Mock).mock.calls[0];
|
|
1649
|
+
const sentData = call[0].data;
|
|
1650
|
+
expect((sentData as any).vizId).toBe(vizId);
|
|
1651
|
+
expect(sentData.length).toBe(2);
|
|
1652
|
+
});
|
|
1653
|
+
});
|
|
1654
|
+
|
|
1655
|
+
test('without viewConfig.vizId — array payload is sent as-is (no vizId property)', async () => {
|
|
1656
|
+
mockMessageChannel();
|
|
1657
|
+
const { lb, iframe } = await renderLiveboard(); // no vizId
|
|
1658
|
+
const arrayPayload = ['viz1'];
|
|
1659
|
+
await executeAfterWait(() => {
|
|
1660
|
+
lb.trigger(HostEvent.SetVisibleVizs, arrayPayload as any);
|
|
1661
|
+
const call = (iframe.contentWindow.postMessage as jest.Mock).mock.calls[0];
|
|
1662
|
+
const sentData = call[0].data;
|
|
1663
|
+
expect((sentData as any).vizId).toBeUndefined();
|
|
1664
|
+
});
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
test('returns null and calls handleError when !isRendered', async () => {
|
|
1668
|
+
const lb = unrenderedLiveboard();
|
|
1669
|
+
const result = await lb.trigger(HostEvent.SetVisibleVizs, ['viz1'] as any);
|
|
1670
|
+
expect(result).toBeNull();
|
|
1671
|
+
expect((lb as any).handleError).toHaveBeenCalledWith(
|
|
1672
|
+
expect.objectContaining({ code: EmbedErrorCodes.RENDER_NOT_CALLED }),
|
|
1673
|
+
);
|
|
1674
|
+
});
|
|
1675
|
+
});
|
|
1676
|
+
|
|
1677
|
+
// ---------------------------------------------------------------------------
|
|
1678
|
+
// UpdateRuntimeFilters — AppEmbed path (no vizId injection)
|
|
1679
|
+
// ---------------------------------------------------------------------------
|
|
1680
|
+
describe('HostEvent.UpdateRuntimeFilters — AppEmbed path', () => {
|
|
1681
|
+
async function renderApp() {
|
|
1682
|
+
const app = new AppEmbed(getRootEl(), {
|
|
1683
|
+
frameParams: { width: '100%', height: '100%' },
|
|
1684
|
+
});
|
|
1685
|
+
await app.render();
|
|
1686
|
+
const iframe = getIFrameEl();
|
|
1687
|
+
jest.spyOn(iframe.contentWindow, 'postMessage');
|
|
1688
|
+
return { app, iframe };
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
test('AppEmbed sends payload directly without vizId injection', async () => {
|
|
1692
|
+
mockMessageChannel();
|
|
1693
|
+
const { app, iframe } = await renderApp();
|
|
1694
|
+
const filters = [{ columnName: 'col', operator: 'EQ', values: ['v1'] }];
|
|
1695
|
+
await executeAfterWait(() => {
|
|
1696
|
+
app.trigger(HostEvent.UpdateRuntimeFilters, filters as any);
|
|
1697
|
+
const call = (iframe.contentWindow.postMessage as jest.Mock).mock.calls[0];
|
|
1698
|
+
const sentData = call[0].data;
|
|
1699
|
+
// Array payload with no vizId property injected
|
|
1700
|
+
expect((sentData as any).vizId).toBeUndefined();
|
|
1701
|
+
expect(sentData).toEqual(filters);
|
|
1702
|
+
});
|
|
1703
|
+
});
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
// ---------------------------------------------------------------------------
|
|
1707
|
+
// Present — vizId injection on LiveboardEmbed
|
|
1708
|
+
// ---------------------------------------------------------------------------
|
|
1709
|
+
describe('HostEvent.Present — vizId injection on LiveboardEmbed', () => {
|
|
1710
|
+
test('viewConfig.vizId is injected into the Present payload', async () => {
|
|
1711
|
+
mockMessageChannel();
|
|
1712
|
+
const { lb, iframe } = await renderLiveboard({ vizId });
|
|
1713
|
+
await executeAfterWait(() => {
|
|
1714
|
+
lb.trigger(HostEvent.Present, {} as any);
|
|
1715
|
+
expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith(
|
|
1716
|
+
expect.objectContaining({
|
|
1717
|
+
data: expect.objectContaining({ vizId }),
|
|
1718
|
+
}),
|
|
1719
|
+
thoughtSpotHost,
|
|
1720
|
+
expect.anything(),
|
|
1721
|
+
);
|
|
1722
|
+
});
|
|
1723
|
+
});
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
// ---------------------------------------------------------------------------
|
|
1727
|
+
// DownloadAsCsv — additional cases
|
|
1728
|
+
// ---------------------------------------------------------------------------
|
|
1729
|
+
describe('HostEvent.DownloadAsCsv — additional cases', () => {
|
|
1730
|
+
test('viewConfig.vizId overwrites an explicit payload vizId', async () => {
|
|
1731
|
+
mockMessageChannel();
|
|
1732
|
+
const { lb, iframe } = await renderLiveboard({ vizId });
|
|
1733
|
+
await executeAfterWait(() => {
|
|
1734
|
+
lb.trigger(HostEvent.DownloadAsCsv, { vizId: 'explicit-id' } as any);
|
|
1735
|
+
const call = (iframe.contentWindow.postMessage as jest.Mock).mock.calls[0];
|
|
1736
|
+
const sentData = call[0].data;
|
|
1737
|
+
expect(sentData.vizId).toBe(vizId); // viewConfig.vizId wins
|
|
1738
|
+
});
|
|
1739
|
+
});
|
|
1740
|
+
|
|
1741
|
+
test('returns null and calls handleError when !isRendered', async () => {
|
|
1742
|
+
const lb = unrenderedLiveboard();
|
|
1743
|
+
const result = await lb.trigger(HostEvent.DownloadAsCsv, {} as any);
|
|
1744
|
+
expect(result).toBeNull();
|
|
1745
|
+
expect((lb as any).handleError).toHaveBeenCalledWith(
|
|
1746
|
+
expect.objectContaining({ code: EmbedErrorCodes.RENDER_NOT_CALLED }),
|
|
1747
|
+
);
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
test('returns null and logs debug when !iFrame', async () => {
|
|
1751
|
+
const lb = new LiveboardEmbed(getRootEl(), {
|
|
1752
|
+
frameParams: { width: '100%', height: '100%' },
|
|
1753
|
+
liveboardId,
|
|
1754
|
+
});
|
|
1755
|
+
(lb as any).isRendered = true;
|
|
1756
|
+
const result = await lb.trigger(HostEvent.DownloadAsCsv, {} as any);
|
|
1757
|
+
expect(result).toBeNull();
|
|
1758
|
+
});
|
|
1759
|
+
});
|
|
@@ -7,6 +7,43 @@ export interface LiveboardTab {
|
|
|
7
7
|
[key: string]: any;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Levels at which a filter or parameter can be applied.
|
|
12
|
+
*/
|
|
13
|
+
export enum ApplicabilityLevel {
|
|
14
|
+
Liveboard = 'LIVEBOARD',
|
|
15
|
+
Tab = 'TAB',
|
|
16
|
+
Group = 'GROUP',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Scopes a filter or parameter to a specific target.
|
|
21
|
+
* At `LIVEBOARD` level the filter applies to the whole Liveboard, so `targetId`
|
|
22
|
+
* is not required.
|
|
23
|
+
*/
|
|
24
|
+
export interface Applicability {
|
|
25
|
+
level: ApplicabilityLevel;
|
|
26
|
+
targetId?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface FilterUpdate {
|
|
30
|
+
column: string;
|
|
31
|
+
oper: string;
|
|
32
|
+
values: string[];
|
|
33
|
+
type?: string;
|
|
34
|
+
applicability?: Applicability;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface LiveboardFilter {
|
|
38
|
+
applicability?: Applicability;
|
|
39
|
+
[key: string]: any;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface LiveboardParameter {
|
|
43
|
+
applicability?: Applicability;
|
|
44
|
+
[key: string]: any;
|
|
45
|
+
}
|
|
46
|
+
|
|
10
47
|
export enum UIPassthroughEvent {
|
|
11
48
|
PinAnswerToLiveboard = 'addVizToPinboard',
|
|
12
49
|
SaveAnswer = 'saveAnswer',
|
|
@@ -103,7 +140,7 @@ export type UIPassthroughContractBase = {
|
|
|
103
140
|
vizId?: string;
|
|
104
141
|
};
|
|
105
142
|
response: {
|
|
106
|
-
liveboardFilters:
|
|
143
|
+
liveboardFilters: LiveboardFilter[];
|
|
107
144
|
runtimeFilters: RuntimeFilter[];
|
|
108
145
|
};
|
|
109
146
|
};
|
|
@@ -116,7 +153,7 @@ export type UIPassthroughContractBase = {
|
|
|
116
153
|
[UIPassthroughEvent.GetParameters]: {
|
|
117
154
|
request: Record<string, never>;
|
|
118
155
|
response: {
|
|
119
|
-
parameters:
|
|
156
|
+
parameters: LiveboardParameter[];
|
|
120
157
|
};
|
|
121
158
|
};
|
|
122
159
|
[UIPassthroughEvent.GetTML]: {
|
|
@@ -143,18 +180,8 @@ export type UIPassthroughContractBase = {
|
|
|
143
180
|
};
|
|
144
181
|
[UIPassthroughEvent.UpdateFilters]: {
|
|
145
182
|
request: {
|
|
146
|
-
filter?:
|
|
147
|
-
|
|
148
|
-
oper: string;
|
|
149
|
-
values: string[];
|
|
150
|
-
type?: string;
|
|
151
|
-
};
|
|
152
|
-
filters?: {
|
|
153
|
-
column: string;
|
|
154
|
-
oper: string;
|
|
155
|
-
values: string[];
|
|
156
|
-
type?: string;
|
|
157
|
-
}[];
|
|
183
|
+
filter?: FilterUpdate;
|
|
184
|
+
filters?: FilterUpdate[];
|
|
158
185
|
};
|
|
159
186
|
response: unknown;
|
|
160
187
|
};
|
|
@@ -311,6 +311,70 @@ describe('HostEventClient', () => {
|
|
|
311
311
|
);
|
|
312
312
|
});
|
|
313
313
|
|
|
314
|
+
it('should dispatch UpdateParameters over the legacy channel', async () => {
|
|
315
|
+
const { client, mockIframe } = createHostEventClient();
|
|
316
|
+
const payload = [
|
|
317
|
+
{ name: 'param1', value: 10, applicability: { level: 'TAB', targetId: 'tab-guid-1' } },
|
|
318
|
+
] as any;
|
|
319
|
+
mockProcessTrigger.mockResolvedValueOnce({ success: true });
|
|
320
|
+
|
|
321
|
+
const result = await client.triggerHostEvent(HostEvent.UpdateParameters, payload);
|
|
322
|
+
|
|
323
|
+
expect(mockProcessTrigger).toHaveBeenCalledTimes(1);
|
|
324
|
+
expect(mockProcessTrigger).toHaveBeenCalledWith(
|
|
325
|
+
mockIframe,
|
|
326
|
+
HostEvent.UpdateParameters,
|
|
327
|
+
mockThoughtSpotHost,
|
|
328
|
+
payload,
|
|
329
|
+
undefined,
|
|
330
|
+
);
|
|
331
|
+
expect(result).toEqual({ success: true });
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('should forward UpdateParameters with null applicability', async () => {
|
|
335
|
+
const { client, mockIframe } = createHostEventClient();
|
|
336
|
+
const payload = [{ name: 'param1', value: 10, applicability: null }] as any;
|
|
337
|
+
mockProcessTrigger.mockResolvedValueOnce({ success: true });
|
|
338
|
+
|
|
339
|
+
await client.triggerHostEvent(HostEvent.UpdateParameters, payload);
|
|
340
|
+
|
|
341
|
+
expect(mockProcessTrigger).toHaveBeenCalledWith(
|
|
342
|
+
mockIframe,
|
|
343
|
+
HostEvent.UpdateParameters,
|
|
344
|
+
mockThoughtSpotHost,
|
|
345
|
+
payload,
|
|
346
|
+
undefined,
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should throw when UpdateParameters entry has invalid applicability', async () => {
|
|
351
|
+
const { client } = createHostEventClient();
|
|
352
|
+
const invalidPayload = [
|
|
353
|
+
{ name: 'param1', value: 10, applicability: { level: 'TAB' } }, // missing targetId
|
|
354
|
+
] as any;
|
|
355
|
+
|
|
356
|
+
await expect(client.triggerHostEvent(HostEvent.UpdateParameters, invalidPayload))
|
|
357
|
+
.rejects.toThrow('UpdateParameters received an invalid applicability');
|
|
358
|
+
expect(mockProcessTrigger).not.toHaveBeenCalled();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should pass context to UpdateParameters event', async () => {
|
|
362
|
+
const { client, mockIframe } = createHostEventClient();
|
|
363
|
+
const payload = [{ name: 'param1', value: 10 }] as any;
|
|
364
|
+
const context = { vizId: 'viz-1' } as any;
|
|
365
|
+
mockProcessTrigger.mockResolvedValueOnce({ success: true });
|
|
366
|
+
|
|
367
|
+
await client.triggerHostEvent(HostEvent.UpdateParameters, payload, context);
|
|
368
|
+
|
|
369
|
+
expect(mockProcessTrigger).toHaveBeenCalledWith(
|
|
370
|
+
mockIframe,
|
|
371
|
+
HostEvent.UpdateParameters,
|
|
372
|
+
mockThoughtSpotHost,
|
|
373
|
+
payload,
|
|
374
|
+
context,
|
|
375
|
+
);
|
|
376
|
+
});
|
|
377
|
+
|
|
314
378
|
it('should throw when DrillDown payload has no valid points', async () => {
|
|
315
379
|
const { client } = createHostEventClient();
|
|
316
380
|
const invalidPayload = {} as any;
|
|
@@ -816,4 +880,266 @@ describe('HostEventClient', () => {
|
|
|
816
880
|
expect(result).toEqual({ ok: true });
|
|
817
881
|
});
|
|
818
882
|
});
|
|
883
|
+
|
|
884
|
+
describe('availablePassthroughKeysCache reuse', () => {
|
|
885
|
+
const allKeys = () => [{ value: { keys: Object.values(UIPassthroughEvent) } }];
|
|
886
|
+
|
|
887
|
+
it('GetFilters: second call reuses cache — GetAvailableUIPassthroughs called only once', async () => {
|
|
888
|
+
const { client } = createHostEventClient();
|
|
889
|
+
const filterResponse = [{ value: { liveboardFilters: [] as any[], runtimeFilters: [] as any[] } }];
|
|
890
|
+
mockProcessTrigger
|
|
891
|
+
.mockResolvedValueOnce(allKeys())
|
|
892
|
+
.mockResolvedValueOnce(filterResponse)
|
|
893
|
+
.mockResolvedValueOnce(filterResponse);
|
|
894
|
+
|
|
895
|
+
await client.triggerHostEvent(HostEvent.GetFilters, {});
|
|
896
|
+
await client.triggerHostEvent(HostEvent.GetFilters, {});
|
|
897
|
+
|
|
898
|
+
// 3 calls total: 1 GetAvailableUIPassthroughs + 2 GetFilters
|
|
899
|
+
expect(mockProcessTrigger).toHaveBeenCalledTimes(3);
|
|
900
|
+
expect(mockProcessTrigger).not.toHaveBeenNthCalledWith(
|
|
901
|
+
3,
|
|
902
|
+
expect.anything(),
|
|
903
|
+
HostEvent.UIPassthrough,
|
|
904
|
+
mockThoughtSpotHost,
|
|
905
|
+
expect.objectContaining({ type: UIPassthroughEvent.GetAvailableUIPassthroughs }),
|
|
906
|
+
undefined,
|
|
907
|
+
);
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
it('GetTabs: second call reuses cache — GetAvailableUIPassthroughs called only once', async () => {
|
|
911
|
+
const { client } = createHostEventClient();
|
|
912
|
+
const tabResponse = [{ value: { orderedTabIds: [] as any[], numberOfTabs: 0, Tabs: [] as any[] } }];
|
|
913
|
+
mockProcessTrigger
|
|
914
|
+
.mockResolvedValueOnce(allKeys())
|
|
915
|
+
.mockResolvedValueOnce(tabResponse)
|
|
916
|
+
.mockResolvedValueOnce(tabResponse);
|
|
917
|
+
|
|
918
|
+
await client.triggerHostEvent(HostEvent.GetTabs, {});
|
|
919
|
+
await client.triggerHostEvent(HostEvent.GetTabs, {});
|
|
920
|
+
|
|
921
|
+
expect(mockProcessTrigger).toHaveBeenCalledTimes(3);
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
it('GetParameters: second call reuses cache — GetAvailableUIPassthroughs called only once', async () => {
|
|
925
|
+
const { client } = createHostEventClient();
|
|
926
|
+
const paramResponse = [{ value: { parameters: [] as any[] } }];
|
|
927
|
+
mockProcessTrigger
|
|
928
|
+
.mockResolvedValueOnce(allKeys())
|
|
929
|
+
.mockResolvedValueOnce(paramResponse)
|
|
930
|
+
.mockResolvedValueOnce(paramResponse);
|
|
931
|
+
|
|
932
|
+
await client.triggerHostEvent(HostEvent.GetParameters, {});
|
|
933
|
+
await client.triggerHostEvent(HostEvent.GetParameters, {});
|
|
934
|
+
|
|
935
|
+
expect(mockProcessTrigger).toHaveBeenCalledTimes(3);
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
it('GetAnswerSession: second call reuses cache — GetAvailableUIPassthroughs called only once', async () => {
|
|
939
|
+
const { client } = createHostEventClient();
|
|
940
|
+
const sessionResponse = [{ value: { session: 's1' } }];
|
|
941
|
+
mockProcessTrigger
|
|
942
|
+
.mockResolvedValueOnce(allKeys())
|
|
943
|
+
.mockResolvedValueOnce(sessionResponse)
|
|
944
|
+
.mockResolvedValueOnce(sessionResponse);
|
|
945
|
+
|
|
946
|
+
await client.triggerHostEvent(HostEvent.GetAnswerSession, {});
|
|
947
|
+
await client.triggerHostEvent(HostEvent.GetAnswerSession, {});
|
|
948
|
+
|
|
949
|
+
expect(mockProcessTrigger).toHaveBeenCalledTimes(3);
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
it('UIPassthrough direct trigger does not populate the cache', async () => {
|
|
953
|
+
const { client } = createHostEventClient();
|
|
954
|
+
mockProcessTrigger.mockResolvedValue([{ value: { result: 'ok' } }]);
|
|
955
|
+
|
|
956
|
+
await client.triggerHostEvent(HostEvent.UIPassthrough, {});
|
|
957
|
+
// Cache not populated — next GetFilters call must still fetch keys
|
|
958
|
+
mockProcessTrigger.mockResolvedValueOnce(allKeys());
|
|
959
|
+
mockProcessTrigger.mockResolvedValueOnce([{ value: { liveboardFilters: [], runtimeFilters: [] as any[] } }]);
|
|
960
|
+
await client.triggerHostEvent(HostEvent.GetFilters, {});
|
|
961
|
+
|
|
962
|
+
// UIPassthrough call + GetAvailableUIPassthroughs + GetFilters = 3
|
|
963
|
+
expect(mockProcessTrigger).toHaveBeenCalledTimes(3);
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
it('GetAnswerSession: catch in getAvailableUIPassthroughKeys does not cache, so next call retries GetAvailableUIPassthroughs', async () => {
|
|
967
|
+
const { client } = createHostEventClient();
|
|
968
|
+
mockProcessTrigger.mockReset();
|
|
969
|
+
|
|
970
|
+
// Flow per triggerHostEvent call when GetAvailableUIPassthroughs rejects:
|
|
971
|
+
// 1. getAvailableUIPassthroughKeys → processTrigger(UIPassthrough, GetAvailableUIPassthroughs) → rejects → returns []
|
|
972
|
+
// 2. keys=[] so getDataWithPassthroughFallback is called →
|
|
973
|
+
// triggerUIPassthroughApi(GetAnswerSession) → processTrigger(UIPassthrough) → null → hostEventFallback called
|
|
974
|
+
// 3. hostEventFallback → processTrigger(GetAnswerSession) → legacy result
|
|
975
|
+
|
|
976
|
+
// First triggerHostEvent: 3 calls
|
|
977
|
+
mockProcessTrigger.mockRejectedValueOnce(new Error('network error')); // call 1
|
|
978
|
+
mockProcessTrigger.mockResolvedValueOnce(null); // call 2
|
|
979
|
+
mockProcessTrigger.mockResolvedValueOnce({ session: 'leg1' }); // call 3
|
|
980
|
+
|
|
981
|
+
await client.triggerHostEvent(HostEvent.GetAnswerSession, { vizId: '1' });
|
|
982
|
+
|
|
983
|
+
// Second triggerHostEvent: cache was NOT set, so GetAvailableUIPassthroughs is retried: 3 more calls
|
|
984
|
+
mockProcessTrigger.mockRejectedValueOnce(new Error('network error')); // call 4
|
|
985
|
+
mockProcessTrigger.mockResolvedValueOnce(null); // call 5
|
|
986
|
+
mockProcessTrigger.mockResolvedValueOnce({ session: 'leg2' }); // call 6
|
|
987
|
+
|
|
988
|
+
await client.triggerHostEvent(HostEvent.GetAnswerSession, { vizId: '2' });
|
|
989
|
+
|
|
990
|
+
// 6 total calls: GetAvailableUIPassthroughs was called TWICE (cache not set after catch)
|
|
991
|
+
expect(mockProcessTrigger).toHaveBeenCalledTimes(6);
|
|
992
|
+
const availableCallsCount = mockProcessTrigger.mock.calls.filter(
|
|
993
|
+
(call) => call[3]?.type === UIPassthroughEvent.GetAvailableUIPassthroughs,
|
|
994
|
+
).length;
|
|
995
|
+
expect(availableCallsCount).toBe(2);
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
describe('GetTML edge cases', () => {
|
|
1000
|
+
const allKeys = () => [{ value: { keys: Object.values(UIPassthroughEvent) } }];
|
|
1001
|
+
|
|
1002
|
+
it('includeNonExecutedSearchTokens flag is forwarded in UIPassthrough parameters', async () => {
|
|
1003
|
+
const { client, mockIframe } = createHostEventClient();
|
|
1004
|
+
mockProcessTrigger
|
|
1005
|
+
.mockResolvedValueOnce(allKeys())
|
|
1006
|
+
.mockResolvedValueOnce([{ value: { tml: 'data' } }]);
|
|
1007
|
+
|
|
1008
|
+
await client.triggerHostEvent(HostEvent.GetTML, { includeNonExecutedSearchTokens: true } as any);
|
|
1009
|
+
|
|
1010
|
+
expect(mockProcessTrigger).toHaveBeenNthCalledWith(
|
|
1011
|
+
2,
|
|
1012
|
+
mockIframe,
|
|
1013
|
+
HostEvent.UIPassthrough,
|
|
1014
|
+
mockThoughtSpotHost,
|
|
1015
|
+
{
|
|
1016
|
+
type: UIPassthroughEvent.GetTML,
|
|
1017
|
+
parameters: { includeNonExecutedSearchTokens: true },
|
|
1018
|
+
},
|
|
1019
|
+
undefined,
|
|
1020
|
+
);
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
it('value.error (singular) throws — distinct from value.errors', async () => {
|
|
1024
|
+
const { client } = createHostEventClient();
|
|
1025
|
+
mockProcessTrigger
|
|
1026
|
+
.mockResolvedValueOnce(allKeys())
|
|
1027
|
+
.mockResolvedValueOnce([{ value: { error: 'TML fetch failed' } }]);
|
|
1028
|
+
|
|
1029
|
+
await expect(client.triggerHostEvent(HostEvent.GetTML, {}))
|
|
1030
|
+
.rejects.toThrow('TML fetch failed');
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
it('GetTML key absent from non-empty keys array falls back to legacy hostEventFallback', async () => {
|
|
1034
|
+
const { client, mockIframe } = createHostEventClient();
|
|
1035
|
+
// Return keys that do NOT include GetTML
|
|
1036
|
+
mockProcessTrigger
|
|
1037
|
+
.mockResolvedValueOnce([{ value: { keys: [UIPassthroughEvent.GetFilters] } }])
|
|
1038
|
+
.mockResolvedValueOnce({ answer: { search_query: 'revenue' } });
|
|
1039
|
+
|
|
1040
|
+
const result = await client.triggerHostEvent(HostEvent.GetTML, {});
|
|
1041
|
+
|
|
1042
|
+
expect(mockProcessTrigger).toHaveBeenNthCalledWith(
|
|
1043
|
+
2,
|
|
1044
|
+
mockIframe,
|
|
1045
|
+
HostEvent.GetTML,
|
|
1046
|
+
mockThoughtSpotHost,
|
|
1047
|
+
{},
|
|
1048
|
+
undefined,
|
|
1049
|
+
);
|
|
1050
|
+
expect(result).toEqual({ answer: { search_query: 'revenue' } });
|
|
1051
|
+
});
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
describe('SaveAnswer key-presence edge cases', () => {
|
|
1055
|
+
const allKeys = () => [{ value: { keys: Object.values(UIPassthroughEvent) } }];
|
|
1056
|
+
const saveResponse = [{
|
|
1057
|
+
value: {
|
|
1058
|
+
saveResponse: {
|
|
1059
|
+
data: {
|
|
1060
|
+
Answer__save: {
|
|
1061
|
+
answer: { id: 'ans123' },
|
|
1062
|
+
},
|
|
1063
|
+
},
|
|
1064
|
+
},
|
|
1065
|
+
},
|
|
1066
|
+
refId: 'viz1',
|
|
1067
|
+
}];
|
|
1068
|
+
|
|
1069
|
+
it('name+description both empty string route through passthrough, not Save fallback', async () => {
|
|
1070
|
+
const { client, mockIframe } = createHostEventClient();
|
|
1071
|
+
mockProcessTrigger
|
|
1072
|
+
.mockResolvedValueOnce(allKeys())
|
|
1073
|
+
.mockResolvedValueOnce(saveResponse);
|
|
1074
|
+
|
|
1075
|
+
await client.triggerHostEvent(HostEvent.SaveAnswer, { name: '', description: '' } as any);
|
|
1076
|
+
|
|
1077
|
+
expect(mockProcessTrigger).toHaveBeenNthCalledWith(
|
|
1078
|
+
2,
|
|
1079
|
+
mockIframe,
|
|
1080
|
+
HostEvent.UIPassthrough,
|
|
1081
|
+
mockThoughtSpotHost,
|
|
1082
|
+
{ type: UIPassthroughEvent.SaveAnswer, parameters: { name: '', description: '' } },
|
|
1083
|
+
undefined,
|
|
1084
|
+
);
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
it('description: undefined (key present) routes through passthrough, not Save fallback', async () => {
|
|
1088
|
+
const { client, mockIframe } = createHostEventClient();
|
|
1089
|
+
mockProcessTrigger
|
|
1090
|
+
.mockResolvedValueOnce(allKeys())
|
|
1091
|
+
.mockResolvedValueOnce(saveResponse);
|
|
1092
|
+
|
|
1093
|
+
await client.triggerHostEvent(HostEvent.SaveAnswer, { name: 'Test', description: undefined } as any);
|
|
1094
|
+
|
|
1095
|
+
expect(mockProcessTrigger).toHaveBeenNthCalledWith(
|
|
1096
|
+
2,
|
|
1097
|
+
mockIframe,
|
|
1098
|
+
HostEvent.UIPassthrough,
|
|
1099
|
+
mockThoughtSpotHost,
|
|
1100
|
+
{ type: UIPassthroughEvent.SaveAnswer, parameters: { name: 'Test', description: undefined } },
|
|
1101
|
+
undefined,
|
|
1102
|
+
);
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
it('name key absent falls back to HostEvent.Save', async () => {
|
|
1106
|
+
const { client, mockIframe } = createHostEventClient();
|
|
1107
|
+
mockProcessTrigger
|
|
1108
|
+
.mockResolvedValueOnce(allKeys())
|
|
1109
|
+
.mockResolvedValueOnce([saveResponse[0].value]);
|
|
1110
|
+
|
|
1111
|
+
await client.triggerHostEvent(HostEvent.SaveAnswer, { description: 'desc only' } as any);
|
|
1112
|
+
|
|
1113
|
+
expect(mockProcessTrigger).toHaveBeenCalledWith(
|
|
1114
|
+
mockIframe,
|
|
1115
|
+
HostEvent.Save,
|
|
1116
|
+
mockThoughtSpotHost,
|
|
1117
|
+
{ description: 'desc only' },
|
|
1118
|
+
undefined,
|
|
1119
|
+
);
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
it('answerId is undefined when saveResponse.data.Answer__save.answer.id is missing', async () => {
|
|
1123
|
+
const { client } = createHostEventClient();
|
|
1124
|
+
const noIdSaveResponse = [{
|
|
1125
|
+
value: {
|
|
1126
|
+
saveResponse: {
|
|
1127
|
+
data: {
|
|
1128
|
+
Answer__save: {
|
|
1129
|
+
answer: {},
|
|
1130
|
+
},
|
|
1131
|
+
},
|
|
1132
|
+
},
|
|
1133
|
+
},
|
|
1134
|
+
refId: 'viz1',
|
|
1135
|
+
}];
|
|
1136
|
+
mockProcessTrigger
|
|
1137
|
+
.mockResolvedValueOnce(allKeys())
|
|
1138
|
+
.mockResolvedValueOnce(noIdSaveResponse);
|
|
1139
|
+
|
|
1140
|
+
const result = await client.triggerHostEvent(HostEvent.SaveAnswer, { name: 'Test', description: 'Desc' } as any);
|
|
1141
|
+
|
|
1142
|
+
expect(result.answerId).toBeUndefined();
|
|
1143
|
+
});
|
|
1144
|
+
});
|
|
819
1145
|
});
|
|
@@ -3,8 +3,10 @@ import { processTrigger as processTriggerService } from '../../utils/processTrig
|
|
|
3
3
|
import { getEmbedConfig } from '../embedConfig';
|
|
4
4
|
import {
|
|
5
5
|
isValidUpdateFiltersPayload,
|
|
6
|
+
isValidUpdateParametersPayload,
|
|
6
7
|
isValidDrillDownPayload,
|
|
7
8
|
throwUpdateFiltersValidationError,
|
|
9
|
+
throwUpdateParametersValidationError,
|
|
8
10
|
throwDrillDownValidationError,
|
|
9
11
|
} from './utils';
|
|
10
12
|
import {
|
|
@@ -58,6 +60,7 @@ export class HostEventClient {
|
|
|
58
60
|
[HostEvent.Pin]: (p, c) => this.handlePinEvent(p, c),
|
|
59
61
|
[HostEvent.SaveAnswer]: (p, c) => this.handleSaveAnswerEvent(p, c),
|
|
60
62
|
[HostEvent.UpdateFilters]: (p, c) => this.handleUpdateFiltersEvent(p, c),
|
|
63
|
+
[HostEvent.UpdateParameters]: (p, c) => this.handleUpdateParametersEvent(p, c),
|
|
61
64
|
[HostEvent.DrillDown]: (p, c) => this.handleDrillDownEvent(p, c),
|
|
62
65
|
};
|
|
63
66
|
}
|
|
@@ -243,6 +246,18 @@ export class HostEventClient {
|
|
|
243
246
|
return this.handleHostEventWithParam(UIPassthroughEvent.UpdateFilters, payload, context as ContextType);
|
|
244
247
|
}
|
|
245
248
|
|
|
249
|
+
protected handleUpdateParametersEvent(
|
|
250
|
+
payload: HostEventRequest<HostEvent.UpdateParameters>,
|
|
251
|
+
context?: ContextType,
|
|
252
|
+
): Promise<any> {
|
|
253
|
+
if (!isValidUpdateParametersPayload(payload)) {
|
|
254
|
+
throwUpdateParametersValidationError();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// UpdateParameters has no UI passthrough contract; dispatch over the legacy channel
|
|
258
|
+
return this.hostEventFallback(HostEvent.UpdateParameters, payload, context);
|
|
259
|
+
}
|
|
260
|
+
|
|
246
261
|
protected handleDrillDownEvent(
|
|
247
262
|
payload: HostEventRequest<HostEvent.DrillDown>,
|
|
248
263
|
context?: ContextType,
|