@thoughtspot/visual-embed-sdk 1.52.1 → 1.52.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 +11 -4
- package/cjs/src/contracts/host-event-contracts.d.ts +49 -11
- package/cjs/src/contracts/host-event-contracts.d.ts.map +1 -1
- package/cjs/src/embed/app.d.ts +4 -3
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js +1 -1
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/app.spec.js +10 -0
- package/cjs/src/embed/app.spec.js.map +1 -1
- package/cjs/src/embed/auto-frame-renderer.js +1 -1
- package/cjs/src/embed/auto-frame-renderer.js.map +1 -1
- package/cjs/src/embed/auto-frame-renderer.spec.js +24 -13
- package/cjs/src/embed/auto-frame-renderer.spec.js.map +1 -1
- package/cjs/src/embed/bodyless-conversation.js +1 -1
- package/cjs/src/embed/bodyless-conversation.js.map +1 -1
- package/cjs/src/embed/conversation.d.ts +1 -0
- package/cjs/src/embed/conversation.d.ts.map +1 -1
- package/cjs/src/embed/conversation.js +4 -4
- package/cjs/src/embed/conversation.js.map +1 -1
- package/cjs/src/embed/conversation.spec.js +35 -24
- package/cjs/src/embed/conversation.spec.js.map +1 -1
- package/cjs/src/embed/embedConfig.spec.js +2 -1
- package/cjs/src/embed/embedConfig.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.d.ts +2 -2
- package/cjs/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.js +16 -10
- package/cjs/src/embed/hostEventClient/host-event-client.js.map +1 -1
- package/cjs/src/embed/hostEventClient/host-event-client.spec.js +14 -8
- package/cjs/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
- package/cjs/src/embed/hostEventClient/utils.d.ts +9 -0
- package/cjs/src/embed/hostEventClient/utils.d.ts.map +1 -1
- package/cjs/src/embed/hostEventClient/utils.js +49 -3
- package/cjs/src/embed/hostEventClient/utils.js.map +1 -1
- package/cjs/src/embed/hostEventClient/utils.spec.js +151 -0
- package/cjs/src/embed/hostEventClient/utils.spec.js.map +1 -1
- package/cjs/src/embed/liveboard.d.ts +4 -3
- package/cjs/src/embed/liveboard.d.ts.map +1 -1
- package/cjs/src/embed/liveboard.js +18 -13
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/liveboard.spec.js +2 -1
- package/cjs/src/embed/liveboard.spec.js.map +1 -1
- package/cjs/src/embed/search-bar.js +1 -1
- package/cjs/src/embed/search-bar.js.map +1 -1
- package/cjs/src/embed/search.js +1 -1
- package/cjs/src/embed/search.js.map +1 -1
- package/cjs/src/embed/search.spec.js +20 -0
- package/cjs/src/embed/search.spec.js.map +1 -1
- package/cjs/src/embed/spotter-utils.d.ts +8 -1
- package/cjs/src/embed/spotter-utils.d.ts.map +1 -1
- package/cjs/src/embed/spotter-utils.js +14 -1
- package/cjs/src/embed/spotter-utils.js.map +1 -1
- package/cjs/src/embed/spotter-utils.spec.js +26 -0
- package/cjs/src/embed/spotter-utils.spec.js.map +1 -1
- package/cjs/src/embed/ts-embed.d.ts +3 -0
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +108 -29
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/embed/ts-embed.spec.js +163 -23
- package/cjs/src/embed/ts-embed.spec.js.map +1 -1
- package/cjs/src/errors.js +1 -1
- package/cjs/src/errors.js.map +1 -1
- package/cjs/src/index.d.ts +2 -1
- package/cjs/src/index.d.ts.map +1 -1
- package/cjs/src/index.js +3 -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 +2 -1
- package/cjs/src/react/all-types-export.js.map +1 -1
- package/cjs/src/types.d.ts +45 -22
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +44 -21
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils/authService/tokenizedAuthService.spec.js +2 -1
- package/cjs/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
- package/cjs/src/utils/custom-actions.spec.js +4 -2
- package/cjs/src/utils/custom-actions.spec.js.map +1 -1
- package/cjs/src/utils/filterConverter.d.ts +106 -0
- package/cjs/src/utils/filterConverter.d.ts.map +1 -0
- package/cjs/src/utils/filterConverter.js +215 -0
- package/cjs/src/utils/filterConverter.js.map +1 -0
- package/cjs/src/utils/filterConverter.spec.d.ts +2 -0
- package/cjs/src/utils/filterConverter.spec.d.ts.map +1 -0
- package/cjs/src/utils/filterConverter.spec.js +709 -0
- package/cjs/src/utils/filterConverter.spec.js.map +1 -0
- package/cjs/src/utils/processData.spec.js +2 -1
- package/cjs/src/utils/processData.spec.js.map +1 -1
- package/dist/{index-C1pmYb5H.js → index-CcBYlGo9.js} +1 -1
- package/dist/src/contracts/host-event-contracts.d.ts +49 -11
- package/dist/src/contracts/host-event-contracts.d.ts.map +1 -1
- package/dist/src/embed/app.d.ts +4 -3
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/conversation.d.ts +1 -0
- package/dist/src/embed/conversation.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/host-event-client.d.ts +2 -2
- package/dist/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/dist/src/embed/hostEventClient/utils.d.ts +9 -0
- package/dist/src/embed/hostEventClient/utils.d.ts.map +1 -1
- package/dist/src/embed/liveboard.d.ts +4 -3
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/embed/spotter-utils.d.ts +8 -1
- package/dist/src/embed/spotter-utils.d.ts.map +1 -1
- package/dist/src/embed/ts-embed.d.ts +3 -0
- package/dist/src/embed/ts-embed.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -1
- 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/types.d.ts +45 -22
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/filterConverter.d.ts +106 -0
- package/dist/src/utils/filterConverter.d.ts.map +1 -0
- package/dist/src/utils/filterConverter.spec.d.ts +2 -0
- package/dist/src/utils/filterConverter.spec.d.ts.map +1 -0
- package/dist/tsembed-react.es.js +257 -87
- package/dist/tsembed-react.js +256 -86
- package/dist/tsembed.es.js +466 -89
- package/dist/tsembed.js +465 -87
- package/dist/visual-embed-sdk-react-full.d.ts +202 -40
- package/dist/visual-embed-sdk-react.d.ts +108 -40
- package/dist/visual-embed-sdk.d.ts +202 -40
- package/lib/package.json +11 -4
- package/lib/src/contracts/host-event-contracts.d.ts +49 -11
- package/lib/src/contracts/host-event-contracts.d.ts.map +1 -1
- package/lib/src/embed/app.d.ts +4 -3
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js +1 -1
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/app.spec.js +10 -0
- package/lib/src/embed/app.spec.js.map +1 -1
- package/lib/src/embed/auto-frame-renderer.js +1 -1
- package/lib/src/embed/auto-frame-renderer.js.map +1 -1
- package/lib/src/embed/auto-frame-renderer.spec.js +24 -13
- package/lib/src/embed/auto-frame-renderer.spec.js.map +1 -1
- package/lib/src/embed/bodyless-conversation.js +1 -1
- package/lib/src/embed/bodyless-conversation.js.map +1 -1
- package/lib/src/embed/conversation.d.ts +1 -0
- package/lib/src/embed/conversation.d.ts.map +1 -1
- package/lib/src/embed/conversation.js +5 -5
- package/lib/src/embed/conversation.js.map +1 -1
- package/lib/src/embed/conversation.spec.js +35 -24
- package/lib/src/embed/conversation.spec.js.map +1 -1
- package/lib/src/embed/embedConfig.spec.js +2 -1
- package/lib/src/embed/embedConfig.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.d.ts +2 -2
- package/lib/src/embed/hostEventClient/host-event-client.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.js +17 -11
- package/lib/src/embed/hostEventClient/host-event-client.js.map +1 -1
- package/lib/src/embed/hostEventClient/host-event-client.spec.js +14 -8
- package/lib/src/embed/hostEventClient/host-event-client.spec.js.map +1 -1
- package/lib/src/embed/hostEventClient/utils.d.ts +9 -0
- package/lib/src/embed/hostEventClient/utils.d.ts.map +1 -1
- package/lib/src/embed/hostEventClient/utils.js +47 -2
- package/lib/src/embed/hostEventClient/utils.js.map +1 -1
- package/lib/src/embed/hostEventClient/utils.spec.js +152 -1
- package/lib/src/embed/hostEventClient/utils.spec.js.map +1 -1
- package/lib/src/embed/liveboard.d.ts +4 -3
- package/lib/src/embed/liveboard.d.ts.map +1 -1
- package/lib/src/embed/liveboard.js +18 -13
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/liveboard.spec.js +2 -1
- package/lib/src/embed/liveboard.spec.js.map +1 -1
- package/lib/src/embed/search-bar.js +1 -1
- package/lib/src/embed/search-bar.js.map +1 -1
- package/lib/src/embed/search.js +1 -1
- package/lib/src/embed/search.js.map +1 -1
- package/lib/src/embed/search.spec.js +20 -0
- package/lib/src/embed/search.spec.js.map +1 -1
- package/lib/src/embed/spotter-utils.d.ts +8 -1
- package/lib/src/embed/spotter-utils.d.ts.map +1 -1
- package/lib/src/embed/spotter-utils.js +12 -0
- package/lib/src/embed/spotter-utils.js.map +1 -1
- package/lib/src/embed/spotter-utils.spec.js +27 -1
- package/lib/src/embed/spotter-utils.spec.js.map +1 -1
- package/lib/src/embed/ts-embed.d.ts +3 -0
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +108 -29
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/embed/ts-embed.spec.js +164 -24
- package/lib/src/embed/ts-embed.spec.js.map +1 -1
- package/lib/src/errors.js +1 -1
- package/lib/src/errors.js.map +1 -1
- package/lib/src/index.d.ts +2 -1
- package/lib/src/index.d.ts.map +1 -1
- package/lib/src/index.js +2 -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 +1 -1
- package/lib/src/react/all-types-export.js.map +1 -1
- package/lib/src/types.d.ts +45 -22
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +44 -21
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/authService/tokenizedAuthService.spec.js +2 -1
- package/lib/src/utils/authService/tokenizedAuthService.spec.js.map +1 -1
- package/lib/src/utils/custom-actions.spec.js +4 -2
- package/lib/src/utils/custom-actions.spec.js.map +1 -1
- package/lib/src/utils/filterConverter.d.ts +106 -0
- package/lib/src/utils/filterConverter.d.ts.map +1 -0
- package/lib/src/utils/filterConverter.js +210 -0
- package/lib/src/utils/filterConverter.js.map +1 -0
- package/lib/src/utils/filterConverter.spec.d.ts +2 -0
- package/lib/src/utils/filterConverter.spec.d.ts.map +1 -0
- package/lib/src/utils/filterConverter.spec.js +707 -0
- package/lib/src/utils/filterConverter.spec.js.map +1 -0
- package/lib/src/utils/processData.spec.js +2 -1
- package/lib/src/utils/processData.spec.js.map +1 -1
- package/lib/src/visual-embed-sdk.d.ts +202 -40
- package/package.json +11 -4
- package/src/contracts/host-event-contracts.ts +55 -11
- package/src/embed/app.spec.ts +15 -0
- package/src/embed/app.ts +5 -4
- package/src/embed/auto-frame-renderer.spec.ts +24 -13
- package/src/embed/auto-frame-renderer.ts +1 -1
- package/src/embed/bodyless-conversation.ts +1 -1
- package/src/embed/conversation.spec.ts +38 -27
- package/src/embed/conversation.ts +5 -5
- package/src/embed/embedConfig.spec.ts +2 -1
- package/src/embed/hostEventClient/host-event-client.spec.ts +14 -8
- package/src/embed/hostEventClient/host-event-client.ts +18 -10
- package/src/embed/hostEventClient/utils.spec.ts +176 -1
- package/src/embed/hostEventClient/utils.ts +51 -2
- package/src/embed/liveboard.spec.ts +4 -3
- package/src/embed/liveboard.ts +22 -16
- package/src/embed/search-bar.tsx +1 -1
- package/src/embed/search.spec.ts +23 -0
- package/src/embed/search.ts +1 -1
- package/src/embed/spotter-utils.spec.ts +29 -0
- package/src/embed/spotter-utils.ts +16 -0
- package/src/embed/ts-embed.spec.ts +204 -23
- package/src/embed/ts-embed.ts +114 -28
- package/src/errors.ts +1 -1
- package/src/index.ts +10 -0
- package/src/react/all-types-export.ts +4 -0
- package/src/types.ts +44 -21
- package/src/utils/authService/tokenizedAuthService.spec.ts +2 -1
- package/src/utils/custom-actions.spec.ts +4 -2
- package/src/utils/filterConverter.spec.ts +801 -0
- package/src/utils/filterConverter.ts +304 -0
- package/src/utils/processData.spec.ts +2 -1
package/src/embed/app.ts
CHANGED
|
@@ -608,9 +608,10 @@ export interface AppViewConfig extends AllEmbedViewConfig, FullHeightViewConfig
|
|
|
608
608
|
isPNGInScheduledEmailsEnabled?: boolean;
|
|
609
609
|
|
|
610
610
|
/**
|
|
611
|
-
* Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is
|
|
612
|
-
* following the exact UI layout, instead of splitting
|
|
613
|
-
* This feature is GA from SDK version
|
|
611
|
+
* Enables the 'what you see is what you get' PDF export for Liveboards. Each tab is
|
|
612
|
+
* rendered on a single page following the exact UI layout, instead of splitting
|
|
613
|
+
* visualizations across multiple A4 pages. This feature is GA from SDK version
|
|
614
|
+
* 1.51.0 and ThoughtSpot version 26.8.0.cl.
|
|
614
615
|
*
|
|
615
616
|
* Supported embed types: `AppEmbed`, `LiveboardEmbed`
|
|
616
617
|
* @type {boolean}
|
|
@@ -990,7 +991,7 @@ export class AppEmbed extends V1Embed {
|
|
|
990
991
|
* embedded Liveboard or visualization.
|
|
991
992
|
*/
|
|
992
993
|
protected getEmbedParams() {
|
|
993
|
-
const params = this.
|
|
994
|
+
const params = this.getUrlQueryParamsObject();
|
|
994
995
|
return getQueryParamString(params, true);
|
|
995
996
|
}
|
|
996
997
|
|
|
@@ -45,7 +45,8 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
45
45
|
getEmbedBasePathSpy.mockRestore();
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
// ─── helpers
|
|
48
|
+
// ─── helpers
|
|
49
|
+
// ──────────────────────────────────────────────────────────────
|
|
49
50
|
|
|
50
51
|
/** Capture the src passed to renderIFrame for the first tsmcp iframe added */
|
|
51
52
|
async function captureRenderedSrc(viewConfig: AutoMCPFrameRendererViewConfig = {}): Promise<string> {
|
|
@@ -96,7 +97,8 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
96
97
|
return mockPort.postMessage.mock.calls[0]?.[0];
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
// ─── MutationObserver setup
|
|
100
|
+
// ─── MutationObserver setup
|
|
101
|
+
// ───────────────────────────────────────────────
|
|
100
102
|
|
|
101
103
|
describe('MutationObserver setup', () => {
|
|
102
104
|
test('should return a MutationObserver', () => {
|
|
@@ -117,7 +119,8 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
117
119
|
});
|
|
118
120
|
});
|
|
119
121
|
|
|
120
|
-
// ─── iframe detection via tsmcp param
|
|
122
|
+
// ─── iframe detection via tsmcp param
|
|
123
|
+
// ─────────────────────────────────────
|
|
121
124
|
|
|
122
125
|
describe('iframe detection via tsmcp param', () => {
|
|
123
126
|
test('should process directly-added iframes with tsmcp=true', async () => {
|
|
@@ -209,7 +212,8 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
209
212
|
});
|
|
210
213
|
});
|
|
211
214
|
|
|
212
|
-
// ─── handleInsertionIntoDOM override
|
|
215
|
+
// ─── handleInsertionIntoDOM override
|
|
216
|
+
// ──────────────────────────────────────
|
|
213
217
|
|
|
214
218
|
describe('handleInsertionIntoDOM override', () => {
|
|
215
219
|
test('should replace the original iframe when renderIFrame inserts DOM', async () => {
|
|
@@ -233,7 +237,8 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
233
237
|
});
|
|
234
238
|
});
|
|
235
239
|
|
|
236
|
-
// ─── URL params forwarding
|
|
240
|
+
// ─── URL params forwarding
|
|
241
|
+
// ────────────────────────────────────────────────
|
|
237
242
|
|
|
238
243
|
describe('URL params forwarding', () => {
|
|
239
244
|
test('should accept empty viewConfig', () => {
|
|
@@ -394,7 +399,8 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
394
399
|
});
|
|
395
400
|
});
|
|
396
401
|
|
|
397
|
-
// ─── frameParams forwarding
|
|
402
|
+
// ─── frameParams forwarding
|
|
403
|
+
// ───────────────────────────────────────────────
|
|
398
404
|
|
|
399
405
|
describe('frameParams forwarding', () => {
|
|
400
406
|
test('frameParams.height and .width applied to the replacement iframe element', async () => {
|
|
@@ -441,7 +447,8 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
441
447
|
});
|
|
442
448
|
});
|
|
443
449
|
|
|
444
|
-
// ─── APP_INIT postMessage params forwarding
|
|
450
|
+
// ─── APP_INIT postMessage params forwarding
|
|
451
|
+
// ───────────────────────────────
|
|
445
452
|
//
|
|
446
453
|
// These params are not in the iframe src URL — they travel via the APP_INIT
|
|
447
454
|
// postMessage channel. AutoFrameRenderer inherits the TsEmbed APP_INIT
|
|
@@ -497,9 +504,10 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
497
504
|
});
|
|
498
505
|
|
|
499
506
|
test('interceptUrls forwarded via APP_INIT', async () => {
|
|
500
|
-
// The SDK expands InterceptedApiType enum values into resolved
|
|
501
|
-
// Assert that the interceptUrls array is
|
|
502
|
-
//
|
|
507
|
+
// The SDK expands InterceptedApiType enum values into resolved
|
|
508
|
+
// prism endpoint URLs. Assert that the interceptUrls array is
|
|
509
|
+
// non-empty (i.e. the config was forwarded and processed) rather
|
|
510
|
+
// than checking the resolved strings directly.
|
|
503
511
|
const response = await getAppInitResponse({
|
|
504
512
|
interceptUrls: [InterceptedApiType.AnswerData],
|
|
505
513
|
});
|
|
@@ -513,7 +521,8 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
513
521
|
});
|
|
514
522
|
});
|
|
515
523
|
|
|
516
|
-
// ─── getMCPIframeSrc URL construction
|
|
524
|
+
// ─── getMCPIframeSrc URL construction
|
|
525
|
+
// ─────────────────────────────────────
|
|
517
526
|
|
|
518
527
|
describe('getMCPIframeSrc URL construction', () => {
|
|
519
528
|
test('should strip tsmcp param and merge embed params into rendered src', async () => {
|
|
@@ -569,7 +578,8 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
569
578
|
document.body.appendChild(iframe);
|
|
570
579
|
await new Promise((r) => setTimeout(r, 50));
|
|
571
580
|
|
|
572
|
-
// At minimum the base embed params (hostAppUrl, sdkVersion, etc.)
|
|
581
|
+
// At minimum the base embed params (hostAppUrl, sdkVersion, etc.)
|
|
582
|
+
// are always present
|
|
573
583
|
expect(capturedSrc).toMatch(/\?[^#]+#/);
|
|
574
584
|
observer.disconnect();
|
|
575
585
|
});
|
|
@@ -594,7 +604,8 @@ describe('startAutoMCPFrameRenderer', () => {
|
|
|
594
604
|
});
|
|
595
605
|
});
|
|
596
606
|
|
|
597
|
-
// ─── URL serialization parity with normal embeds
|
|
607
|
+
// ─── URL serialization parity with normal embeds
|
|
608
|
+
// ──────────────────────────
|
|
598
609
|
//
|
|
599
610
|
// These tests verify that array-typed params (hideAction, disableAction,
|
|
600
611
|
// visibleAction) are serialized identically by startAutoMCPFrameRenderer
|
|
@@ -108,7 +108,7 @@ class AutoFrameRenderer extends TsEmbed {
|
|
|
108
108
|
* @returns The constructed URL to use for the ThoughtSpot embed iframe.
|
|
109
109
|
*/
|
|
110
110
|
private getMCPIframeSrc(sourceSrc: string) {
|
|
111
|
-
const queryParams = this.
|
|
111
|
+
const queryParams = this.getUrlQueryParamsObject();
|
|
112
112
|
const sourceURL = new URL(sourceSrc);
|
|
113
113
|
const existingQueryParams = sourceURL.searchParams;
|
|
114
114
|
const existingQueryParamsObject = Object.fromEntries(existingQueryParams);
|
|
@@ -56,7 +56,7 @@ export class ConversationMessage extends TsEmbed {
|
|
|
56
56
|
messageId,
|
|
57
57
|
} = this.viewConfig;
|
|
58
58
|
const path = 'conv-assist-answer';
|
|
59
|
-
const queryParams = this.
|
|
59
|
+
const queryParams = this.getUrlQueryParamsObject();
|
|
60
60
|
|
|
61
61
|
let query = '';
|
|
62
62
|
const queryParamsString = getQueryParamString(queryParams, true);
|
|
@@ -537,7 +537,7 @@ describe('ConversationEmbed', () => {
|
|
|
537
537
|
);
|
|
538
538
|
});
|
|
539
539
|
|
|
540
|
-
it('should
|
|
540
|
+
it('should not add analystId to the url, it is sent over APP_INIT instead', async () => {
|
|
541
541
|
const viewConfig: SpotterEmbedViewConfig = {
|
|
542
542
|
worksheetId: 'worksheetId',
|
|
543
543
|
searchOptions: {
|
|
@@ -549,37 +549,13 @@ describe('ConversationEmbed', () => {
|
|
|
549
549
|
};
|
|
550
550
|
const conversationEmbed = new SpotterEmbed(getRootEl(), viewConfig);
|
|
551
551
|
await conversationEmbed.render();
|
|
552
|
+
expect(getIFrameSrc()).not.toContain('analystId');
|
|
552
553
|
expectUrlMatchesWithParams(
|
|
553
554
|
getIFrameSrc(),
|
|
554
|
-
`http://${thoughtSpotHost}/v2/?${defaultParams}&isSpotterExperienceEnabled=true
|
|
555
|
+
`http://${thoughtSpotHost}/v2/?${defaultParams}&isSpotterExperienceEnabled=true#/embed/insights/conv-assist?worksheet=worksheetId&query=searchQuery`,
|
|
555
556
|
);
|
|
556
557
|
});
|
|
557
558
|
|
|
558
|
-
it('should not add analystId to the url when spotterAnalystConfig is not provided', async () => {
|
|
559
|
-
const viewConfig: SpotterEmbedViewConfig = {
|
|
560
|
-
worksheetId: 'worksheetId',
|
|
561
|
-
searchOptions: {
|
|
562
|
-
searchQuery: 'searchQuery',
|
|
563
|
-
},
|
|
564
|
-
};
|
|
565
|
-
const conversationEmbed = new SpotterEmbed(getRootEl(), viewConfig);
|
|
566
|
-
await conversationEmbed.render();
|
|
567
|
-
expect(getIFrameSrc()).not.toContain('analystId');
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
it('should not add analystId to the url when spotterAnalystConfig is empty', async () => {
|
|
571
|
-
const viewConfig: SpotterEmbedViewConfig = {
|
|
572
|
-
worksheetId: 'worksheetId',
|
|
573
|
-
searchOptions: {
|
|
574
|
-
searchQuery: 'searchQuery',
|
|
575
|
-
},
|
|
576
|
-
spotterAnalystConfig: {},
|
|
577
|
-
};
|
|
578
|
-
const conversationEmbed = new SpotterEmbed(getRootEl(), viewConfig);
|
|
579
|
-
await conversationEmbed.render();
|
|
580
|
-
expect(getIFrameSrc()).not.toContain('analystId');
|
|
581
|
-
});
|
|
582
|
-
|
|
583
559
|
describe('spotter chat hiddenActions', () => {
|
|
584
560
|
it.each([
|
|
585
561
|
['SpotterChatConnectorResources', Action.SpotterChatConnectorResources],
|
|
@@ -1057,4 +1033,39 @@ describe('SpotterEmbed APP_INIT starterPrompts', () => {
|
|
|
1057
1033
|
quick: { questions: [{ label: 'Q1', prompt: 'P1' }] },
|
|
1058
1034
|
});
|
|
1059
1035
|
});
|
|
1036
|
+
|
|
1037
|
+
it('should include spotterAnalystConfig in embedParams when analystId is provided', async () => {
|
|
1038
|
+
const response = await getAppInitResponse({
|
|
1039
|
+
worksheetId: 'ws1',
|
|
1040
|
+
spotterAnalystConfig: { analystId: 'analyst-id-1234' },
|
|
1041
|
+
});
|
|
1042
|
+
expect(response.data.embedParams.spotterAnalystConfig).toEqual({
|
|
1043
|
+
analystId: 'analyst-id-1234',
|
|
1044
|
+
});
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
it('should include spotterAnalystConfig alongside the other spotter embedParams', async () => {
|
|
1048
|
+
const response = await getAppInitResponse({
|
|
1049
|
+
worksheetId: 'ws1',
|
|
1050
|
+
spotterSidebarConfig: { enablePastConversationsSidebar: true },
|
|
1051
|
+
spotterAnalystConfig: { analystId: 'analyst-id-1234' },
|
|
1052
|
+
});
|
|
1053
|
+
expect(response.data.embedParams.spotterSidebarConfig.enablePastConversationsSidebar).toBe(true);
|
|
1054
|
+
expect(response.data.embedParams.spotterAnalystConfig).toEqual({
|
|
1055
|
+
analystId: 'analyst-id-1234',
|
|
1056
|
+
});
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
it('should not include spotterAnalystConfig in embedParams when not configured', async () => {
|
|
1060
|
+
const response = await getAppInitResponse({ worksheetId: 'ws1' });
|
|
1061
|
+
expect(response.data.embedParams?.spotterAnalystConfig).toBeUndefined();
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
it('should not include spotterAnalystConfig in embedParams when analystId is absent', async () => {
|
|
1065
|
+
const response = await getAppInitResponse({
|
|
1066
|
+
worksheetId: 'ws1',
|
|
1067
|
+
spotterAnalystConfig: {},
|
|
1068
|
+
});
|
|
1069
|
+
expect(response.data.embedParams?.spotterAnalystConfig).toBeUndefined();
|
|
1070
|
+
});
|
|
1060
1071
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Param, BaseViewConfig, RuntimeFilter, RuntimeParameter, ErrorDetailsTypes, EmbedErrorCodes, DefaultAppInitData, VisualizationOverrides, SpotterFileUploadFileTypes } from '../types';
|
|
2
2
|
import { TsEmbed } from './ts-embed';
|
|
3
|
-
import { buildSpotterSidebarAppInitData, buildSpotterShareConversationAppInitData, buildStarterPromptsAppInitData } from './spotter-utils';
|
|
3
|
+
import { buildSpotterSidebarAppInitData, buildSpotterShareConversationAppInitData, buildStarterPromptsAppInitData, buildSpotterAnalystAppInitData } from './spotter-utils';
|
|
4
4
|
import { getQueryParamString, getFilterQuery, getRuntimeParameters, setParamIfDefined } from '../utils';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -801,6 +801,7 @@ export interface SpotterAppInitData extends DefaultAppInitData {
|
|
|
801
801
|
spotterShareConversationConfig?: SpotterShareConversationConfig;
|
|
802
802
|
visualOverridesParams?: VisualizationOverrides | null;
|
|
803
803
|
starterPrompts?: StarterPromptsConfig;
|
|
804
|
+
spotterAnalystConfig?: SpotterAnalystConfig;
|
|
804
805
|
};
|
|
805
806
|
}
|
|
806
807
|
|
|
@@ -850,7 +851,8 @@ export class SpotterEmbed extends TsEmbed {
|
|
|
850
851
|
this.handleError.bind(this),
|
|
851
852
|
);
|
|
852
853
|
const shareInitData = buildSpotterShareConversationAppInitData(sidebarInitData, this.viewConfig);
|
|
853
|
-
|
|
854
|
+
const starterPromptsInitData = buildStarterPromptsAppInitData(shareInitData, this.viewConfig);
|
|
855
|
+
return buildSpotterAnalystAppInitData(starterPromptsInitData, this.viewConfig);
|
|
854
856
|
}
|
|
855
857
|
|
|
856
858
|
protected getEmbedParamsObject() {
|
|
@@ -871,7 +873,6 @@ export class SpotterEmbed extends TsEmbed {
|
|
|
871
873
|
defaultQueryMode,
|
|
872
874
|
enableStopAnswerGenerationEmbed,
|
|
873
875
|
spotterChatConfig,
|
|
874
|
-
spotterAnalystConfig,
|
|
875
876
|
} = this.viewConfig;
|
|
876
877
|
|
|
877
878
|
const queryParams = this.getBaseQueryParams();
|
|
@@ -887,7 +888,6 @@ export class SpotterEmbed extends TsEmbed {
|
|
|
887
888
|
setParamIfDefined(queryParams, Param.ShowSpotterRadiance, showSpotterRadiance, true);
|
|
888
889
|
setParamIfDefined(queryParams, Param.DefaultQueryMode, defaultQueryMode);
|
|
889
890
|
setParamIfDefined(queryParams, Param.EnableStopAnswerGenerationEmbed, enableStopAnswerGenerationEmbed, true);
|
|
890
|
-
setParamIfDefined(queryParams, Param.AnalystId, spotterAnalystConfig?.analystId);
|
|
891
891
|
|
|
892
892
|
// Handle spotterChatConfig params
|
|
893
893
|
if (spotterChatConfig) {
|
|
@@ -931,7 +931,7 @@ export class SpotterEmbed extends TsEmbed {
|
|
|
931
931
|
const path = sharedConversationId
|
|
932
932
|
? `insights/conv-assist/s/${encodeURIComponent(sharedConversationId)}`
|
|
933
933
|
: 'insights/conv-assist';
|
|
934
|
-
const queryParams = this.
|
|
934
|
+
const queryParams = this.getUrlQueryParamsObject();
|
|
935
935
|
|
|
936
936
|
let query = '';
|
|
937
937
|
const queryParamsString = getQueryParamString(queryParams, true);
|
|
@@ -3,7 +3,8 @@ import { storeValueInWindow, resetValueFromWindow } from '../utils';
|
|
|
3
3
|
|
|
4
4
|
describe('embedConfig', () => {
|
|
5
5
|
beforeEach(() => {
|
|
6
|
-
// Ensure the SDK window namespace exists before resetting individual
|
|
6
|
+
// Ensure the SDK window namespace exists before resetting individual
|
|
7
|
+
// keys
|
|
7
8
|
storeValueInWindow('embedConfig', undefined);
|
|
8
9
|
resetValueFromWindow('embedConfig');
|
|
9
10
|
});
|
|
@@ -979,11 +979,14 @@ describe('HostEventClient', () => {
|
|
|
979
979
|
const { client } = createHostEventClient();
|
|
980
980
|
mockProcessTrigger.mockReset();
|
|
981
981
|
|
|
982
|
-
// Flow per triggerHostEvent call when GetAvailableUIPassthroughs
|
|
983
|
-
//
|
|
984
|
-
//
|
|
985
|
-
//
|
|
986
|
-
//
|
|
982
|
+
// Flow per triggerHostEvent call when GetAvailableUIPassthroughs
|
|
983
|
+
// rejects: 1. getAvailableUIPassthroughKeys →
|
|
984
|
+
// processTrigger(UIPassthrough, GetAvailableUIPassthroughs) →
|
|
985
|
+
// rejects → returns [] 2. keys=[] so getDataWithPassthroughFallback
|
|
986
|
+
// is called → triggerUIPassthroughApi(GetAnswerSession) →
|
|
987
|
+
// processTrigger(UIPassthrough) → null → hostEventFallback called
|
|
988
|
+
// 3. hostEventFallback → processTrigger(GetAnswerSession) → legacy
|
|
989
|
+
// result
|
|
987
990
|
|
|
988
991
|
// First triggerHostEvent: 3 calls
|
|
989
992
|
mockProcessTrigger.mockRejectedValueOnce(new Error('network error')); // call 1
|
|
@@ -992,14 +995,16 @@ describe('HostEventClient', () => {
|
|
|
992
995
|
|
|
993
996
|
await client.triggerHostEvent(HostEvent.GetAnswerSession, { vizId: '1' });
|
|
994
997
|
|
|
995
|
-
// Second triggerHostEvent: cache was NOT set, so
|
|
998
|
+
// Second triggerHostEvent: cache was NOT set, so
|
|
999
|
+
// GetAvailableUIPassthroughs is retried: 3 more calls
|
|
996
1000
|
mockProcessTrigger.mockRejectedValueOnce(new Error('network error')); // call 4
|
|
997
1001
|
mockProcessTrigger.mockResolvedValueOnce(null); // call 5
|
|
998
1002
|
mockProcessTrigger.mockResolvedValueOnce({ session: 'leg2' }); // call 6
|
|
999
1003
|
|
|
1000
1004
|
await client.triggerHostEvent(HostEvent.GetAnswerSession, { vizId: '2' });
|
|
1001
1005
|
|
|
1002
|
-
// 6 total calls: GetAvailableUIPassthroughs was called TWICE
|
|
1006
|
+
// 6 total calls: GetAvailableUIPassthroughs was called TWICE
|
|
1007
|
+
// (cache not set after catch)
|
|
1003
1008
|
expect(mockProcessTrigger).toHaveBeenCalledTimes(6);
|
|
1004
1009
|
const availableCallsCount = mockProcessTrigger.mock.calls.filter(
|
|
1005
1010
|
(call) => call[3]?.type === UIPassthroughEvent.GetAvailableUIPassthroughs,
|
|
@@ -1175,7 +1180,8 @@ describe('HostEventClient', () => {
|
|
|
1175
1180
|
{ numberOfTabs: number; orderedTabIds: string[] }
|
|
1176
1181
|
> = true;
|
|
1177
1182
|
const probeUnknownField = (r: TabsResponse) =>
|
|
1178
|
-
// @ts-expect-error — field not on the GetTabs contract (was
|
|
1183
|
+
// @ts-expect-error — field not on the GetTabs contract (was
|
|
1184
|
+
// silent when `any`)
|
|
1179
1185
|
r.tabCount;
|
|
1180
1186
|
void probeUnknownField;
|
|
1181
1187
|
|
|
@@ -3,6 +3,7 @@ import { processTrigger as processTriggerService } from '../../utils/processTrig
|
|
|
3
3
|
import { getEmbedConfig } from '../embedConfig';
|
|
4
4
|
import {
|
|
5
5
|
isValidUpdateFiltersPayload,
|
|
6
|
+
resolveUpdateFiltersAliases,
|
|
6
7
|
isValidUpdateParametersPayload,
|
|
7
8
|
isValidDrillDownPayload,
|
|
8
9
|
throwUpdateFiltersValidationError,
|
|
@@ -27,7 +28,8 @@ import {
|
|
|
27
28
|
/**
|
|
28
29
|
* Maps HostEvent to its corresponding UIPassthroughEvent.
|
|
29
30
|
* Includes both custom-handler events (Pin, SaveAnswer, UpdateFilters, DrillDown)
|
|
30
|
-
* and getter events (GetAnswerSession, GetFilters, etc.) that use
|
|
31
|
+
* and getter events (GetAnswerSession, GetFilters, etc.) that use
|
|
32
|
+
* getDataWithPassthroughFallback.
|
|
31
33
|
*/
|
|
32
34
|
const PASSTHROUGH_MAP: Partial<Record<HostEvent, UIPassthroughEvent>> = {
|
|
33
35
|
// Custom handlers (setters with special logic)
|
|
@@ -249,13 +251,17 @@ export class HostEventClient {
|
|
|
249
251
|
}
|
|
250
252
|
|
|
251
253
|
// The shared contract accepts both current (columnName/operator) and
|
|
252
|
-
// legacy (column/oper) filter field names
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
//
|
|
254
|
+
// legacy (column/oper) filter field names, but only the legacy pair is
|
|
255
|
+
// understood downstream, so the payload is rewritten before it is
|
|
256
|
+
// forwarded — otherwise a columnName/operator filter passes validation
|
|
257
|
+
// here and is then silently ignored by the app. The cast bridges the
|
|
258
|
+
// UIPassthrough FilterUpdate type, which still declares only the legacy
|
|
259
|
+
// names.
|
|
260
|
+
const resolvedPayload = resolveUpdateFiltersAliases(payload);
|
|
261
|
+
|
|
256
262
|
return this.handleHostEventWithParam(
|
|
257
263
|
UIPassthroughEvent.UpdateFilters,
|
|
258
|
-
|
|
264
|
+
resolvedPayload as UIPassthroughRequest<UIPassthroughEvent.UpdateFilters>,
|
|
259
265
|
context as ContextType,
|
|
260
266
|
);
|
|
261
267
|
}
|
|
@@ -268,7 +274,8 @@ export class HostEventClient {
|
|
|
268
274
|
throwUpdateParametersValidationError();
|
|
269
275
|
}
|
|
270
276
|
|
|
271
|
-
// UpdateParameters has no UI passthrough contract; dispatch over the
|
|
277
|
+
// UpdateParameters has no UI passthrough contract; dispatch over the
|
|
278
|
+
// legacy channel
|
|
272
279
|
return this.hostEventFallback(HostEvent.UpdateParameters, payload, context);
|
|
273
280
|
}
|
|
274
281
|
|
|
@@ -285,8 +292,8 @@ export class HostEventClient {
|
|
|
285
292
|
|
|
286
293
|
/**
|
|
287
294
|
* Dispatches a host event using the appropriate channel:
|
|
288
|
-
* 1. If the embedded app supports UI passthrough for this event, use it (custom
|
|
289
|
-
* 2. Otherwise fall back to the legacy host event channel.
|
|
295
|
+
* 1. If the embedded app supports UI passthrough for this event, use it (custom
|
|
296
|
+
* handler or getter). 2. Otherwise fall back to the legacy host event channel.
|
|
290
297
|
*
|
|
291
298
|
* @param hostEvent - The host event to trigger
|
|
292
299
|
* @param payload - Optional payload for the event
|
|
@@ -304,7 +311,8 @@ export class HostEventClient {
|
|
|
304
311
|
const customHandler = this.customHandlers[hostEvent];
|
|
305
312
|
const passthroughEvent = PASSTHROUGH_MAP[hostEvent];
|
|
306
313
|
|
|
307
|
-
// If embedded app supports passthrough but not this event, use legacy
|
|
314
|
+
// If embedded app supports passthrough but not this event, use legacy
|
|
315
|
+
// channel
|
|
308
316
|
const keys = passthroughEvent ? await this.getAvailableUIPassthroughKeys(context as ContextType) : [];
|
|
309
317
|
if (passthroughEvent && keys.length > 0 && !keys.includes(passthroughEvent)) {
|
|
310
318
|
return this.hostEventFallback(hostEvent, payload, context) as any;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isValidUpdateFiltersPayload,
|
|
3
|
+
resolveUpdateFiltersAliases,
|
|
3
4
|
isValidUpdateParametersPayload,
|
|
4
5
|
isValidDrillDownPayload,
|
|
5
6
|
createValidationError,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
throwUpdateParametersValidationError,
|
|
8
9
|
throwDrillDownValidationError,
|
|
9
10
|
} from './utils';
|
|
11
|
+
import { HostFilterUpdate as FilterUpdate } from '../../contracts/host-event-contracts';
|
|
10
12
|
import { ERROR_MESSAGE } from '../../errors';
|
|
11
13
|
import { EmbedEvent } from '../../types';
|
|
12
14
|
import { embedEventStatus } from '../../utils';
|
|
@@ -426,4 +428,177 @@ describe('hostEventClient utils', () => {
|
|
|
426
428
|
.toThrow(ERROR_MESSAGE.DRILLDOWN_INVALID_PAYLOAD);
|
|
427
429
|
});
|
|
428
430
|
});
|
|
429
|
-
|
|
431
|
+
// =========================
|
|
432
|
+
// FilterUpdate type shape
|
|
433
|
+
// =========================
|
|
434
|
+
// The @ts-expect-error assertions below are enforced by tsc: an unused one
|
|
435
|
+
// is itself an error, so re-loosening the type fails the build.
|
|
436
|
+
describe('FilterUpdate type', () => {
|
|
437
|
+
it('accepts the documented columnName/operator spelling', () => {
|
|
438
|
+
const filter: FilterUpdate = { columnName: 'region', operator: 'EQ', values: ['west'] };
|
|
439
|
+
expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('still accepts the deprecated column/oper aliases', () => {
|
|
443
|
+
const filter: FilterUpdate = { column: 'region', oper: 'EQ', values: ['west'] };
|
|
444
|
+
expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('accepts a mix of documented and deprecated spellings', () => {
|
|
448
|
+
const filter: FilterUpdate = { columnName: 'region', oper: 'EQ', values: ['west'] };
|
|
449
|
+
expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('accepts a worksheet-qualified column name', () => {
|
|
453
|
+
const filter: FilterUpdate = {
|
|
454
|
+
columnName: '(Sample) Retail - Apparel::city',
|
|
455
|
+
operator: 'IN',
|
|
456
|
+
values: ['atlanta'],
|
|
457
|
+
};
|
|
458
|
+
expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('rejects a filter with no column at compile time and at runtime', () => {
|
|
462
|
+
// @ts-expect-error one of columnName/column is required
|
|
463
|
+
const filter: FilterUpdate = { operator: 'EQ', values: ['west'] };
|
|
464
|
+
expect(isValidUpdateFiltersPayload({ filter })).toBe(false);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('rejects a filter with no operator at compile time and at runtime', () => {
|
|
468
|
+
// @ts-expect-error one of operator/oper is required
|
|
469
|
+
const filter: FilterUpdate = { columnName: 'region', values: ['west'] };
|
|
470
|
+
expect(isValidUpdateFiltersPayload({ filter })).toBe(false);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('rejects both column spellings at compile time', () => {
|
|
474
|
+
// @ts-expect-error columnName and column are mutually exclusive
|
|
475
|
+
const filter: FilterUpdate = {
|
|
476
|
+
columnName: 'region', column: 'region', operator: 'EQ', values: ['west'],
|
|
477
|
+
};
|
|
478
|
+
// The runtime validator is laxer than the type - it reads whichever
|
|
479
|
+
// spelling is present - so this still passes at runtime. The point
|
|
480
|
+
// of the type is to stop the ambiguity reaching it.
|
|
481
|
+
expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('rejects both operator spellings at compile time', () => {
|
|
485
|
+
// @ts-expect-error operator and oper are mutually exclusive
|
|
486
|
+
const filter: FilterUpdate = {
|
|
487
|
+
columnName: 'region', operator: 'EQ', oper: 'EQ', values: ['west'],
|
|
488
|
+
};
|
|
489
|
+
expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// The fields SCAL-325540 added to the host-event contract have to keep
|
|
493
|
+
// working alongside the exclusive alias pairs - the union must widen
|
|
494
|
+
// the column and operator halves only, and leave the rest of the
|
|
495
|
+
// filter shape alone.
|
|
496
|
+
it('accepts the fields added by the host-event contract', () => {
|
|
497
|
+
const filter: FilterUpdate = {
|
|
498
|
+
columnName: 'date',
|
|
499
|
+
columnId: 'col-guid',
|
|
500
|
+
operator: 'EQ',
|
|
501
|
+
values: ['2023-07-31'],
|
|
502
|
+
type: 'EXACT_DATE',
|
|
503
|
+
datePeriod: 'DAY',
|
|
504
|
+
negate: true,
|
|
505
|
+
applicability: { level: 'TAB', targetId: 'tab-guid' },
|
|
506
|
+
};
|
|
507
|
+
expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('accepts non-string values alongside the documented spelling', () => {
|
|
511
|
+
const filter: FilterUpdate = {
|
|
512
|
+
columnName: 'quantity', operator: 'GE', values: [5, true],
|
|
513
|
+
};
|
|
514
|
+
expect(isValidUpdateFiltersPayload({ filter })).toBe(true);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('accepts the request-level scoping fields', () => {
|
|
518
|
+
const payload = {
|
|
519
|
+
filters: [{ columnName: 'region', operator: 'IN', values: ['west'] }],
|
|
520
|
+
visualizationId: 'viz-guid',
|
|
521
|
+
liveboardId: 'lb-guid',
|
|
522
|
+
};
|
|
523
|
+
expect(isValidUpdateFiltersPayload(payload)).toBe(true);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
describe('resolveUpdateFiltersAliases', () => {
|
|
527
|
+
it('rewrites columnName/operator to column/oper on a single filter', () => {
|
|
528
|
+
const out = resolveUpdateFiltersAliases({
|
|
529
|
+
filter: { columnName: 'region', operator: 'EQ', values: ['west'] },
|
|
530
|
+
} as any);
|
|
531
|
+
|
|
532
|
+
expect(out.filter).toEqual({ column: 'region', oper: 'EQ', values: ['west'] });
|
|
533
|
+
expect(out.filter).not.toHaveProperty('columnName');
|
|
534
|
+
expect(out.filter).not.toHaveProperty('operator');
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('rewrites every entry in a filters array', () => {
|
|
538
|
+
const out = resolveUpdateFiltersAliases({
|
|
539
|
+
filters: [
|
|
540
|
+
{ columnName: 'region', operator: 'EQ', values: ['west'] },
|
|
541
|
+
{ columnName: 'item type', operator: 'IN', values: ['bags'] },
|
|
542
|
+
],
|
|
543
|
+
} as any);
|
|
544
|
+
|
|
545
|
+
expect(out.filters).toEqual([
|
|
546
|
+
{ column: 'region', oper: 'EQ', values: ['west'] },
|
|
547
|
+
{ column: 'item type', oper: 'IN', values: ['bags'] },
|
|
548
|
+
]);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('leaves a payload that already uses column/oper untouched', () => {
|
|
552
|
+
const payload = { filter: { column: 'region', oper: 'EQ', values: ['west'] } } as any;
|
|
553
|
+
|
|
554
|
+
expect(resolveUpdateFiltersAliases(payload)).toEqual(payload);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('prefers columnName/operator when both spellings are present', () => {
|
|
558
|
+
const out = resolveUpdateFiltersAliases({
|
|
559
|
+
filter: {
|
|
560
|
+
column: 'from-column', columnName: 'from-columnName',
|
|
561
|
+
oper: 'EQ', operator: 'IN',
|
|
562
|
+
values: ['x'],
|
|
563
|
+
},
|
|
564
|
+
} as any);
|
|
565
|
+
|
|
566
|
+
// The value survives under the wire name, but it is the documented
|
|
567
|
+
// spelling's value that survives.
|
|
568
|
+
expect(out.filter).toEqual({ column: 'from-columnName', oper: 'IN', values: ['x'] });
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('preserves the other filter fields, including applicability', () => {
|
|
572
|
+
const out = resolveUpdateFiltersAliases({
|
|
573
|
+
filter: {
|
|
574
|
+
columnName: 'date',
|
|
575
|
+
operator: 'EQ',
|
|
576
|
+
values: [1700000000],
|
|
577
|
+
type: 'EXACT_DATE',
|
|
578
|
+
applicability: { level: 'TAB', targetId: 'tab-guid' },
|
|
579
|
+
},
|
|
580
|
+
} as any);
|
|
581
|
+
|
|
582
|
+
expect(out.filter).toEqual({
|
|
583
|
+
column: 'date',
|
|
584
|
+
oper: 'EQ',
|
|
585
|
+
values: [1700000000],
|
|
586
|
+
type: 'EXACT_DATE',
|
|
587
|
+
applicability: { level: 'TAB', targetId: 'tab-guid' },
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('produces a payload that still passes validation', () => {
|
|
592
|
+
const out = resolveUpdateFiltersAliases({
|
|
593
|
+
filters: [{ columnName: 'region', operator: 'EQ', values: ['west'] }],
|
|
594
|
+
} as any);
|
|
595
|
+
|
|
596
|
+
expect(isValidUpdateFiltersPayload(out as any)).toBe(true);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it('is a no-op on a non-object payload', () => {
|
|
600
|
+
expect(resolveUpdateFiltersAliases(undefined as any)).toBeUndefined();
|
|
601
|
+
expect(resolveUpdateFiltersAliases(null as any)).toBeNull();
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
});
|