@thoughtspot/visual-embed-sdk 1.53.0 → 1.53.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/contracts/contracts.spec.js +4 -0
- package/cjs/src/contracts/contracts.spec.js.map +1 -1
- package/cjs/src/contracts/host-event-contracts.d.ts +10 -0
- package/cjs/src/contracts/host-event-contracts.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/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 +32 -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 +97 -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/types.d.ts +69 -2
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +67 -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-CrBnZBZv.js} +1 -1
- package/dist/src/contracts/host-event-contracts.d.ts +10 -0
- package/dist/src/contracts/host-event-contracts.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/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/types.d.ts +69 -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 +247 -78
- package/dist/tsembed-react.js +246 -77
- package/dist/tsembed.es.js +247 -78
- package/dist/tsembed.js +246 -77
- package/dist/visual-embed-sdk-react-full.d.ts +116 -18
- package/dist/visual-embed-sdk-react.d.ts +116 -18
- package/dist/visual-embed-sdk.d.ts +116 -33
- package/lib/package.json +1 -1
- package/lib/src/contracts/contracts.spec.js +4 -0
- package/lib/src/contracts/contracts.spec.js.map +1 -1
- package/lib/src/contracts/host-event-contracts.d.ts +10 -0
- package/lib/src/contracts/host-event-contracts.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/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 +32 -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 +97 -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/types.d.ts +69 -2
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +67 -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 +116 -33
- package/package.json +1 -1
- package/src/contracts/contracts.spec.ts +4 -0
- package/src/contracts/host-event-contracts.ts +13 -0
- package/src/embed/app.ts +1 -0
- package/src/embed/conversation.spec.ts +24 -0
- package/src/embed/conversation.ts +14 -0
- package/src/embed/host-events.spec.ts +44 -0
- package/src/embed/liveboard.spec.ts +111 -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/types.ts +68 -0
- package/src/utils/runtimeTelemetry.spec.ts +148 -0
- package/src/utils/runtimeTelemetry.ts +50 -0
|
@@ -2728,12 +2728,11 @@ describe('Unit test case for ts embed', () => {
|
|
|
2728
2728
|
contentRect: { height: 297, width: 987 },
|
|
2729
2729
|
},
|
|
2730
2730
|
]);
|
|
2731
|
-
|
|
2732
2731
|
expect(preRenderWrapper.style.height).toEqual(`${297}px`);
|
|
2733
2732
|
expect(preRenderWrapper.style.width).toEqual(`${987}px`);
|
|
2734
2733
|
|
|
2735
2734
|
expect(preRenderWrapper.style.opacity).toBe('');
|
|
2736
|
-
expect(preRenderWrapper.style.pointerEvents).toBe('');
|
|
2735
|
+
expect(preRenderWrapper.style.pointerEvents).toBe('auto');
|
|
2737
2736
|
expect(preRenderWrapper.style.zIndex).toBe('');
|
|
2738
2737
|
|
|
2739
2738
|
libEmbed.hidePreRender();
|
|
@@ -2770,7 +2769,7 @@ describe('Unit test case for ts embed', () => {
|
|
|
2770
2769
|
const preRenderWrapper = document.getElementById(preRenderIds.wrapper);
|
|
2771
2770
|
|
|
2772
2771
|
expect(preRenderWrapper.style.opacity).toBe('');
|
|
2773
|
-
expect(preRenderWrapper.style.pointerEvents).toBe('');
|
|
2772
|
+
expect(preRenderWrapper.style.pointerEvents).toBe('auto');
|
|
2774
2773
|
expect(preRenderWrapper.style.zIndex).toBe('');
|
|
2775
2774
|
});
|
|
2776
2775
|
|
|
@@ -2943,6 +2942,216 @@ describe('Unit test case for ts embed', () => {
|
|
|
2943
2942
|
(logger.debug as any).mockRestore();
|
|
2944
2943
|
});
|
|
2945
2944
|
|
|
2945
|
+
describe('position tracking (SCAL-338563)', () => {
|
|
2946
|
+
/**
|
|
2947
|
+
* A host app whose page scrolls in an inner element rather than the
|
|
2948
|
+
* document — the shape both reported cases have. The placeholder's
|
|
2949
|
+
* rect is driven off that element's scrollTop, the way a real
|
|
2950
|
+
* layout would move it.
|
|
2951
|
+
*/
|
|
2952
|
+
const mountInNestedScroller = () => {
|
|
2953
|
+
createRootEleForEmbed();
|
|
2954
|
+
const scroller = document.createElement('div');
|
|
2955
|
+
scroller.id = 'inner-scroller';
|
|
2956
|
+
document.body.appendChild(scroller);
|
|
2957
|
+
scroller.appendChild(document.getElementById('tsEmbedDiv'));
|
|
2958
|
+
|
|
2959
|
+
let scrollTop = 0;
|
|
2960
|
+
Object.defineProperty(scroller, 'scrollTop', {
|
|
2961
|
+
configurable: true,
|
|
2962
|
+
get: () => scrollTop,
|
|
2963
|
+
set: (value) => {
|
|
2964
|
+
scrollTop = value;
|
|
2965
|
+
},
|
|
2966
|
+
});
|
|
2967
|
+
scroller.getBoundingClientRect = () =>
|
|
2968
|
+
({
|
|
2969
|
+
x: 0, y: 0, width: 800, height: 600,
|
|
2970
|
+
top: 0, left: 0, bottom: 600, right: 800,
|
|
2971
|
+
} as DOMRect);
|
|
2972
|
+
|
|
2973
|
+
const placeholderTopAtRest = 400;
|
|
2974
|
+
const readPlaceholderTop = () => placeholderTopAtRest - scroller.scrollTop;
|
|
2975
|
+
|
|
2976
|
+
return { scroller, readPlaceholderTop };
|
|
2977
|
+
};
|
|
2978
|
+
|
|
2979
|
+
const stubPlaceholderRect = (embed: any, readTop: () => number) => {
|
|
2980
|
+
const placeholder = embed.getPreRenderPlaceHolderElement() as HTMLElement;
|
|
2981
|
+
placeholder.getBoundingClientRect = () => {
|
|
2982
|
+
const top = readTop();
|
|
2983
|
+
return {
|
|
2984
|
+
x: 0, y: top, width: 800, height: 300,
|
|
2985
|
+
top, left: 0, bottom: top + 300, right: 800,
|
|
2986
|
+
} as DOMRect;
|
|
2987
|
+
};
|
|
2988
|
+
return placeholder;
|
|
2989
|
+
};
|
|
2990
|
+
|
|
2991
|
+
it('should stop observing the placeholder once the frame is hidden', async () => {
|
|
2992
|
+
const { scroller } = mountInNestedScroller();
|
|
2993
|
+
|
|
2994
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
2995
|
+
preRenderId: 'observer-stops-on-hide',
|
|
2996
|
+
liveboardId: 'myLiveboardId',
|
|
2997
|
+
});
|
|
2998
|
+
libEmbed.preRender();
|
|
2999
|
+
await waitFor(() => !!getIFrameEl());
|
|
3000
|
+
await libEmbed.showPreRender();
|
|
3001
|
+
|
|
3002
|
+
// Size is the only thing the browser will not handle for us, so
|
|
3003
|
+
// the observer is the one live resource a hide has to release.
|
|
3004
|
+
const observer = (libEmbed as any).resizeObserver;
|
|
3005
|
+
expect(observer).toBeDefined();
|
|
3006
|
+
const disconnectSpy = jest.spyOn(observer, 'disconnect');
|
|
3007
|
+
|
|
3008
|
+
libEmbed.hidePreRender();
|
|
3009
|
+
|
|
3010
|
+
expect(disconnectSpy).toHaveBeenCalled();
|
|
3011
|
+
|
|
3012
|
+
disconnectSpy.mockRestore();
|
|
3013
|
+
libEmbed.destroy();
|
|
3014
|
+
scroller.remove();
|
|
3015
|
+
});
|
|
3016
|
+
|
|
3017
|
+
it('should default the container to the host scrolling ancestor', async () => {
|
|
3018
|
+
const { scroller } = mountInNestedScroller();
|
|
3019
|
+
scroller.style.overflowY = 'auto';
|
|
3020
|
+
|
|
3021
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
3022
|
+
preRenderId: 'auto-scroll-container',
|
|
3023
|
+
liveboardId: 'myLiveboardId',
|
|
3024
|
+
});
|
|
3025
|
+
libEmbed.preRender();
|
|
3026
|
+
await waitFor(() => !!getIFrameEl());
|
|
3027
|
+
await libEmbed.showPreRender();
|
|
3028
|
+
|
|
3029
|
+
const wrapper = document.getElementById(libEmbed.getPreRenderIds().wrapper);
|
|
3030
|
+
// In the scroller, so the browser moves it — no repositioning.
|
|
3031
|
+
expect(wrapper.parentElement).toBe(scroller);
|
|
3032
|
+
expect((libEmbed as any).preRenderContainerEl).toBe(scroller);
|
|
3033
|
+
|
|
3034
|
+
libEmbed.destroy();
|
|
3035
|
+
scroller.remove();
|
|
3036
|
+
});
|
|
3037
|
+
|
|
3038
|
+
it('should still use document.body when nothing scrolls', async () => {
|
|
3039
|
+
createRootEleForEmbed();
|
|
3040
|
+
|
|
3041
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
3042
|
+
preRenderId: 'no-scroller-body',
|
|
3043
|
+
liveboardId: 'myLiveboardId',
|
|
3044
|
+
});
|
|
3045
|
+
libEmbed.preRender();
|
|
3046
|
+
await waitFor(() => !!getIFrameEl());
|
|
3047
|
+
await libEmbed.showPreRender();
|
|
3048
|
+
|
|
3049
|
+
const wrapper = document.getElementById(libEmbed.getPreRenderIds().wrapper);
|
|
3050
|
+
expect(wrapper.parentElement).toBe(document.body);
|
|
3051
|
+
|
|
3052
|
+
libEmbed.destroy();
|
|
3053
|
+
});
|
|
3054
|
+
|
|
3055
|
+
it('should set pointer-events on show rather than dropping the property', async () => {
|
|
3056
|
+
createRootEleForEmbed();
|
|
3057
|
+
const container = document.createElement('div');
|
|
3058
|
+
container.id = 'parking-root';
|
|
3059
|
+
// Usual styling for a parking root: no swallowed clicks.
|
|
3060
|
+
container.style.pointerEvents = 'none';
|
|
3061
|
+
document.body.appendChild(container);
|
|
3062
|
+
|
|
3063
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
3064
|
+
preRenderId: 'pointer-events-restored',
|
|
3065
|
+
liveboardId: 'myLiveboardId',
|
|
3066
|
+
preRenderContainer: '#parking-root',
|
|
3067
|
+
});
|
|
3068
|
+
libEmbed.preRender();
|
|
3069
|
+
await waitFor(() => !!getIFrameEl());
|
|
3070
|
+
await libEmbed.showPreRender();
|
|
3071
|
+
|
|
3072
|
+
const wrapper = document.getElementById(libEmbed.getPreRenderIds().wrapper);
|
|
3073
|
+
// Merely removing the property would leave the container's
|
|
3074
|
+
// inherited `none` in force, and the frame dead to input.
|
|
3075
|
+
expect(wrapper.style.pointerEvents).toBe('auto');
|
|
3076
|
+
|
|
3077
|
+
libEmbed.destroy();
|
|
3078
|
+
container.remove();
|
|
3079
|
+
});
|
|
3080
|
+
|
|
3081
|
+
it('should park a hidden wrapper out of the scrollable area', async () => {
|
|
3082
|
+
createRootEleForEmbed();
|
|
3083
|
+
|
|
3084
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
3085
|
+
preRenderId: 'hidden-parked',
|
|
3086
|
+
liveboardId: 'myLiveboardId',
|
|
3087
|
+
});
|
|
3088
|
+
libEmbed.preRender();
|
|
3089
|
+
await waitFor(() => !!getIFrameEl());
|
|
3090
|
+
await libEmbed.showPreRender();
|
|
3091
|
+
|
|
3092
|
+
const wrapper = document.getElementById(libEmbed.getPreRenderIds().wrapper);
|
|
3093
|
+
expect(wrapper.style.transform).toBe('');
|
|
3094
|
+
|
|
3095
|
+
libEmbed.hidePreRender();
|
|
3096
|
+
// Detached from every scroll container (fixed, resolves to the
|
|
3097
|
+
// viewport), parked for the captured-fixed case, size
|
|
3098
|
+
// preserved.
|
|
3099
|
+
expect(wrapper.style.position).toBe('fixed');
|
|
3100
|
+
expect(wrapper.style.transform).toBe('translateY(-100%)');
|
|
3101
|
+
expect(wrapper.style.height).not.toBe('');
|
|
3102
|
+
|
|
3103
|
+
await libEmbed.showPreRender();
|
|
3104
|
+
expect(wrapper.style.transform).toBe('');
|
|
3105
|
+
|
|
3106
|
+
libEmbed.destroy();
|
|
3107
|
+
});
|
|
3108
|
+
|
|
3109
|
+
it('should not seed the placeholder with the unmeasured wrapper height', async () => {
|
|
3110
|
+
createRootEleForEmbed();
|
|
3111
|
+
|
|
3112
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
3113
|
+
preRenderId: 'unmeasured-height',
|
|
3114
|
+
liveboardId: 'myLiveboardId',
|
|
3115
|
+
fullHeight: true,
|
|
3116
|
+
frameParams: { height: 420 },
|
|
3117
|
+
});
|
|
3118
|
+
libEmbed.preRender();
|
|
3119
|
+
await waitFor(() => !!getIFrameEl());
|
|
3120
|
+
|
|
3121
|
+
const wrapper = document.getElementById(libEmbed.getPreRenderIds().wrapper);
|
|
3122
|
+
// createPreRenderWrapper() seeds 100vh, not a measurement.
|
|
3123
|
+
expect(wrapper.style.height).toBe('100vh');
|
|
3124
|
+
|
|
3125
|
+
await libEmbed.showPreRender();
|
|
3126
|
+
|
|
3127
|
+
const placeholder = (libEmbed as any).getPreRenderPlaceHolderElement() as HTMLElement;
|
|
3128
|
+
expect(placeholder.style.height).toBe('420px');
|
|
3129
|
+
|
|
3130
|
+
libEmbed.destroy();
|
|
3131
|
+
});
|
|
3132
|
+
|
|
3133
|
+
it('should seed the placeholder with a height fullHeight has measured', async () => {
|
|
3134
|
+
createRootEleForEmbed();
|
|
3135
|
+
|
|
3136
|
+
const libEmbed = new LiveboardEmbed('#tsEmbedDiv', {
|
|
3137
|
+
preRenderId: 'measured-height',
|
|
3138
|
+
liveboardId: 'myLiveboardId',
|
|
3139
|
+
fullHeight: true,
|
|
3140
|
+
frameParams: { height: 420 },
|
|
3141
|
+
});
|
|
3142
|
+
libEmbed.preRender();
|
|
3143
|
+
await waitFor(() => !!getIFrameEl());
|
|
3144
|
+
|
|
3145
|
+
(libEmbed as any).setIFrameHeight(1180);
|
|
3146
|
+
await libEmbed.showPreRender();
|
|
3147
|
+
|
|
3148
|
+
const placeholder = (libEmbed as any).getPreRenderPlaceHolderElement() as HTMLElement;
|
|
3149
|
+
expect(placeholder.style.height).toBe('1180px');
|
|
3150
|
+
|
|
3151
|
+
libEmbed.destroy();
|
|
3152
|
+
});
|
|
3153
|
+
});
|
|
3154
|
+
|
|
2946
3155
|
describe('preRenderContainer', () => {
|
|
2947
3156
|
it('should append preRenderWrapper to document.body when preRenderContainer is not set', async () => {
|
|
2948
3157
|
createRootEleForEmbed();
|
|
@@ -3216,21 +3425,16 @@ describe('Unit test case for ts embed', () => {
|
|
|
3216
3425
|
|
|
3217
3426
|
// Simulate React remounting the container: the old node (with
|
|
3218
3427
|
// our wrapper) is detached and a fresh node takes its place.
|
|
3219
|
-
const removeSpy = jest.spyOn(oldContainer, 'removeEventListener');
|
|
3220
3428
|
oldContainer.remove();
|
|
3221
3429
|
const newContainer = document.createElement('div');
|
|
3222
3430
|
newContainer.id = 'remount-container';
|
|
3223
3431
|
document.body.appendChild(newContainer);
|
|
3224
|
-
const addSpy = jest.spyOn(newContainer, 'addEventListener');
|
|
3225
3432
|
|
|
3226
3433
|
// A reposition (scroll/resize/re-show) heals the stale ref.
|
|
3227
3434
|
libEmbed.syncPreRenderStyle();
|
|
3228
3435
|
|
|
3229
3436
|
expect(newContainer.contains(wrapper)).toBe(true);
|
|
3230
3437
|
expect(oldContainer.contains(wrapper)).toBe(false);
|
|
3231
|
-
// The scroll listener is migrated to the live container.
|
|
3232
|
-
expect(removeSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
3233
|
-
expect(addSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
3234
3438
|
|
|
3235
3439
|
libEmbed.destroy();
|
|
3236
3440
|
newContainer.remove();
|
|
@@ -7372,3 +7576,88 @@ describe('excludeConfigFromURL', () => {
|
|
|
7372
7576
|
});
|
|
7373
7577
|
});
|
|
7374
7578
|
});
|
|
7579
|
+
|
|
7580
|
+
describe('visual-sdk-embed-create does not upload runtime filter or parameter data', () => {
|
|
7581
|
+
// Spied per test: an earlier suite calls jest.restoreAllMocks(), which
|
|
7582
|
+
// would unwire a spy created at collection time.
|
|
7583
|
+
let mockMixPanelEvent: jest.SpyInstance;
|
|
7584
|
+
|
|
7585
|
+
const runtimeFilters: RuntimeFilter[] = [
|
|
7586
|
+
{
|
|
7587
|
+
columnName: 'Patient SSN',
|
|
7588
|
+
operator: RuntimeFilterOp.EQ,
|
|
7589
|
+
values: ['123-45-6789'],
|
|
7590
|
+
},
|
|
7591
|
+
];
|
|
7592
|
+
const runtimeParameters: RuntimeParameter[] = [
|
|
7593
|
+
{
|
|
7594
|
+
name: 'Sales Region',
|
|
7595
|
+
value: 'EMEA',
|
|
7596
|
+
},
|
|
7597
|
+
];
|
|
7598
|
+
|
|
7599
|
+
beforeAll(() => {
|
|
7600
|
+
init({
|
|
7601
|
+
thoughtSpotHost: 'tshost',
|
|
7602
|
+
authType: AuthType.None,
|
|
7603
|
+
});
|
|
7604
|
+
});
|
|
7605
|
+
|
|
7606
|
+
beforeEach(() => {
|
|
7607
|
+
document.body.innerHTML = getDocumentBody();
|
|
7608
|
+
mockMixPanelEvent = jest.spyOn(mixpanelInstance, 'uploadMixpanelEvent');
|
|
7609
|
+
});
|
|
7610
|
+
|
|
7611
|
+
afterEach(() => {
|
|
7612
|
+
mockMixPanelEvent.mockRestore();
|
|
7613
|
+
});
|
|
7614
|
+
|
|
7615
|
+
const getEmbedCreateProps = () => {
|
|
7616
|
+
const call = mockMixPanelEvent.mock.calls.find(
|
|
7617
|
+
([eventId]) => eventId === MIXPANEL_EVENT.VISUAL_SDK_EMBED_CREATE,
|
|
7618
|
+
);
|
|
7619
|
+
expect(call).toBeDefined();
|
|
7620
|
+
return call[1];
|
|
7621
|
+
};
|
|
7622
|
+
|
|
7623
|
+
test('uploads the summary instead of the filters and parameters', () => {
|
|
7624
|
+
new LiveboardEmbed(getRootEl(), {
|
|
7625
|
+
liveboardId: 'lb-guid',
|
|
7626
|
+
runtimeFilters,
|
|
7627
|
+
runtimeParameters,
|
|
7628
|
+
} as LiveboardViewConfig);
|
|
7629
|
+
|
|
7630
|
+
expect(getEmbedCreateProps()).toEqual(
|
|
7631
|
+
expect.objectContaining({
|
|
7632
|
+
liveboardId: 'lb-guid',
|
|
7633
|
+
runtimeFilters: { count: 1, operators: ['EQ'] },
|
|
7634
|
+
runtimeParameters: { count: 1, applicabilityLevels: [] },
|
|
7635
|
+
}),
|
|
7636
|
+
);
|
|
7637
|
+
});
|
|
7638
|
+
|
|
7639
|
+
test('no column name, parameter name or operand reaches mixpanel', () => {
|
|
7640
|
+
new LiveboardEmbed(getRootEl(), {
|
|
7641
|
+
liveboardId: 'lb-guid',
|
|
7642
|
+
runtimeFilters,
|
|
7643
|
+
runtimeParameters,
|
|
7644
|
+
} as LiveboardViewConfig);
|
|
7645
|
+
|
|
7646
|
+
const uploaded = JSON.stringify(getEmbedCreateProps());
|
|
7647
|
+
['Patient SSN', '123-45-6789', 'Sales Region', 'EMEA'].forEach((customerValue) => {
|
|
7648
|
+
expect(uploaded).not.toContain(customerValue);
|
|
7649
|
+
});
|
|
7650
|
+
});
|
|
7651
|
+
|
|
7652
|
+
test('an embed with no runtime filters is unchanged', () => {
|
|
7653
|
+
new LiveboardEmbed(getRootEl(), {
|
|
7654
|
+
liveboardId: 'lb-guid',
|
|
7655
|
+
} as LiveboardViewConfig);
|
|
7656
|
+
|
|
7657
|
+
const props = getEmbedCreateProps();
|
|
7658
|
+
expect(props).toEqual({
|
|
7659
|
+
liveboardId: 'lb-guid',
|
|
7660
|
+
embedComponentType: 'LiveboardEmbed',
|
|
7661
|
+
});
|
|
7662
|
+
});
|
|
7663
|
+
});
|
package/src/embed/ts-embed.ts
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
getHostEventsConfig,
|
|
46
46
|
getValueFromWindow,
|
|
47
47
|
deserializeParam,
|
|
48
|
+
getScrollableAncestors,
|
|
48
49
|
setParamIfDefined,
|
|
49
50
|
} from '../utils';
|
|
50
51
|
import { getCustomActions } from '../utils/custom-actions';
|
|
@@ -80,6 +81,7 @@ import {
|
|
|
80
81
|
BaseViewConfig,
|
|
81
82
|
} from '../types';
|
|
82
83
|
import { uploadMixpanelEvent, MIXPANEL_EVENT } from '../mixpanel-service';
|
|
84
|
+
import { summarizeRuntimeDataForTelemetry } from '../utils/runtimeTelemetry';
|
|
83
85
|
import { processEventData, processAuthFailure } from '../utils/processData';
|
|
84
86
|
import { version } from '../utils/sdk-version';
|
|
85
87
|
import {
|
|
@@ -116,6 +118,9 @@ const TS_EMBED_ID = '_thoughtspot-embed';
|
|
|
116
118
|
*/
|
|
117
119
|
const PRERENDER_CONTAINER_ORIGINAL_POSITION_KEY = 'tsEmbedOriginalPosition';
|
|
118
120
|
const PRERENDER_WRAPPER_ID_PREFIX = 'tsEmbed-pre-render-wrapper-';
|
|
121
|
+
const PRERENDER_PARKED_TRANSFORM = 'translateY(-100%)';
|
|
122
|
+
/** The height createPreRenderWrapper() seeds before anything is measured. */
|
|
123
|
+
const UNMEASURED_WRAPPER_HEIGHT = '100vh';
|
|
119
124
|
|
|
120
125
|
// The container applies UpdateEmbedParams through React state, so a delivered
|
|
121
126
|
// post is not the same as the params being in effect.
|
|
@@ -325,8 +330,7 @@ export class TsEmbed {
|
|
|
325
330
|
|
|
326
331
|
private preRenderContainerEl: HTMLElement = document.body;
|
|
327
332
|
|
|
328
|
-
|
|
329
|
-
|
|
333
|
+
/** Last height the embedded app reported, once fullHeight has measured one. */
|
|
330
334
|
protected hostEventClient: HostEventClient;
|
|
331
335
|
|
|
332
336
|
protected isReadyForRenderPromise;
|
|
@@ -347,9 +351,10 @@ export class TsEmbed {
|
|
|
347
351
|
...viewConfig,
|
|
348
352
|
};
|
|
349
353
|
this.registerAppInit();
|
|
350
|
-
uploadMixpanelEvent(
|
|
351
|
-
|
|
352
|
-
|
|
354
|
+
uploadMixpanelEvent(
|
|
355
|
+
MIXPANEL_EVENT.VISUAL_SDK_EMBED_CREATE,
|
|
356
|
+
summarizeRuntimeDataForTelemetry(viewConfig),
|
|
357
|
+
);
|
|
353
358
|
const embedConfig = getEmbedConfig();
|
|
354
359
|
if (embedConfig) {
|
|
355
360
|
this.embedConfig = embedConfig;
|
|
@@ -1283,7 +1288,7 @@ export class TsEmbed {
|
|
|
1283
1288
|
top: '0',
|
|
1284
1289
|
left: '0',
|
|
1285
1290
|
width: '100vw',
|
|
1286
|
-
height:
|
|
1291
|
+
height: UNMEASURED_WRAPPER_HEIGHT,
|
|
1287
1292
|
};
|
|
1288
1293
|
setStyleProperties(preRenderWrapper, initialPreRenderWrapperStyle);
|
|
1289
1294
|
|
|
@@ -1369,10 +1374,12 @@ export class TsEmbed {
|
|
|
1369
1374
|
}
|
|
1370
1375
|
|
|
1371
1376
|
/**
|
|
1372
|
-
* Resolves the configured preRenderContainer
|
|
1373
|
-
*
|
|
1374
|
-
*
|
|
1375
|
-
*
|
|
1377
|
+
* Resolves the configured preRenderContainer, or defaults to the host's
|
|
1378
|
+
* nearest scrolling ancestor (document.body if none). The absolutely
|
|
1379
|
+
* positioned wrapper only follows the page while it sits inside what
|
|
1380
|
+
* scrolls, so body is right only when the document itself scrolls — an inner
|
|
1381
|
+
* scroller left the frame pinned to the viewport (SCAL-338563). A string
|
|
1382
|
+
* selector is re-queried each call so a remounted container resolves fresh.
|
|
1376
1383
|
*/
|
|
1377
1384
|
private resolvePreRenderContainerTarget(): HTMLElement {
|
|
1378
1385
|
const containerConfig = this.getPreRenderConfig().containerSelector;
|
|
@@ -1389,7 +1396,13 @@ export class TsEmbed {
|
|
|
1389
1396
|
} else if (containerConfig) {
|
|
1390
1397
|
container = containerConfig;
|
|
1391
1398
|
}
|
|
1392
|
-
|
|
1399
|
+
if (container) {
|
|
1400
|
+
return container as HTMLElement;
|
|
1401
|
+
}
|
|
1402
|
+
if (!this.hostElement) {
|
|
1403
|
+
return document.body;
|
|
1404
|
+
}
|
|
1405
|
+
return getScrollableAncestors(this.hostElement)[0] ?? document.body;
|
|
1393
1406
|
}
|
|
1394
1407
|
|
|
1395
1408
|
private inheritPreRenderContainer(): void {
|
|
@@ -1443,8 +1456,9 @@ export class TsEmbed {
|
|
|
1443
1456
|
* Re-attaches the wrapper to a live container when the previously resolved
|
|
1444
1457
|
* one has been detached or no longer holds the wrapper — e.g. the host app
|
|
1445
1458
|
* remounted a custom preRenderContainer, which would otherwise leave a stale
|
|
1446
|
-
* reference and collapse the wrapper.
|
|
1447
|
-
* re-resolved; a container passed as an element
|
|
1459
|
+
* reference and collapse the wrapper. Selectors and the auto-resolved
|
|
1460
|
+
* scrolling ancestor are both re-resolved; a container passed as an element
|
|
1461
|
+
* is left untouched, as there is nothing to re-query.
|
|
1448
1462
|
*/
|
|
1449
1463
|
private reconcilePreRenderContainer(): void {
|
|
1450
1464
|
const wrapper = this.preRenderWrapper;
|
|
@@ -1463,14 +1477,6 @@ export class TsEmbed {
|
|
|
1463
1477
|
if (resolved === stored && stored.contains(wrapper)) {
|
|
1464
1478
|
return;
|
|
1465
1479
|
}
|
|
1466
|
-
if (this.containerScrollListener && stored !== resolved) {
|
|
1467
|
-
if (stored !== document.body) {
|
|
1468
|
-
stored.removeEventListener('scroll', this.containerScrollListener);
|
|
1469
|
-
}
|
|
1470
|
-
if (resolved !== document.body) {
|
|
1471
|
-
resolved.addEventListener('scroll', this.containerScrollListener);
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
1480
|
this.preRenderContainerEl = resolved;
|
|
1475
1481
|
this.applyPreRenderContainerPositioning();
|
|
1476
1482
|
if (wrapper.parentNode !== resolved) {
|
|
@@ -1539,10 +1545,11 @@ export class TsEmbed {
|
|
|
1539
1545
|
*/
|
|
1540
1546
|
protected setIFrameHeight(height: number | string): void {
|
|
1541
1547
|
if (this.isPreRendered) {
|
|
1548
|
+
const next = getCssDimension(height);
|
|
1542
1549
|
if (this.insertedDomEl) {
|
|
1543
|
-
(this.insertedDomEl as HTMLElement).style.height =
|
|
1550
|
+
(this.insertedDomEl as HTMLElement).style.height = next;
|
|
1544
1551
|
} else if (this.preRenderWrapper) {
|
|
1545
|
-
this.preRenderWrapper.style.height =
|
|
1552
|
+
this.preRenderWrapper.style.height = next;
|
|
1546
1553
|
}
|
|
1547
1554
|
} else {
|
|
1548
1555
|
// normal (non-preRender) mode: size the iframe directly
|
|
@@ -2113,15 +2120,27 @@ export class TsEmbed {
|
|
|
2113
2120
|
}
|
|
2114
2121
|
|
|
2115
2122
|
/**
|
|
2116
|
-
*
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2123
|
+
* Re-sizes the wrapper when its placeholder changes size.
|
|
2124
|
+
*
|
|
2125
|
+
* Position deliberately has no equivalent. An absolutely positioned wrapper
|
|
2126
|
+
* follows its containing block for free, so a correctly placed frame is the
|
|
2127
|
+
* browser's job and costs nothing; repositioning it on scroll would mean a
|
|
2128
|
+
* layout read and a style write per frame to compensate for placing it in
|
|
2129
|
+
* the wrong box. Size is the one thing the browser will not do for us: the
|
|
2130
|
+
* placeholder grows when the embedded app reports a new height, and the
|
|
2131
|
+
* wrapper has to be told.
|
|
2132
|
+
*/
|
|
2133
|
+
private observePreRenderSize(): void {
|
|
2134
|
+
if (this.getPreRenderConfig().doNotTrackSize || typeof ResizeObserver === 'undefined') {
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
const placeholder = this.getPreRenderPlaceHolderElement();
|
|
2138
|
+
if (!placeholder) {
|
|
2120
2139
|
return;
|
|
2121
2140
|
}
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
this.
|
|
2141
|
+
this.resizeObserver?.disconnect();
|
|
2142
|
+
this.resizeObserver = new ResizeObserver(() => this.syncPreRenderStyle());
|
|
2143
|
+
this.resizeObserver.observe(placeholder);
|
|
2125
2144
|
}
|
|
2126
2145
|
|
|
2127
2146
|
/**
|
|
@@ -2131,7 +2150,7 @@ export class TsEmbed {
|
|
|
2131
2150
|
public destroy(): void {
|
|
2132
2151
|
try {
|
|
2133
2152
|
this.removeFullscreenChangeHandler();
|
|
2134
|
-
this.
|
|
2153
|
+
this.resizeObserver?.disconnect();
|
|
2135
2154
|
this.unsubscribeToEvents();
|
|
2136
2155
|
this.preRenderWrapper?.remove();
|
|
2137
2156
|
this.restorePreRenderContainerPosition();
|
|
@@ -2258,14 +2277,18 @@ export class TsEmbed {
|
|
|
2258
2277
|
|
|
2259
2278
|
if (this.hostElement) {
|
|
2260
2279
|
this.insertedDomEl = this.createPreRenderPlaceholder();
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
}
|
|
2280
|
+
// Carry a height fullHeight has already measured onto the fresh
|
|
2281
|
+
// placeholder, so a re-show does not flash at frameParams height.
|
|
2282
|
+
// UNMEASURED_WRAPPER_HEIGHT is what createPreRenderWrapper() seeds
|
|
2283
|
+
// before anything has been measured; treating that as a measurement
|
|
2284
|
+
// is what made a first reveal overshoot to a full viewport.
|
|
2285
|
+
const wrapperHeight = this.preRenderWrapper.style.height;
|
|
2286
|
+
if (
|
|
2287
|
+
(this.viewConfig as { fullHeight: boolean }).fullHeight
|
|
2288
|
+
&& wrapperHeight
|
|
2289
|
+
&& wrapperHeight !== UNMEASURED_WRAPPER_HEIGHT
|
|
2290
|
+
) {
|
|
2291
|
+
(this.insertedDomEl as HTMLDivElement).style.height = wrapperHeight;
|
|
2269
2292
|
}
|
|
2270
2293
|
|
|
2271
2294
|
const placeHolderId = this.getPreRenderIds().placeHolder;
|
|
@@ -2282,32 +2305,20 @@ export class TsEmbed {
|
|
|
2282
2305
|
this.hostElement.appendChild(this.insertedDomEl);
|
|
2283
2306
|
|
|
2284
2307
|
this.syncPreRenderStyle();
|
|
2285
|
-
|
|
2286
|
-
const customContainer = this.getCustomPreRenderContainer();
|
|
2287
|
-
if (customContainer && !this.containerScrollListener) {
|
|
2288
|
-
this.containerScrollListener = () => this.syncPreRenderStyle();
|
|
2289
|
-
customContainer.addEventListener('scroll', this.containerScrollListener);
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
if (!this.getPreRenderConfig().doNotTrackSize) {
|
|
2293
|
-
const observeTarget = (this.insertedDomEl as HTMLElement) ?? this.hostElement;
|
|
2294
|
-
this.resizeObserver = new ResizeObserver((entries) => {
|
|
2295
|
-
entries.forEach((entry) => {
|
|
2296
|
-
if (entry.target === observeTarget) {
|
|
2297
|
-
this.syncPreRenderStyle();
|
|
2298
|
-
}
|
|
2299
|
-
});
|
|
2300
|
-
});
|
|
2301
|
-
this.resizeObserver.observe(observeTarget);
|
|
2302
|
-
}
|
|
2308
|
+
this.observePreRenderSize();
|
|
2303
2309
|
}
|
|
2304
2310
|
|
|
2305
2311
|
removeStyleProperties(this.preRenderWrapper, [
|
|
2306
2312
|
'z-index',
|
|
2307
2313
|
'opacity',
|
|
2308
|
-
'pointer-events',
|
|
2309
2314
|
'overflow',
|
|
2315
|
+
'transform',
|
|
2310
2316
|
]);
|
|
2317
|
+
// Set rather than removed: a container that carries `pointer-events:
|
|
2318
|
+
// none` — the usual styling for a parking root that must not swallow
|
|
2319
|
+
// clicks — passes it down, and dropping the property here would leave
|
|
2320
|
+
// the inherited `none` in force and the frame dead to input.
|
|
2321
|
+
setStyleProperties(this.preRenderWrapper, { pointerEvents: 'auto' });
|
|
2311
2322
|
this.subscribeToEvents();
|
|
2312
2323
|
|
|
2313
2324
|
// Setup fullscreen change handler for prerendered components
|
|
@@ -2379,22 +2390,26 @@ export class TsEmbed {
|
|
|
2379
2390
|
return;
|
|
2380
2391
|
}
|
|
2381
2392
|
const { zIndex } = this.getPreRenderConfig();
|
|
2393
|
+
// A hidden pre-render frame must add no scroll space to the host page.
|
|
2382
2394
|
const preRenderHideStyles = {
|
|
2383
2395
|
opacity: '0',
|
|
2384
2396
|
pointerEvents: 'none',
|
|
2385
2397
|
zIndex: zIndex !== undefined ? String(zIndex) : '-1000',
|
|
2386
|
-
|
|
2398
|
+
// Resolves to the viewport, so the hidden frame belongs to no
|
|
2399
|
+
// scroll container's overflow; syncPreRenderStyle restores absolute
|
|
2400
|
+
// on show.
|
|
2401
|
+
position: 'fixed',
|
|
2387
2402
|
top: '0',
|
|
2388
2403
|
left: '0',
|
|
2389
2404
|
overflow: 'hidden',
|
|
2405
|
+
// The one exception: a transformed or contained ancestor captures
|
|
2406
|
+
// `fixed`; parking above the top edge keeps it out of that
|
|
2407
|
+
// overflow.
|
|
2408
|
+
transform: PRERENDER_PARKED_TRANSFORM,
|
|
2390
2409
|
};
|
|
2391
2410
|
setStyleProperties(this.preRenderWrapper, preRenderHideStyles);
|
|
2392
2411
|
|
|
2393
|
-
this.
|
|
2394
|
-
|
|
2395
|
-
if (this.resizeObserver) {
|
|
2396
|
-
this.resizeObserver.disconnect();
|
|
2397
|
-
}
|
|
2412
|
+
this.resizeObserver?.disconnect();
|
|
2398
2413
|
|
|
2399
2414
|
const placeHolderEle = this.getPreRenderPlaceHolderElement();
|
|
2400
2415
|
if (placeHolderEle) {
|
package/src/index.ts
CHANGED
|
@@ -159,6 +159,7 @@ import {
|
|
|
159
159
|
ScheduleEmailRequest,
|
|
160
160
|
NavigateRequest,
|
|
161
161
|
SetActiveTabRequest,
|
|
162
|
+
UpdateThemeRequest,
|
|
162
163
|
} from './contracts/host-event-contracts';
|
|
163
164
|
import {
|
|
164
165
|
convertFilterChangedToUpdateFiltersPayload,
|
|
@@ -292,6 +293,7 @@ export {
|
|
|
292
293
|
ScheduleEmailRequest,
|
|
293
294
|
NavigateRequest,
|
|
294
295
|
SetActiveTabRequest,
|
|
296
|
+
UpdateThemeRequest,
|
|
295
297
|
LiveboardTab,
|
|
296
298
|
LiveboardGroup,
|
|
297
299
|
ApplicabilityLevel,
|