@thoughtspot/visual-embed-sdk 1.53.0 → 1.53.2-beta
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/auth.d.ts.map +1 -1
- package/cjs/src/auth.js +25 -6
- package/cjs/src/auth.js.map +1 -1
- package/cjs/src/auth.spec.js +40 -8
- package/cjs/src/auth.spec.js.map +1 -1
- package/cjs/src/contracts/contracts.spec.js +169 -2
- package/cjs/src/contracts/contracts.spec.js.map +1 -1
- package/cjs/src/contracts/embed-event-payloads.d.ts +212 -14
- package/cjs/src/contracts/embed-event-payloads.d.ts.map +1 -1
- package/cjs/src/contracts/host-event-contracts.d.ts +11 -0
- package/cjs/src/contracts/host-event-contracts.d.ts.map +1 -1
- package/cjs/src/css-variables.d.ts +140 -0
- package/cjs/src/css-variables.d.ts.map +1 -1
- package/cjs/src/embed/app.d.ts +1 -0
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/auto-frame-renderer.d.ts +14 -1
- package/cjs/src/embed/auto-frame-renderer.d.ts.map +1 -1
- package/cjs/src/embed/auto-frame-renderer.js +91 -2
- package/cjs/src/embed/auto-frame-renderer.js.map +1 -1
- package/cjs/src/embed/auto-frame-renderer.spec.js +112 -7
- package/cjs/src/embed/auto-frame-renderer.spec.js.map +1 -1
- package/cjs/src/embed/conversation.d.ts +14 -0
- package/cjs/src/embed/conversation.d.ts.map +1 -1
- package/cjs/src/embed/conversation.js.map +1 -1
- package/cjs/src/embed/conversation.spec.js +22 -0
- package/cjs/src/embed/conversation.spec.js.map +1 -1
- package/cjs/src/embed/host-events.spec.js +50 -0
- package/cjs/src/embed/host-events.spec.js.map +1 -1
- package/cjs/src/embed/liveboard.d.ts +2 -16
- package/cjs/src/embed/liveboard.d.ts.map +1 -1
- package/cjs/src/embed/liveboard.js +29 -10
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/liveboard.spec.js +139 -0
- package/cjs/src/embed/liveboard.spec.js.map +1 -1
- package/cjs/src/embed/spotter-utils.d.ts +1 -0
- package/cjs/src/embed/spotter-utils.d.ts.map +1 -1
- package/cjs/src/embed/spotter-utils.js +7 -1
- package/cjs/src/embed/spotter-utils.js.map +1 -1
- package/cjs/src/embed/spotter-utils.spec.js +19 -0
- package/cjs/src/embed/spotter-utils.spec.js.map +1 -1
- package/cjs/src/embed/ts-embed.d.ts +21 -16
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +77 -63
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/embed/ts-embed.spec.js +240 -7
- package/cjs/src/embed/ts-embed.spec.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.map +1 -1
- package/cjs/src/react/all-types-export.d.ts +1 -1
- package/cjs/src/react/all-types-export.d.ts.map +1 -1
- package/cjs/src/react/all-types-export.js.map +1 -1
- package/cjs/src/react/util.d.ts +8 -1
- package/cjs/src/react/util.d.ts.map +1 -1
- package/cjs/src/react/util.js.map +1 -1
- package/cjs/src/types.d.ts +249 -2
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +247 -0
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils/runtimeTelemetry.d.ts +11 -0
- package/cjs/src/utils/runtimeTelemetry.d.ts.map +1 -0
- package/cjs/src/utils/runtimeTelemetry.js +34 -0
- package/cjs/src/utils/runtimeTelemetry.js.map +1 -0
- package/cjs/src/utils/runtimeTelemetry.spec.d.ts +2 -0
- package/cjs/src/utils/runtimeTelemetry.spec.d.ts.map +1 -0
- package/cjs/src/utils/runtimeTelemetry.spec.js +124 -0
- package/cjs/src/utils/runtimeTelemetry.spec.js.map +1 -0
- package/dist/{index-CyDR8j_0.js → index-DQ7r5f1z.js} +1 -1
- package/dist/src/auth.d.ts.map +1 -1
- package/dist/src/contracts/embed-event-payloads.d.ts +212 -14
- package/dist/src/contracts/embed-event-payloads.d.ts.map +1 -1
- package/dist/src/contracts/host-event-contracts.d.ts +11 -0
- package/dist/src/contracts/host-event-contracts.d.ts.map +1 -1
- package/dist/src/css-variables.d.ts +140 -0
- package/dist/src/css-variables.d.ts.map +1 -1
- package/dist/src/embed/app.d.ts +1 -0
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/auto-frame-renderer.d.ts +14 -1
- package/dist/src/embed/auto-frame-renderer.d.ts.map +1 -1
- package/dist/src/embed/conversation.d.ts +14 -0
- package/dist/src/embed/conversation.d.ts.map +1 -1
- package/dist/src/embed/liveboard.d.ts +2 -16
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/embed/spotter-utils.d.ts +1 -0
- package/dist/src/embed/spotter-utils.d.ts.map +1 -1
- package/dist/src/embed/ts-embed.d.ts +21 -16
- package/dist/src/embed/ts-embed.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/react/all-types-export.d.ts +1 -1
- package/dist/src/react/all-types-export.d.ts.map +1 -1
- package/dist/src/react/util.d.ts +8 -1
- package/dist/src/react/util.d.ts.map +1 -1
- package/dist/src/types.d.ts +249 -2
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/runtimeTelemetry.d.ts +11 -0
- package/dist/src/utils/runtimeTelemetry.d.ts.map +1 -0
- package/dist/src/utils/runtimeTelemetry.spec.d.ts +2 -0
- package/dist/src/utils/runtimeTelemetry.spec.d.ts.map +1 -0
- package/dist/tsembed-react.es.js +474 -84
- package/dist/tsembed-react.js +473 -83
- package/dist/tsembed.es.js +542 -85
- package/dist/tsembed.js +541 -84
- package/dist/visual-embed-sdk-react-full.d.ts +649 -30
- package/dist/visual-embed-sdk-react.d.ts +646 -28
- package/dist/visual-embed-sdk.d.ts +642 -44
- package/lib/package.json +1 -1
- package/lib/src/auth.d.ts.map +1 -1
- package/lib/src/auth.js +25 -6
- package/lib/src/auth.js.map +1 -1
- package/lib/src/auth.spec.js +40 -8
- package/lib/src/auth.spec.js.map +1 -1
- package/lib/src/contracts/contracts.spec.js +169 -2
- package/lib/src/contracts/contracts.spec.js.map +1 -1
- package/lib/src/contracts/embed-event-payloads.d.ts +212 -14
- package/lib/src/contracts/embed-event-payloads.d.ts.map +1 -1
- package/lib/src/contracts/host-event-contracts.d.ts +11 -0
- package/lib/src/contracts/host-event-contracts.d.ts.map +1 -1
- package/lib/src/css-variables.d.ts +140 -0
- package/lib/src/css-variables.d.ts.map +1 -1
- package/lib/src/embed/app.d.ts +1 -0
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/auto-frame-renderer.d.ts +14 -1
- package/lib/src/embed/auto-frame-renderer.d.ts.map +1 -1
- package/lib/src/embed/auto-frame-renderer.js +91 -2
- package/lib/src/embed/auto-frame-renderer.js.map +1 -1
- package/lib/src/embed/auto-frame-renderer.spec.js +113 -8
- package/lib/src/embed/auto-frame-renderer.spec.js.map +1 -1
- package/lib/src/embed/conversation.d.ts +14 -0
- package/lib/src/embed/conversation.d.ts.map +1 -1
- package/lib/src/embed/conversation.js.map +1 -1
- package/lib/src/embed/conversation.spec.js +22 -0
- package/lib/src/embed/conversation.spec.js.map +1 -1
- package/lib/src/embed/host-events.spec.js +50 -0
- package/lib/src/embed/host-events.spec.js.map +1 -1
- package/lib/src/embed/liveboard.d.ts +2 -16
- package/lib/src/embed/liveboard.d.ts.map +1 -1
- package/lib/src/embed/liveboard.js +28 -10
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/liveboard.spec.js +139 -0
- package/lib/src/embed/liveboard.spec.js.map +1 -1
- package/lib/src/embed/spotter-utils.d.ts +1 -0
- package/lib/src/embed/spotter-utils.d.ts.map +1 -1
- package/lib/src/embed/spotter-utils.js +7 -1
- package/lib/src/embed/spotter-utils.js.map +1 -1
- package/lib/src/embed/spotter-utils.spec.js +19 -0
- package/lib/src/embed/spotter-utils.spec.js.map +1 -1
- package/lib/src/embed/ts-embed.d.ts +21 -16
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +78 -64
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +240 -7
- package/lib/src/embed/ts-embed.spec.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.map +1 -1
- package/lib/src/react/all-types-export.d.ts +1 -1
- package/lib/src/react/all-types-export.d.ts.map +1 -1
- package/lib/src/react/all-types-export.js.map +1 -1
- package/lib/src/react/util.d.ts +8 -1
- package/lib/src/react/util.d.ts.map +1 -1
- package/lib/src/react/util.js.map +1 -1
- package/lib/src/types.d.ts +249 -2
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +247 -0
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/runtimeTelemetry.d.ts +11 -0
- package/lib/src/utils/runtimeTelemetry.d.ts.map +1 -0
- package/lib/src/utils/runtimeTelemetry.js +28 -0
- package/lib/src/utils/runtimeTelemetry.js.map +1 -0
- package/lib/src/utils/runtimeTelemetry.spec.d.ts +2 -0
- package/lib/src/utils/runtimeTelemetry.spec.d.ts.map +1 -0
- package/lib/src/utils/runtimeTelemetry.spec.js +122 -0
- package/lib/src/utils/runtimeTelemetry.spec.js.map +1 -0
- package/lib/src/visual-embed-sdk.d.ts +642 -44
- package/package.json +1 -1
- package/src/auth.spec.ts +47 -11
- package/src/auth.ts +35 -6
- package/src/contracts/contracts.spec.ts +186 -3
- package/src/contracts/embed-event-payloads.ts +281 -14
- package/src/contracts/host-event-contracts.ts +14 -0
- package/src/css-variables.ts +168 -0
- package/src/embed/app.ts +1 -0
- package/src/embed/auto-frame-renderer.spec.ts +134 -8
- package/src/embed/auto-frame-renderer.ts +103 -2
- package/src/embed/conversation.spec.ts +24 -0
- package/src/embed/conversation.ts +14 -0
- package/src/embed/host-events.spec.ts +68 -0
- package/src/embed/liveboard.spec.ts +156 -0
- package/src/embed/liveboard.ts +34 -10
- package/src/embed/spotter-utils.spec.ts +20 -0
- package/src/embed/spotter-utils.ts +12 -1
- package/src/embed/ts-embed.spec.ts +297 -8
- package/src/embed/ts-embed.ts +79 -64
- package/src/index.ts +2 -0
- package/src/react/all-types-export.ts +1 -0
- package/src/react/util.ts +13 -1
- package/src/types.ts +249 -0
- package/src/utils/runtimeTelemetry.spec.ts +148 -0
- package/src/utils/runtimeTelemetry.ts +50 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { startAutoMCPFrameRenderer } from './auto-frame-renderer';
|
|
1
|
+
import { startAutoMCPFrameRenderer, HiddenActionItemByDefaultForMCPAnswers } from './auto-frame-renderer';
|
|
2
2
|
import { Action, AuthType, AutoMCPFrameRendererViewConfig, EmbedEvent, InterceptedApiType, Param } from '../types';
|
|
3
3
|
import { init } from '../index';
|
|
4
4
|
import * as authInstance from '../auth';
|
|
@@ -266,9 +266,15 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
266
266
|
expect(src).toContain('disableHint');
|
|
267
267
|
});
|
|
268
268
|
|
|
269
|
-
test('hiddenActions → hideAction in rendered src as JSON array', async () => {
|
|
269
|
+
test('hiddenActions → hideAction in rendered src as JSON array (unioned with MCP denylist)', async () => {
|
|
270
270
|
const src = await captureRenderedSrc({ hiddenActions: [Action.Pin] });
|
|
271
|
-
expect(src).toContain(
|
|
271
|
+
expect(src).toContain(
|
|
272
|
+
`hideAction=${JSON.stringify([
|
|
273
|
+
Action.ReportError,
|
|
274
|
+
Action.Pin,
|
|
275
|
+
...HiddenActionItemByDefaultForMCPAnswers,
|
|
276
|
+
])}`,
|
|
277
|
+
);
|
|
272
278
|
});
|
|
273
279
|
|
|
274
280
|
test('visibleActions → visibleAction in rendered src', async () => {
|
|
@@ -399,6 +405,95 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
399
405
|
});
|
|
400
406
|
});
|
|
401
407
|
|
|
408
|
+
// ─── MCP-answer default hidden actions (denylist)
|
|
409
|
+
// ───────────────────────────────────────────────
|
|
410
|
+
|
|
411
|
+
describe('MCP-answer default hidden actions', () => {
|
|
412
|
+
/** Parse the hideAction JSON array out of a rendered iframe src. */
|
|
413
|
+
function parseHideAction(src: string): string[] {
|
|
414
|
+
const raw = decodeURIComponent(src.split('hideAction=')[1]?.split('&')[0] ?? '');
|
|
415
|
+
return raw ? JSON.parse(raw) : [];
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Actions that must stay visible for MCP-rendered answers. */
|
|
419
|
+
const shouldStayVisible = [
|
|
420
|
+
Action.Pin,
|
|
421
|
+
Action.Save,
|
|
422
|
+
Action.Download,
|
|
423
|
+
Action.AxisMenuAggregate,
|
|
424
|
+
Action.AxisMenuFilter,
|
|
425
|
+
Action.AxisMenuSort,
|
|
426
|
+
Action.AxisMenuPosition,
|
|
427
|
+
Action.DrillExclude,
|
|
428
|
+
Action.DrillInclude,
|
|
429
|
+
Action.DrillDown,
|
|
430
|
+
Action.ShowUnderlyingData,
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
test('empty config hides the full MCP denylist by default', async () => {
|
|
434
|
+
const src = await captureRenderedSrc();
|
|
435
|
+
const hidden = parseHideAction(src);
|
|
436
|
+
HiddenActionItemByDefaultForMCPAnswers.forEach((action) =>
|
|
437
|
+
expect(hidden).toContain(action));
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test('empty config keeps the curated visible actions visible', async () => {
|
|
441
|
+
const src = await captureRenderedSrc();
|
|
442
|
+
const hidden = parseHideAction(src);
|
|
443
|
+
shouldStayVisible.forEach((action) => expect(hidden).not.toContain(action));
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test('caller hiddenActions are unioned with the MCP denylist', async () => {
|
|
447
|
+
const src = await captureRenderedSrc({ hiddenActions: [Action.Share] });
|
|
448
|
+
const hidden = parseHideAction(src);
|
|
449
|
+
expect(hidden).toContain(Action.Share);
|
|
450
|
+
HiddenActionItemByDefaultForMCPAnswers.forEach((action) =>
|
|
451
|
+
expect(hidden).toContain(action));
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('string hideAction from additionalFlags is parsed, not spread into chars', async () => {
|
|
455
|
+
const src = await captureRenderedSrc({
|
|
456
|
+
additionalFlags: { hideAction: `${Action.Share}, ${Action.Edit}` },
|
|
457
|
+
});
|
|
458
|
+
const hidden = parseHideAction(src);
|
|
459
|
+
expect(hidden).toContain(Action.Share);
|
|
460
|
+
expect(hidden).toContain(Action.Edit);
|
|
461
|
+
hidden.forEach((action) => expect(action.length).toBeGreaterThan(1));
|
|
462
|
+
HiddenActionItemByDefaultForMCPAnswers.forEach((action) =>
|
|
463
|
+
expect(hidden).toContain(action));
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test('allowlist wins: visibleActions skips the MCP denylist', async () => {
|
|
467
|
+
const src = await captureRenderedSrc({ visibleActions: [Action.Pin] });
|
|
468
|
+
expect(src).toContain('visibleAction');
|
|
469
|
+
const hidden = parseHideAction(src);
|
|
470
|
+
HiddenActionItemByDefaultForMCPAnswers.forEach((action) =>
|
|
471
|
+
expect(hidden).not.toContain(action));
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test('source-iframe hideAction is unioned, not clobbered', async () => {
|
|
475
|
+
let capturedSrc = '';
|
|
476
|
+
renderIFrameSpy.mockRestore();
|
|
477
|
+
renderIFrameSpy = jest.spyOn(TsEmbed.prototype as any, 'renderIFrame')
|
|
478
|
+
.mockImplementation(async function (this: any, src: string) {
|
|
479
|
+
capturedSrc = src;
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const observer = startAutoMCPFrameRenderer();
|
|
483
|
+
const iframe = document.createElement('iframe');
|
|
484
|
+
const sourceHide = encodeURIComponent(JSON.stringify([Action.Save]));
|
|
485
|
+
iframe.src = `https://${thoughtSpotHost}/v2/?${Param.Tsmcp}=true&hideAction=${sourceHide}#/embed/answer`;
|
|
486
|
+
document.body.appendChild(iframe);
|
|
487
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
488
|
+
|
|
489
|
+
const hidden = parseHideAction(capturedSrc);
|
|
490
|
+
expect(hidden).toContain(Action.Save);
|
|
491
|
+
HiddenActionItemByDefaultForMCPAnswers.forEach((action) =>
|
|
492
|
+
expect(hidden).toContain(action));
|
|
493
|
+
observer.disconnect();
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
402
497
|
// ─── frameParams forwarding
|
|
403
498
|
// ───────────────────────────────────────────────
|
|
404
499
|
|
|
@@ -539,12 +634,37 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
539
634
|
document.body.appendChild(iframe);
|
|
540
635
|
await new Promise((r) => setTimeout(r, 50));
|
|
541
636
|
|
|
637
|
+
// tsmcp must not survive on the rendered iframe: isTSMCPIframe would
|
|
638
|
+
// otherwise re-match the SDK's own output and cause an infinite
|
|
639
|
+
// replacement loop via the same MutationObserver.
|
|
542
640
|
expect(capturedSrc).not.toContain(`${Param.Tsmcp}=true`);
|
|
543
641
|
expect(capturedSrc).toContain('customParam=hello');
|
|
544
642
|
expect(capturedSrc).toMatch(/\?[^#]+#/);
|
|
545
643
|
observer.disconnect();
|
|
546
644
|
});
|
|
547
645
|
|
|
646
|
+
test('should mark the rendered iframe with isMcpAnswerEmbed=true instead of tsmcp', async () => {
|
|
647
|
+
let capturedSrc = '';
|
|
648
|
+
renderIFrameSpy.mockRestore();
|
|
649
|
+
renderIFrameSpy = jest.spyOn(TsEmbed.prototype as any, 'renderIFrame')
|
|
650
|
+
.mockImplementation(async function (this: any, src: string) {
|
|
651
|
+
capturedSrc = src;
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
const observer = startAutoMCPFrameRenderer();
|
|
655
|
+
const iframe = document.createElement('iframe');
|
|
656
|
+
iframe.src = `https://${thoughtSpotHost}/v2/?${Param.Tsmcp}=true#/embed/answer`;
|
|
657
|
+
document.body.appendChild(iframe);
|
|
658
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
659
|
+
|
|
660
|
+
// The app-facing marker is present, but tsmcp (the SDK's own
|
|
661
|
+
// re-processing trigger) is not — this is what keeps the
|
|
662
|
+
// MutationObserver from looping on its own rendered output.
|
|
663
|
+
expect(capturedSrc).toContain(`${Param.IsMcpAnswerEmbed}=true`);
|
|
664
|
+
expect(capturedSrc).not.toContain(`${Param.Tsmcp}=true`);
|
|
665
|
+
observer.disconnect();
|
|
666
|
+
});
|
|
667
|
+
|
|
548
668
|
test('should preserve hash from original iframe src', async () => {
|
|
549
669
|
let capturedSrc = '';
|
|
550
670
|
renderIFrameSpy.mockRestore();
|
|
@@ -639,8 +759,9 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
639
759
|
return new URLSearchParams(qs).get(param);
|
|
640
760
|
}
|
|
641
761
|
|
|
762
|
+
// hiddenActions is covered separately below: the auto-renderer unions the
|
|
763
|
+
// MCP denylist into hideAction, so its value is a superset of LiveboardEmbed's.
|
|
642
764
|
test.each([
|
|
643
|
-
['hiddenActions', { hiddenActions: [Action.Pin] }, 'hideAction'],
|
|
644
765
|
['disabledActions', { disabledActions: [Action.Pin] }, 'disableAction'],
|
|
645
766
|
['visibleActions', { visibleActions: [Action.Download] }, 'visibleAction'],
|
|
646
767
|
])(
|
|
@@ -658,7 +779,7 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
658
779
|
},
|
|
659
780
|
);
|
|
660
781
|
|
|
661
|
-
test('hideAction value is a JSON array, not CSV', async () => {
|
|
782
|
+
test('hideAction value is a JSON array, not CSV, and is a superset of LiveboardEmbed', async () => {
|
|
662
783
|
const autoSrc = await captureRenderedSrc({ hiddenActions: [Action.Pin] });
|
|
663
784
|
const liveboardSrc = await captureLiveboardSrc({ hiddenActions: [Action.Pin] });
|
|
664
785
|
|
|
@@ -668,9 +789,14 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
668
789
|
// Both must parse as a JSON array — not CSV like "reportError,pin"
|
|
669
790
|
expect(() => JSON.parse(autoValue)).not.toThrow();
|
|
670
791
|
expect(() => JSON.parse(liveboardValue)).not.toThrow();
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
expect(
|
|
792
|
+
const autoParsed = JSON.parse(autoValue);
|
|
793
|
+
const liveboardParsed = JSON.parse(liveboardValue);
|
|
794
|
+
expect(Array.isArray(autoParsed)).toBe(true);
|
|
795
|
+
expect(Array.isArray(liveboardParsed)).toBe(true);
|
|
796
|
+
// Auto-renderer hides everything LiveboardEmbed does, plus the MCP denylist.
|
|
797
|
+
liveboardParsed.forEach((action: string) => expect(autoParsed).toContain(action));
|
|
798
|
+
HiddenActionItemByDefaultForMCPAnswers.forEach((action) =>
|
|
799
|
+
expect(autoParsed).toContain(action));
|
|
674
800
|
});
|
|
675
801
|
});
|
|
676
802
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AutoMCPFrameRendererViewConfig, Param } from "../types";
|
|
1
|
+
import { Action, AutoMCPFrameRendererViewConfig, Param } from "../types";
|
|
2
2
|
import { TsEmbed } from "./ts-embed";
|
|
3
3
|
import { getQueryParamString } from "../utils";
|
|
4
4
|
import { tokenizedFetch } from "../tokenizedFetch";
|
|
@@ -35,6 +35,28 @@ interface AnswerSessionParams {
|
|
|
35
35
|
acGenNo: string;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Actions hidden by default on Answers rendered by the ThoughtSpot MCP server
|
|
40
|
+
* (iframes carrying the `tsmcp=true` marker). Applied automatically by
|
|
41
|
+
* {@link AutoFrameRenderer} so the auto-rendered answer exposes a curated set of
|
|
42
|
+
* controls: Pin/Save/Download at the top level, Aggregate/Filter/Sort/Position in
|
|
43
|
+
* the column menu, and Exclude/Only include/Drill down/Show underlying data in the
|
|
44
|
+
* axis menu remain visible, while the actions below are removed.
|
|
45
|
+
*
|
|
46
|
+
* This is a denylist merged into {@link Param.HideActions}; anything not listed
|
|
47
|
+
* here stays visible. It is skipped when the caller supplies a `visibleActions`
|
|
48
|
+
* allowlist (see {@link AutoFrameRenderer.getEmbedParamsObject}).
|
|
49
|
+
*/
|
|
50
|
+
export const HiddenActionItemByDefaultForMCPAnswers = [
|
|
51
|
+
Action.Edit,
|
|
52
|
+
Action.InConversationTraining, // Add to Coaching
|
|
53
|
+
Action.AxisMenuConditionalFormat,
|
|
54
|
+
Action.AxisMenuRename,
|
|
55
|
+
Action.AxisMenuEdit,
|
|
56
|
+
Action.AxisMenuRemove,
|
|
57
|
+
Action.MakeACopy,
|
|
58
|
+
];
|
|
59
|
+
|
|
38
60
|
|
|
39
61
|
/**
|
|
40
62
|
* Starts an automatic renderer that watches the DOM for iframes containing
|
|
@@ -219,6 +241,39 @@ function getAnswerIds(
|
|
|
219
241
|
return request;
|
|
220
242
|
}
|
|
221
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Normalizes a `hideAction` value into an array of action ids. The SDK-side value
|
|
246
|
+
* is already an `Action[]`; a value coming off the source iframe URL is a string
|
|
247
|
+
* that may be a JSON array (`["edit","save"]`) or a comma-separated list.
|
|
248
|
+
*/
|
|
249
|
+
function parseHideActions(value: unknown): string[] {
|
|
250
|
+
if (Array.isArray(value)) {
|
|
251
|
+
return value as string[];
|
|
252
|
+
}
|
|
253
|
+
if (typeof value !== 'string' || value === '') {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const parsed = JSON.parse(value);
|
|
258
|
+
if (Array.isArray(parsed)) {
|
|
259
|
+
return parsed.map(String);
|
|
260
|
+
}
|
|
261
|
+
} catch (e) {
|
|
262
|
+
// Not JSON — fall back to a comma-separated list.
|
|
263
|
+
}
|
|
264
|
+
return value.split(',').map((s) => s.trim()).filter(Boolean);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Unions two `hideAction` values (SDK-computed and source-iframe) into a single
|
|
269
|
+
* de-duplicated array of action ids.
|
|
270
|
+
*/
|
|
271
|
+
function unionHideActions(sdkValue: unknown, sourceValue: unknown): string[] {
|
|
272
|
+
return Array.from(
|
|
273
|
+
new Set([...parseHideActions(sdkValue), ...parseHideActions(sourceValue)]),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
222
277
|
function isTSMCPIframe(iframe: HTMLIFrameElement) {
|
|
223
278
|
try {
|
|
224
279
|
const url = new URL(iframe.src);
|
|
@@ -247,11 +302,45 @@ class AutoFrameRenderer extends TsEmbed {
|
|
|
247
302
|
super(container, viewConfig);
|
|
248
303
|
}
|
|
249
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Extends the base embed params with the curated MCP-answer denylist
|
|
307
|
+
* ({@link HiddenActionItemByDefaultForMCPAnswers}) and a marker letting
|
|
308
|
+
* the ThoughtSpot app detect this answer was rendered via the MCP
|
|
309
|
+
* auto-frame-renderer ({@link Param.IsMcpAnswerEmbed}).
|
|
310
|
+
*
|
|
311
|
+
* Precedence — merge, allowlist wins:
|
|
312
|
+
* - When the caller supplies a `visibleActions` allowlist, the base already
|
|
313
|
+
* set {@link Param.VisibleActions}; the denylist is skipped so the allowlist
|
|
314
|
+
* fully controls visibility.
|
|
315
|
+
* - Otherwise the denylist is unioned onto {@link Param.HideActions}, which the
|
|
316
|
+
* base has already populated with the default hidden actions plus any
|
|
317
|
+
* caller-supplied `hiddenActions`.
|
|
318
|
+
*/
|
|
319
|
+
protected getEmbedParamsObject() {
|
|
320
|
+
const queryParams = super.getEmbedParamsObject();
|
|
321
|
+
if (!queryParams[Param.VisibleActions]) {
|
|
322
|
+
// `additionalFlags` can override this with a non-array value, so
|
|
323
|
+
// normalize it rather than spreading it directly.
|
|
324
|
+
queryParams[Param.HideActions] = unionHideActions(
|
|
325
|
+
queryParams[Param.HideActions],
|
|
326
|
+
HiddenActionItemByDefaultForMCPAnswers,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
queryParams[Param.IsMcpAnswerEmbed] = true;
|
|
330
|
+
return queryParams;
|
|
331
|
+
}
|
|
332
|
+
|
|
250
333
|
/**
|
|
251
334
|
* Builds the final iframe `src` by merging the SDK embed parameters
|
|
252
335
|
* with the query parameters already present on the source iframe URL.
|
|
336
|
+
*
|
|
253
337
|
* The `tsmcp` marker param is removed so it does not propagate to the
|
|
254
|
-
* ThoughtSpot application.
|
|
338
|
+
* ThoughtSpot application. This is not just cosmetic: `tsmcp=true` is
|
|
339
|
+
* also how {@link isTSMCPIframe} recognizes an iframe as one this
|
|
340
|
+
* renderer must process. If the marker survived onto the iframe this
|
|
341
|
+
* class inserts, the same {@link MutationObserver} would immediately
|
|
342
|
+
* detect its own output as a new MCP iframe and re-process it, causing
|
|
343
|
+
* infinite recursive replacement. Do not re-add `tsmcp` here.
|
|
255
344
|
*
|
|
256
345
|
* @param sourceSrc - The original iframe's `src` URL string.
|
|
257
346
|
* @param preserveSourceUrl - When true, return the source URL unchanged.
|
|
@@ -274,7 +363,19 @@ class AutoFrameRenderer extends TsEmbed {
|
|
|
274
363
|
delete existingQueryParamsObject[Param.TsmcpConversationId];
|
|
275
364
|
delete existingQueryParamsObject[Param.TsmcpAnswerIndex];
|
|
276
365
|
|
|
366
|
+
// Source-iframe params win over SDK params on merge. Hidden actions
|
|
367
|
+
// are the exception: union them so a `hideAction` on the source iframe
|
|
368
|
+
// does not clobber the SDK-computed denylist (and vice versa).
|
|
369
|
+
const mergedHideActions = unionHideActions(
|
|
370
|
+
queryParams[Param.HideActions],
|
|
371
|
+
existingQueryParamsObject[Param.HideActions],
|
|
372
|
+
);
|
|
373
|
+
delete existingQueryParamsObject[Param.HideActions];
|
|
374
|
+
|
|
277
375
|
const mergedQueryParams = { ...queryParams, ...existingQueryParamsObject };
|
|
376
|
+
if (mergedHideActions.length) {
|
|
377
|
+
mergedQueryParams[Param.HideActions] = mergedHideActions;
|
|
378
|
+
}
|
|
278
379
|
const mergedQueryParamsString = getQueryParamString(mergedQueryParams, true);
|
|
279
380
|
const queryString = mergedQueryParamsString ? `?${mergedQueryParamsString}` : '';
|
|
280
381
|
const hash = resolved
|
|
@@ -795,6 +795,30 @@ describe('SpotterEmbed APP_INIT embedParams', () => {
|
|
|
795
795
|
).toEqual(spotterChatPinConfig);
|
|
796
796
|
});
|
|
797
797
|
|
|
798
|
+
it('should send enableConnectorMarketplace in embedParams.spotterSidebarConfig, not the URL', async () => {
|
|
799
|
+
const response = await getAppInitResponse({
|
|
800
|
+
worksheetId: 'ws1',
|
|
801
|
+
spotterSidebarConfig: {
|
|
802
|
+
enableConnectorMarketplace: true,
|
|
803
|
+
},
|
|
804
|
+
});
|
|
805
|
+
expect(response.data.embedParams.spotterSidebarConfig.enableConnectorMarketplace).toBe(true);
|
|
806
|
+
expect(getIFrameSrc()).not.toContain('enableConnectorMarketplace');
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
it('should send enableSpotterInstructionAndMoreSettings at the top level of embedParams', async () => {
|
|
810
|
+
const response = await getAppInitResponse({
|
|
811
|
+
worksheetId: 'ws1',
|
|
812
|
+
spotterSidebarConfig: {
|
|
813
|
+
enablePastConversationsSidebar: true,
|
|
814
|
+
enableSpotterInstructionAndMoreSettings: true,
|
|
815
|
+
},
|
|
816
|
+
});
|
|
817
|
+
expect(response.data.embedParams.enableSpotterInstructionAndMoreSettings).toBe(true);
|
|
818
|
+
expect(response.data.embedParams.spotterSidebarConfig)
|
|
819
|
+
.not.toHaveProperty('enableSpotterInstructionAndMoreSettings');
|
|
820
|
+
});
|
|
821
|
+
|
|
798
822
|
it('should populate enablePastConversationsSidebar from deprecated standalone flag', async () => {
|
|
799
823
|
const response = await getAppInitResponse({
|
|
800
824
|
worksheetId: 'ws1',
|
|
@@ -152,6 +152,19 @@ export interface SpotterSidebarViewConfig {
|
|
|
152
152
|
* @default Analysts
|
|
153
153
|
*/
|
|
154
154
|
spotterAnalystsLabel?: string;
|
|
155
|
+
/**
|
|
156
|
+
* Shows the connector marketplace option in the settings menu of the
|
|
157
|
+
* Spotter sidebar footer. When not set, the cluster setting applies.
|
|
158
|
+
* @version SDK: 1.53.0 | ThoughtSpot Cloud: 26.10.0.cl
|
|
159
|
+
*/
|
|
160
|
+
enableConnectorMarketplace?: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Shows the Spotter instructions and more settings options in the
|
|
163
|
+
* settings menu of the Spotter sidebar footer.
|
|
164
|
+
* @version SDK: 1.53.0 | ThoughtSpot Cloud: 26.10.0.cl
|
|
165
|
+
* @default false
|
|
166
|
+
*/
|
|
167
|
+
enableSpotterInstructionAndMoreSettings?: boolean;
|
|
155
168
|
}
|
|
156
169
|
|
|
157
170
|
/**
|
|
@@ -822,6 +835,7 @@ export interface SpotterAppInitData extends DefaultAppInitData {
|
|
|
822
835
|
visualOverridesParams?: VisualizationOverrides | null;
|
|
823
836
|
starterPrompts?: StarterPromptsConfig;
|
|
824
837
|
spotterAnalystConfig?: SpotterAnalystConfig;
|
|
838
|
+
enableSpotterInstructionAndMoreSettings?: boolean;
|
|
825
839
|
};
|
|
826
840
|
}
|
|
827
841
|
|
|
@@ -228,6 +228,30 @@ describe('HostEvent.OpenAddParameterModal', () => {
|
|
|
228
228
|
});
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// 10e – OpenAddTabModal
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
describe('HostEvent.OpenAddTabModal', () => {
|
|
235
|
+
test('postMessage type is openAddTabModal (camelCase, no payload)', async () => {
|
|
236
|
+
mockMessageChannel();
|
|
237
|
+
const { lb, iframe } = await renderLiveboard();
|
|
238
|
+
await executeAfterWait(() => {
|
|
239
|
+
lb.trigger(HostEvent.OpenAddTabModal);
|
|
240
|
+
expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith(
|
|
241
|
+
expect.objectContaining({ type: 'openAddTabModal' }),
|
|
242
|
+
thoughtSpotHost,
|
|
243
|
+
expect.anything(),
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('returns null when !isRendered', async () => {
|
|
249
|
+
const lb = unrenderedLiveboard();
|
|
250
|
+
const result = await lb.trigger(HostEvent.OpenAddTabModal);
|
|
251
|
+
expect(result).toBeNull();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
231
255
|
// ---------------------------------------------------------------------------
|
|
232
256
|
// 11 – AddColumns
|
|
233
257
|
// ---------------------------------------------------------------------------
|
|
@@ -636,6 +660,50 @@ describe('HostEvent.Edit', () => {
|
|
|
636
660
|
});
|
|
637
661
|
});
|
|
638
662
|
|
|
663
|
+
// ---------------------------------------------------------------------------
|
|
664
|
+
// ExitEditMode
|
|
665
|
+
// ---------------------------------------------------------------------------
|
|
666
|
+
describe('HostEvent.ExitEditMode', () => {
|
|
667
|
+
test("enum value is 'exitEditMode' (wire contract with the host app)", () => {
|
|
668
|
+
expect(HostEvent.ExitEditMode).toBe('exitEditMode');
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test("postMessage type is 'exitEditMode' (camelCase)", async () => {
|
|
672
|
+
mockMessageChannel();
|
|
673
|
+
const { lb, iframe } = await renderLiveboard();
|
|
674
|
+
await executeAfterWait(() => {
|
|
675
|
+
lb.trigger(HostEvent.ExitEditMode);
|
|
676
|
+
expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith(
|
|
677
|
+
expect.objectContaining({ type: 'exitEditMode' }),
|
|
678
|
+
thoughtSpotHost,
|
|
679
|
+
expect.anything(),
|
|
680
|
+
);
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test('viewConfig.vizId is injected into the payload as data.vizId', async () => {
|
|
685
|
+
mockMessageChannel();
|
|
686
|
+
const { lb, iframe } = await renderLiveboard({ vizId });
|
|
687
|
+
await executeAfterWait(() => {
|
|
688
|
+
lb.trigger(HostEvent.ExitEditMode, {} as any);
|
|
689
|
+
expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith(
|
|
690
|
+
expect.objectContaining({
|
|
691
|
+
type: 'exitEditMode',
|
|
692
|
+
data: expect.objectContaining({ vizId }),
|
|
693
|
+
}),
|
|
694
|
+
thoughtSpotHost,
|
|
695
|
+
expect.anything(),
|
|
696
|
+
);
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
test('returns null when !isRendered', async () => {
|
|
701
|
+
const lb = unrenderedLiveboard();
|
|
702
|
+
const result = await lb.trigger(HostEvent.ExitEditMode);
|
|
703
|
+
expect(result).toBeNull();
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
|
|
639
707
|
// ---------------------------------------------------------------------------
|
|
640
708
|
// 30 – CopyLink (maps to 'embedDocument')
|
|
641
709
|
// ---------------------------------------------------------------------------
|
|
@@ -158,6 +158,25 @@ describe('Liveboard/viz embed tests', () => {
|
|
|
158
158
|
});
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
+
test('should hide and disable the Cancel button in the Liveboard edit header', async () => {
|
|
162
|
+
expect(Action.Cancel).toBe('cancel');
|
|
163
|
+
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
|
|
164
|
+
hiddenActions: [Action.Cancel],
|
|
165
|
+
disabledActions: [Action.Cancel],
|
|
166
|
+
disabledActionReason: 'Save your changes to leave edit mode',
|
|
167
|
+
...defaultViewConfig,
|
|
168
|
+
liveboardId,
|
|
169
|
+
} as LiveboardViewConfig);
|
|
170
|
+
liveboardEmbed.render();
|
|
171
|
+
await executeAfterWait(() => {
|
|
172
|
+
expectUrlToHaveParamsWithValues(getIFrameSrc(), {
|
|
173
|
+
hideAction: JSON.stringify([Action.ReportError, Action.Cancel]),
|
|
174
|
+
disableAction: JSON.stringify([Action.Cancel]),
|
|
175
|
+
disableHint: 'Save your changes to leave edit mode',
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
161
180
|
test('should hide and disable the Spotter button on the Liveboard header', async () => {
|
|
162
181
|
expect(Action.SpotterOnLiveboard).toBe('spotterOnLiveboard');
|
|
163
182
|
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
|
|
@@ -175,6 +194,51 @@ describe('Liveboard/viz embed tests', () => {
|
|
|
175
194
|
});
|
|
176
195
|
});
|
|
177
196
|
|
|
197
|
+
test('should hide the LiveboardAddPanel action', async () => {
|
|
198
|
+
expect(Action.LiveboardAddPanel).toBe('liveboardAddPanel');
|
|
199
|
+
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
|
|
200
|
+
hiddenActions: [Action.LiveboardAddPanel],
|
|
201
|
+
...defaultViewConfig,
|
|
202
|
+
liveboardId,
|
|
203
|
+
} as LiveboardViewConfig);
|
|
204
|
+
liveboardEmbed.render();
|
|
205
|
+
await executeAfterWait(() => {
|
|
206
|
+
expectUrlToHaveParamsWithValues(getIFrameSrc(), {
|
|
207
|
+
hideAction: JSON.stringify([Action.ReportError, Action.LiveboardAddPanel]),
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('should disable the LiveboardAddPanel action with a reason', async () => {
|
|
213
|
+
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
|
|
214
|
+
disabledActions: [Action.LiveboardAddPanel],
|
|
215
|
+
disabledActionReason: 'Action denied',
|
|
216
|
+
...defaultViewConfig,
|
|
217
|
+
liveboardId,
|
|
218
|
+
} as LiveboardViewConfig);
|
|
219
|
+
liveboardEmbed.render();
|
|
220
|
+
await executeAfterWait(() => {
|
|
221
|
+
expectUrlToHaveParamsWithValues(getIFrameSrc(), {
|
|
222
|
+
disableAction: JSON.stringify([Action.LiveboardAddPanel]),
|
|
223
|
+
disableHint: 'Action denied',
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('should set LiveboardAddPanel in visible actions', async () => {
|
|
229
|
+
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
|
|
230
|
+
visibleActions: [Action.LiveboardAddPanel],
|
|
231
|
+
...defaultViewConfig,
|
|
232
|
+
liveboardId,
|
|
233
|
+
} as LiveboardViewConfig);
|
|
234
|
+
liveboardEmbed.render();
|
|
235
|
+
await executeAfterWait(() => {
|
|
236
|
+
expectUrlToHaveParamsWithValues(getIFrameSrc(), {
|
|
237
|
+
visibleAction: JSON.stringify([Action.LiveboardAddPanel]),
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
178
242
|
test('should set EditLiveboard and EditVisualization independently of each other and of Edit', async () => {
|
|
179
243
|
expect(Action.Edit).toBe('edit');
|
|
180
244
|
expect(Action.EditLiveboard).toBe('editLiveboard');
|
|
@@ -2832,6 +2896,98 @@ describe('Liveboard/viz embed tests', () => {
|
|
|
2832
2896
|
expect(navigateToLiveboardSpy).toHaveBeenCalled();
|
|
2833
2897
|
});
|
|
2834
2898
|
|
|
2899
|
+
test('should not route via home when a new instance shows an equal config', async () => {
|
|
2900
|
+
const viewConfig = {
|
|
2901
|
+
liveboardId,
|
|
2902
|
+
vizId,
|
|
2903
|
+
activeTabId,
|
|
2904
|
+
...defaultViewConfig,
|
|
2905
|
+
};
|
|
2906
|
+
const previous = new LiveboardEmbed(getRootEl(), { ...viewConfig, beforeRender: () => {} } as any);
|
|
2907
|
+
const liveboardEmbed = new LiveboardEmbed(getRootEl(), { ...viewConfig, beforeRender: () => {} } as any);
|
|
2908
|
+
|
|
2909
|
+
// React constructs a new embed on every mount.
|
|
2910
|
+
jest.spyOn(liveboardEmbed as any, 'getPreRenderObj').mockReturnValue(previous);
|
|
2911
|
+
const triggerSpy = jest
|
|
2912
|
+
.spyOn(liveboardEmbed, 'trigger')
|
|
2913
|
+
.mockImplementation(() => Promise.resolve(undefined as any));
|
|
2914
|
+
const navigateToLiveboardSpy = jest
|
|
2915
|
+
.spyOn(liveboardEmbed, 'navigateToLiveboard')
|
|
2916
|
+
.mockImplementation(() => Promise.resolve(undefined));
|
|
2917
|
+
|
|
2918
|
+
liveboardEmbed.isEmbedContainerLoaded = false;
|
|
2919
|
+
liveboardEmbed['beforePrerenderVisible']();
|
|
2920
|
+
liveboardEmbed.isEmbedContainerLoaded = true;
|
|
2921
|
+
liveboardEmbed['executeEmbedContainerReadyCallbacks']();
|
|
2922
|
+
await waitForPreRenderNavigate();
|
|
2923
|
+
|
|
2924
|
+
expect(triggerSpy).not.toHaveBeenCalledWith(HostEvent.Navigate, 'home');
|
|
2925
|
+
expect(navigateToLiveboardSpy).toHaveBeenCalled();
|
|
2926
|
+
});
|
|
2927
|
+
|
|
2928
|
+
test('should not route via home when the previous instance has been shown once', async () => {
|
|
2929
|
+
// navigateToLiveboard assigns vizId/activeTabId/personalizedViewId
|
|
2930
|
+
// unconditionally, so a shown instance carries them as undefined
|
|
2931
|
+
// while a fresh one lacks the keys entirely. Comparing raw objects
|
|
2932
|
+
// counts own keys, so it never matches again.
|
|
2933
|
+
const viewConfig = { liveboardId, ...defaultViewConfig };
|
|
2934
|
+
const previous = new LiveboardEmbed(getRootEl(), { ...viewConfig } as any);
|
|
2935
|
+
(previous as any).viewConfig.vizId = undefined;
|
|
2936
|
+
(previous as any).viewConfig.activeTabId = undefined;
|
|
2937
|
+
(previous as any).viewConfig.personalizedViewId = undefined;
|
|
2938
|
+
|
|
2939
|
+
const liveboardEmbed = new LiveboardEmbed(getRootEl(), { ...viewConfig } as any);
|
|
2940
|
+
|
|
2941
|
+
jest.spyOn(liveboardEmbed as any, 'getPreRenderObj').mockReturnValue(previous);
|
|
2942
|
+
const triggerSpy = jest
|
|
2943
|
+
.spyOn(liveboardEmbed, 'trigger')
|
|
2944
|
+
.mockImplementation(() => Promise.resolve(undefined as any));
|
|
2945
|
+
jest
|
|
2946
|
+
.spyOn(liveboardEmbed, 'navigateToLiveboard')
|
|
2947
|
+
.mockImplementation(() => Promise.resolve(undefined));
|
|
2948
|
+
|
|
2949
|
+
liveboardEmbed.isEmbedContainerLoaded = false;
|
|
2950
|
+
liveboardEmbed['beforePrerenderVisible']();
|
|
2951
|
+
liveboardEmbed.isEmbedContainerLoaded = true;
|
|
2952
|
+
liveboardEmbed['executeEmbedContainerReadyCallbacks']();
|
|
2953
|
+
await waitForPreRenderNavigate();
|
|
2954
|
+
|
|
2955
|
+
expect(triggerSpy).not.toHaveBeenCalledWith(HostEvent.Navigate, 'home');
|
|
2956
|
+
});
|
|
2957
|
+
|
|
2958
|
+
test('should route via home when a new instance shows the same route with other filters', async () => {
|
|
2959
|
+
const viewConfig = {
|
|
2960
|
+
liveboardId,
|
|
2961
|
+
vizId,
|
|
2962
|
+
activeTabId,
|
|
2963
|
+
...defaultViewConfig,
|
|
2964
|
+
};
|
|
2965
|
+
const previous = new LiveboardEmbed(getRootEl(), {
|
|
2966
|
+
...viewConfig,
|
|
2967
|
+
runtimeFilters: [{ columnName: 'region', operator: RuntimeFilterOp.EQ, values: ['east'] }],
|
|
2968
|
+
});
|
|
2969
|
+
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
|
|
2970
|
+
...viewConfig,
|
|
2971
|
+
runtimeFilters: [{ columnName: 'region', operator: RuntimeFilterOp.EQ, values: ['west'] }],
|
|
2972
|
+
});
|
|
2973
|
+
|
|
2974
|
+
jest.spyOn(liveboardEmbed as any, 'getPreRenderObj').mockReturnValue(previous);
|
|
2975
|
+
const triggerSpy = jest
|
|
2976
|
+
.spyOn(liveboardEmbed, 'trigger')
|
|
2977
|
+
.mockImplementation(() => Promise.resolve(undefined as any));
|
|
2978
|
+
jest
|
|
2979
|
+
.spyOn(liveboardEmbed, 'navigateToLiveboard')
|
|
2980
|
+
.mockImplementation(() => Promise.resolve(undefined));
|
|
2981
|
+
|
|
2982
|
+
liveboardEmbed.isEmbedContainerLoaded = false;
|
|
2983
|
+
liveboardEmbed['beforePrerenderVisible']();
|
|
2984
|
+
liveboardEmbed.isEmbedContainerLoaded = true;
|
|
2985
|
+
liveboardEmbed['executeEmbedContainerReadyCallbacks']();
|
|
2986
|
+
await waitForHomeHopNavigate();
|
|
2987
|
+
|
|
2988
|
+
expect(triggerSpy).toHaveBeenCalledWith(HostEvent.Navigate, 'home');
|
|
2989
|
+
});
|
|
2990
|
+
|
|
2835
2991
|
test('should not route via home when the pre-render is on another liveboard', async () => {
|
|
2836
2992
|
const liveboardEmbed = new LiveboardEmbed(getRootEl(), {
|
|
2837
2993
|
liveboardId,
|