@thoughtspot/visual-embed-sdk 1.39.3 → 1.40.1-alpha.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/src/embed/app.d.ts +56 -0
- package/cjs/src/embed/app.d.ts.map +1 -1
- package/cjs/src/embed/app.js +47 -8
- package/cjs/src/embed/app.js.map +1 -1
- package/cjs/src/embed/app.spec.js +322 -7
- package/cjs/src/embed/app.spec.js.map +1 -1
- package/cjs/src/embed/liveboard.d.ts +58 -1
- package/cjs/src/embed/liveboard.d.ts.map +1 -1
- package/cjs/src/embed/liveboard.js +59 -8
- package/cjs/src/embed/liveboard.js.map +1 -1
- package/cjs/src/embed/liveboard.spec.js +206 -0
- package/cjs/src/embed/liveboard.spec.js.map +1 -1
- package/cjs/src/embed/ts-embed.d.ts +7 -0
- package/cjs/src/embed/ts-embed.d.ts.map +1 -1
- package/cjs/src/embed/ts-embed.js +61 -7
- package/cjs/src/embed/ts-embed.js.map +1 -1
- package/cjs/src/types.d.ts +37 -6
- package/cjs/src/types.d.ts.map +1 -1
- package/cjs/src/types.js +35 -4
- package/cjs/src/types.js.map +1 -1
- package/cjs/src/utils/processTrigger.js +2 -1
- package/cjs/src/utils/processTrigger.js.map +1 -1
- package/cjs/src/utils.d.ts +6 -0
- package/cjs/src/utils.d.ts.map +1 -1
- package/cjs/src/utils.js +23 -3
- package/cjs/src/utils.js.map +1 -1
- package/cjs/src/utils.spec.js +212 -1
- package/cjs/src/utils.spec.js.map +1 -1
- package/dist/{index-ZrE8YYq8.js → index-CmEQfuE3.js} +1 -1
- package/dist/index-D1pyb7RG.js +7371 -0
- package/dist/index-DeFzsyFF.js +7371 -0
- package/dist/index-Dpf0rd6w.js +7371 -0
- package/dist/index-UuEbsISo.js +7447 -0
- package/dist/index-e3Uw3YFO.js +7371 -0
- package/dist/src/embed/app.d.ts +56 -0
- package/dist/src/embed/app.d.ts.map +1 -1
- package/dist/src/embed/bodyless-conversation.d.ts +0 -4
- package/dist/src/embed/bodyless-conversation.d.ts.map +1 -1
- package/dist/src/embed/liveboard.d.ts +56 -0
- package/dist/src/embed/liveboard.d.ts.map +1 -1
- package/dist/src/react/index.d.ts +0 -2
- package/dist/src/react/index.d.ts.map +1 -1
- package/dist/src/types.d.ts +16 -198
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/graphql/nlsService/conversation-service.d.ts.map +1 -1
- package/dist/src/utils.d.ts +6 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/tsembed-react.es.js +137 -224
- package/dist/tsembed-react.js +136 -223
- package/dist/tsembed.es.js +137 -224
- package/dist/tsembed.js +136 -223
- package/dist/visual-embed-sdk-react-full.d.ts +106 -204
- package/dist/visual-embed-sdk-react.d.ts +106 -204
- package/dist/visual-embed-sdk.d.ts +106 -202
- package/lib/src/embed/app.d.ts +56 -0
- package/lib/src/embed/app.d.ts.map +1 -1
- package/lib/src/embed/app.js +48 -9
- package/lib/src/embed/app.js.map +1 -1
- package/lib/src/embed/app.spec.js +322 -7
- package/lib/src/embed/app.spec.js.map +1 -1
- package/lib/src/embed/liveboard.d.ts +58 -1
- package/lib/src/embed/liveboard.d.ts.map +1 -1
- package/lib/src/embed/liveboard.js +60 -9
- package/lib/src/embed/liveboard.js.map +1 -1
- package/lib/src/embed/liveboard.spec.js +206 -0
- package/lib/src/embed/liveboard.spec.js.map +1 -1
- package/lib/src/embed/ts-embed.d.ts +7 -0
- package/lib/src/embed/ts-embed.d.ts.map +1 -1
- package/lib/src/embed/ts-embed.js +61 -7
- package/lib/src/embed/ts-embed.js.map +1 -1
- package/lib/src/types.d.ts +37 -6
- package/lib/src/types.d.ts.map +1 -1
- package/lib/src/types.js +35 -4
- package/lib/src/types.js.map +1 -1
- package/lib/src/utils/processTrigger.js +2 -1
- package/lib/src/utils/processTrigger.js.map +1 -1
- package/lib/src/utils.d.ts +6 -0
- package/lib/src/utils.d.ts.map +1 -1
- package/lib/src/utils.js +21 -2
- package/lib/src/utils.js.map +1 -1
- package/lib/src/utils.spec.js +213 -2
- package/lib/src/utils.spec.js.map +1 -1
- package/lib/src/visual-embed-sdk.d.ts +106 -202
- package/package.json +1 -2
- package/src/embed/app.spec.ts +397 -8
- package/src/embed/app.ts +106 -12
- package/src/embed/liveboard.spec.ts +254 -1
- package/src/embed/liveboard.ts +109 -11
- package/src/embed/ts-embed.ts +84 -21
- package/src/types.ts +36 -5
- package/src/utils/processTrigger.ts +1 -1
- package/src/utils.spec.ts +250 -2
- package/src/utils.ts +28 -2
package/src/embed/app.spec.ts
CHANGED
|
@@ -131,7 +131,7 @@ describe('App embed tests', () => {
|
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
describe('should render the correct routes for pages', () => {
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
const pageRouteMap = {
|
|
136
136
|
[Page.Search]: 'answer',
|
|
137
137
|
[Page.Answers]: 'answers',
|
|
@@ -148,7 +148,7 @@ describe('App embed tests', () => {
|
|
|
148
148
|
const pageId = pageIds[i];
|
|
149
149
|
|
|
150
150
|
test(`${pageId}`, async () => {
|
|
151
|
-
const route = pageRouteMap[pageId];
|
|
151
|
+
const route = pageRouteMap[pageId as keyof typeof pageRouteMap];
|
|
152
152
|
const appEmbed = new AppEmbed(getRootEl(), {
|
|
153
153
|
...defaultViewConfig,
|
|
154
154
|
pageId: pageId as Page,
|
|
@@ -181,7 +181,7 @@ describe('App embed tests', () => {
|
|
|
181
181
|
const pageIdsForModularHome = pageIdsForModularHomes[i];
|
|
182
182
|
|
|
183
183
|
test(`${pageIdsForModularHome}`, async () => {
|
|
184
|
-
const route =
|
|
184
|
+
const route = pageRouteMapForModularHome[pageIdsForModularHome as keyof typeof pageRouteMapForModularHome];
|
|
185
185
|
const appEmbed = new AppEmbed(getRootEl(), {
|
|
186
186
|
...defaultViewConfig,
|
|
187
187
|
modularHomeExperience: true,
|
|
@@ -753,7 +753,7 @@ describe('App embed tests', () => {
|
|
|
753
753
|
test('Should add dataPanelCustomGroupsAccordionInitialState flag to the iframe src', async () => {
|
|
754
754
|
const appEmbed = new AppEmbed(getRootEl(), {
|
|
755
755
|
...defaultViewConfig,
|
|
756
|
-
|
|
756
|
+
|
|
757
757
|
dataPanelCustomGroupsAccordionInitialState:
|
|
758
758
|
DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST,
|
|
759
759
|
} as AppViewConfig);
|
|
@@ -768,12 +768,13 @@ describe('App embed tests', () => {
|
|
|
768
768
|
});
|
|
769
769
|
|
|
770
770
|
test('should register event handlers to adjust iframe height', async () => {
|
|
771
|
+
let embedHeightCallback: any = () => { };
|
|
771
772
|
const onSpy = jest.spyOn(AppEmbed.prototype, 'on').mockImplementation((event, callback) => {
|
|
772
773
|
if (event === EmbedEvent.RouteChange) {
|
|
773
774
|
callback({ data: { currentPath: '/answers' } }, jest.fn());
|
|
774
775
|
}
|
|
775
776
|
if (event === EmbedEvent.EmbedHeight) {
|
|
776
|
-
|
|
777
|
+
embedHeightCallback = callback;
|
|
777
778
|
}
|
|
778
779
|
if (event === EmbedEvent.EmbedIframeCenter) {
|
|
779
780
|
callback({}, jest.fn());
|
|
@@ -785,16 +786,23 @@ describe('App embed tests', () => {
|
|
|
785
786
|
const appEmbed = new AppEmbed(getRootEl(), {
|
|
786
787
|
...defaultViewConfig,
|
|
787
788
|
fullHeight: true,
|
|
789
|
+
lazyLoadingForFullHeight: true,
|
|
788
790
|
} as AppViewConfig);
|
|
789
791
|
|
|
790
|
-
|
|
792
|
+
// Set the iframe before render
|
|
793
|
+
(appEmbed as any).iFrame = document.createElement('iframe');
|
|
794
|
+
|
|
795
|
+
// Wait for render to complete
|
|
796
|
+
await appEmbed.render();
|
|
797
|
+
embedHeightCallback({ data: '100%' });
|
|
791
798
|
|
|
799
|
+
// Verify event handlers were registered
|
|
792
800
|
await executeAfterWait(() => {
|
|
793
801
|
expect(onSpy).toHaveBeenCalledWith(EmbedEvent.EmbedHeight, expect.anything());
|
|
794
802
|
expect(onSpy).toHaveBeenCalledWith(EmbedEvent.RouteChange, expect.anything());
|
|
795
803
|
expect(onSpy).toHaveBeenCalledWith(EmbedEvent.EmbedIframeCenter, expect.anything());
|
|
796
|
-
|
|
797
|
-
|
|
804
|
+
expect(onSpy).toHaveBeenCalledWith(EmbedEvent.RequestVisibleEmbedCoordinates, expect.anything());
|
|
805
|
+
}, 100);
|
|
798
806
|
});
|
|
799
807
|
|
|
800
808
|
describe('Navigate to Page API', () => {
|
|
@@ -882,4 +890,385 @@ describe('App embed tests', () => {
|
|
|
882
890
|
);
|
|
883
891
|
});
|
|
884
892
|
});
|
|
893
|
+
|
|
894
|
+
describe('LazyLoadingForFullHeight functionality', () => {
|
|
895
|
+
let mockIFrame: HTMLIFrameElement;
|
|
896
|
+
|
|
897
|
+
beforeEach(() => {
|
|
898
|
+
mockIFrame = document.createElement('iframe');
|
|
899
|
+
mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
|
|
900
|
+
top: 100,
|
|
901
|
+
left: 150,
|
|
902
|
+
bottom: 600,
|
|
903
|
+
right: 800,
|
|
904
|
+
width: 650,
|
|
905
|
+
height: 500,
|
|
906
|
+
});
|
|
907
|
+
jest.spyOn(document, 'createElement').mockImplementation((tagName) => {
|
|
908
|
+
if (tagName === 'iframe') {
|
|
909
|
+
return mockIFrame;
|
|
910
|
+
}
|
|
911
|
+
return document.createElement(tagName);
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
afterEach(() => {
|
|
916
|
+
jest.restoreAllMocks();
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
test('should set lazyLoadingMargin parameter when provided', async () => {
|
|
920
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
921
|
+
...defaultViewConfig,
|
|
922
|
+
fullHeight: true,
|
|
923
|
+
lazyLoadingForFullHeight: true,
|
|
924
|
+
lazyLoadingMargin: '100px 0px',
|
|
925
|
+
} as AppViewConfig);
|
|
926
|
+
|
|
927
|
+
await appEmbed.render();
|
|
928
|
+
|
|
929
|
+
await executeAfterWait(() => {
|
|
930
|
+
const iframeSrc = getIFrameSrc();
|
|
931
|
+
expect(iframeSrc).toContain('isLazyLoadingForEmbedEnabled=true');
|
|
932
|
+
expect(iframeSrc).toContain('isFullHeightPinboard=true');
|
|
933
|
+
expect(iframeSrc).toContain('rootMarginForLazyLoad=100px%200px');
|
|
934
|
+
}, 100);
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
test('should set isLazyLoadingForEmbedEnabled=true when both fullHeight and lazyLoadingForFullHeight are enabled', async () => {
|
|
938
|
+
// Mock the iframe element first
|
|
939
|
+
mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
|
|
940
|
+
top: 100,
|
|
941
|
+
left: 150,
|
|
942
|
+
bottom: 600,
|
|
943
|
+
right: 800,
|
|
944
|
+
width: 650,
|
|
945
|
+
height: 500,
|
|
946
|
+
});
|
|
947
|
+
Object.defineProperty(mockIFrame, 'scrollHeight', { value: 500 });
|
|
948
|
+
|
|
949
|
+
// Mock the event handlers
|
|
950
|
+
const onSpy = jest.spyOn(AppEmbed.prototype, 'on').mockImplementation((event, callback) => {
|
|
951
|
+
return null;
|
|
952
|
+
});
|
|
953
|
+
jest.spyOn(TsEmbed.prototype as any, 'getIframeCenter').mockReturnValue({});
|
|
954
|
+
jest.spyOn(TsEmbed.prototype as any, 'setIFrameHeight').mockReturnValue({});
|
|
955
|
+
|
|
956
|
+
// Create the AppEmbed instance
|
|
957
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
958
|
+
...defaultViewConfig,
|
|
959
|
+
fullHeight: true,
|
|
960
|
+
lazyLoadingForFullHeight: true,
|
|
961
|
+
} as AppViewConfig);
|
|
962
|
+
|
|
963
|
+
// Set the iframe before render
|
|
964
|
+
(appEmbed as any).iFrame = mockIFrame;
|
|
965
|
+
|
|
966
|
+
// Add the iframe to the DOM
|
|
967
|
+
const rootEl = getRootEl();
|
|
968
|
+
rootEl.appendChild(mockIFrame);
|
|
969
|
+
|
|
970
|
+
// Wait for render to complete
|
|
971
|
+
await appEmbed.render();
|
|
972
|
+
|
|
973
|
+
// Wait for iframe initialization and URL parameters to be set
|
|
974
|
+
await executeAfterWait(() => {
|
|
975
|
+
const iframeSrc = appEmbed.getIFrameSrc();
|
|
976
|
+
expect(iframeSrc).toContain('isLazyLoadingForEmbedEnabled=true');
|
|
977
|
+
expect(iframeSrc).toContain('isFullHeightPinboard=true');
|
|
978
|
+
}, 100);
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
test('should not set lazyLoadingForEmbed when lazyLoadingForFullHeight is enabled but fullHeight is false', async () => {
|
|
982
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
983
|
+
...defaultViewConfig,
|
|
984
|
+
fullHeight: false,
|
|
985
|
+
lazyLoadingForFullHeight: true,
|
|
986
|
+
} as AppViewConfig);
|
|
987
|
+
|
|
988
|
+
// Wait for render to complete
|
|
989
|
+
await appEmbed.render();
|
|
990
|
+
|
|
991
|
+
// Wait for iframe initialization and URL parameters to be set
|
|
992
|
+
await executeAfterWait(() => {
|
|
993
|
+
const iframeSrc = getIFrameSrc();
|
|
994
|
+
expect(iframeSrc).not.toContain('isLazyLoadingForEmbedEnabled=true');
|
|
995
|
+
expect(iframeSrc).not.toContain('isFullHeightPinboard=true');
|
|
996
|
+
}, 100); // 100ms wait time to ensure iframe src is set
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
test('should not set isLazyLoadingForEmbedEnabled when fullHeight is true but lazyLoadingForFullHeight is false', async () => {
|
|
1000
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1001
|
+
...defaultViewConfig,
|
|
1002
|
+
fullHeight: true,
|
|
1003
|
+
lazyLoadingForFullHeight: false,
|
|
1004
|
+
} as AppViewConfig);
|
|
1005
|
+
|
|
1006
|
+
// Wait for render to complete
|
|
1007
|
+
await appEmbed.render();
|
|
1008
|
+
|
|
1009
|
+
// Wait for iframe initialization and URL parameters to be set
|
|
1010
|
+
await executeAfterWait(() => {
|
|
1011
|
+
const iframeSrc = getIFrameSrc();
|
|
1012
|
+
expect(iframeSrc).not.toContain('isLazyLoadingForEmbedEnabled=true');
|
|
1013
|
+
expect(iframeSrc).toContain('isFullHeightPinboard=true');
|
|
1014
|
+
}, 100); // 100ms wait time to ensure iframe src is set
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
test('should register RequestFullHeightLazyLoadData event handler when fullHeight is enabled', async () => {
|
|
1018
|
+
const onSpy = jest.spyOn(AppEmbed.prototype, 'on');
|
|
1019
|
+
|
|
1020
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1021
|
+
...defaultViewConfig,
|
|
1022
|
+
fullHeight: true,
|
|
1023
|
+
} as AppViewConfig);
|
|
1024
|
+
|
|
1025
|
+
await appEmbed.render();
|
|
1026
|
+
|
|
1027
|
+
expect(onSpy).toHaveBeenCalledWith(EmbedEvent.RequestVisibleEmbedCoordinates, expect.any(Function));
|
|
1028
|
+
|
|
1029
|
+
onSpy.mockRestore();
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
test('should send correct visible data when RequestFullHeightLazyLoadData is triggered', async () => {
|
|
1033
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1034
|
+
...defaultViewConfig,
|
|
1035
|
+
fullHeight: true,
|
|
1036
|
+
lazyLoadingForFullHeight: true,
|
|
1037
|
+
} as AppViewConfig);
|
|
1038
|
+
|
|
1039
|
+
const mockTrigger = jest.spyOn(appEmbed, 'trigger');
|
|
1040
|
+
|
|
1041
|
+
await appEmbed.render();
|
|
1042
|
+
|
|
1043
|
+
// Trigger the lazy load data calculation
|
|
1044
|
+
(appEmbed as any).sendFullHeightLazyLoadData();
|
|
1045
|
+
|
|
1046
|
+
expect(mockTrigger).toHaveBeenCalledWith(HostEvent.VisibleEmbedCoordinates, {
|
|
1047
|
+
top: 0,
|
|
1048
|
+
height: 500,
|
|
1049
|
+
left: 0,
|
|
1050
|
+
width: 650,
|
|
1051
|
+
});
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
test('should calculate correct visible data for partially visible full height element', async () => {
|
|
1055
|
+
// Mock iframe partially clipped from top and left
|
|
1056
|
+
mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
|
|
1057
|
+
top: -50,
|
|
1058
|
+
left: -30,
|
|
1059
|
+
bottom: 700,
|
|
1060
|
+
right: 1024,
|
|
1061
|
+
width: 1054,
|
|
1062
|
+
height: 750,
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1066
|
+
...defaultViewConfig,
|
|
1067
|
+
fullHeight: true,
|
|
1068
|
+
lazyLoadingForFullHeight: true,
|
|
1069
|
+
} as AppViewConfig);
|
|
1070
|
+
|
|
1071
|
+
const mockTrigger = jest.spyOn(appEmbed, 'trigger');
|
|
1072
|
+
|
|
1073
|
+
await appEmbed.render();
|
|
1074
|
+
|
|
1075
|
+
// Trigger the lazy load data calculation
|
|
1076
|
+
(appEmbed as any).sendFullHeightLazyLoadData();
|
|
1077
|
+
|
|
1078
|
+
expect(mockTrigger).toHaveBeenCalledWith(HostEvent.VisibleEmbedCoordinates, {
|
|
1079
|
+
top: 50, // 50px clipped from top
|
|
1080
|
+
height: 700, // visible height (from 0 to 700)
|
|
1081
|
+
left: 30, // 30px clipped from left
|
|
1082
|
+
width: 1024, // visible width (from 0 to 1024)
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
test('should add window event listeners for resize and scroll when fullHeight and lazyLoadingForFullHeight are enabled', async () => {
|
|
1087
|
+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
|
1088
|
+
|
|
1089
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1090
|
+
...defaultViewConfig,
|
|
1091
|
+
fullHeight: true,
|
|
1092
|
+
lazyLoadingForFullHeight: true,
|
|
1093
|
+
} as AppViewConfig);
|
|
1094
|
+
|
|
1095
|
+
await appEmbed.render();
|
|
1096
|
+
|
|
1097
|
+
// Wait for the post-render events to be registered
|
|
1098
|
+
await executeAfterWait(() => {
|
|
1099
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function));
|
|
1100
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function), true);
|
|
1101
|
+
}, 100);
|
|
1102
|
+
|
|
1103
|
+
addEventListenerSpy.mockRestore();
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
test('should remove window event listeners on destroy when fullHeight and lazyLoadingForFullHeight are enabled', async () => {
|
|
1107
|
+
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
|
|
1108
|
+
|
|
1109
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1110
|
+
...defaultViewConfig,
|
|
1111
|
+
fullHeight: true,
|
|
1112
|
+
lazyLoadingForFullHeight: true,
|
|
1113
|
+
} as AppViewConfig);
|
|
1114
|
+
|
|
1115
|
+
await appEmbed.render();
|
|
1116
|
+
appEmbed.destroy();
|
|
1117
|
+
|
|
1118
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function));
|
|
1119
|
+
expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
1120
|
+
|
|
1121
|
+
removeEventListenerSpy.mockRestore();
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
test('should handle RequestVisibleEmbedCoordinates event and respond with correct data', async () => {
|
|
1125
|
+
// Mock the iframe element
|
|
1126
|
+
mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
|
|
1127
|
+
top: 100,
|
|
1128
|
+
left: 150,
|
|
1129
|
+
bottom: 600,
|
|
1130
|
+
right: 800,
|
|
1131
|
+
width: 650,
|
|
1132
|
+
height: 500,
|
|
1133
|
+
});
|
|
1134
|
+
Object.defineProperty(mockIFrame, 'scrollHeight', { value: 500 });
|
|
1135
|
+
|
|
1136
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1137
|
+
...defaultViewConfig,
|
|
1138
|
+
fullHeight: true,
|
|
1139
|
+
lazyLoadingForFullHeight: true,
|
|
1140
|
+
} as AppViewConfig);
|
|
1141
|
+
|
|
1142
|
+
// Set the iframe before render
|
|
1143
|
+
(appEmbed as any).iFrame = mockIFrame;
|
|
1144
|
+
|
|
1145
|
+
await appEmbed.render();
|
|
1146
|
+
|
|
1147
|
+
// Create a mock responder function
|
|
1148
|
+
const mockResponder = jest.fn();
|
|
1149
|
+
|
|
1150
|
+
// Trigger the handler directly
|
|
1151
|
+
(appEmbed as any).requestVisibleEmbedCoordinatesHandler({}, mockResponder);
|
|
1152
|
+
|
|
1153
|
+
// Verify the responder was called with the correct data
|
|
1154
|
+
expect(mockResponder).toHaveBeenCalledWith({
|
|
1155
|
+
type: EmbedEvent.RequestVisibleEmbedCoordinates,
|
|
1156
|
+
data: {
|
|
1157
|
+
top: 0,
|
|
1158
|
+
height: 500,
|
|
1159
|
+
left: 0,
|
|
1160
|
+
width: 650,
|
|
1161
|
+
},
|
|
1162
|
+
});
|
|
1163
|
+
});
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
describe('IFrame height management', () => {
|
|
1167
|
+
let mockIFrame: HTMLIFrameElement;
|
|
1168
|
+
|
|
1169
|
+
beforeEach(() => {
|
|
1170
|
+
mockIFrame = document.createElement('iframe');
|
|
1171
|
+
mockIFrame.getBoundingClientRect = jest.fn().mockReturnValue({
|
|
1172
|
+
top: 100,
|
|
1173
|
+
left: 150,
|
|
1174
|
+
bottom: 600,
|
|
1175
|
+
right: 800,
|
|
1176
|
+
width: 650,
|
|
1177
|
+
height: 500,
|
|
1178
|
+
});
|
|
1179
|
+
Object.defineProperty(mockIFrame, 'scrollHeight', { value: 500 });
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
test('should not call setIFrameHeight if currentPath starts with "/embed/viz/"', () => {
|
|
1183
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1184
|
+
...defaultViewConfig,
|
|
1185
|
+
fullHeight: true,
|
|
1186
|
+
} as AppViewConfig) as any;
|
|
1187
|
+
const spySetIFrameHeight = jest.spyOn(appEmbed, 'setIFrameHeight');
|
|
1188
|
+
|
|
1189
|
+
appEmbed.render();
|
|
1190
|
+
appEmbed.setIframeHeightForNonEmbedLiveboard({
|
|
1191
|
+
data: { currentPath: '/embed/viz/' },
|
|
1192
|
+
type: 'Route',
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
expect(spySetIFrameHeight).not.toHaveBeenCalled();
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
test('should not call setIFrameHeight if currentPath starts with "/embed/insights/viz/"', () => {
|
|
1199
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1200
|
+
...defaultViewConfig,
|
|
1201
|
+
fullHeight: true,
|
|
1202
|
+
} as AppViewConfig) as any;
|
|
1203
|
+
const spySetIFrameHeight = jest.spyOn(appEmbed, 'setIFrameHeight');
|
|
1204
|
+
|
|
1205
|
+
appEmbed.render();
|
|
1206
|
+
appEmbed.setIframeHeightForNonEmbedLiveboard({
|
|
1207
|
+
data: { currentPath: '/embed/insights/viz/' },
|
|
1208
|
+
type: 'Route',
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
expect(spySetIFrameHeight).not.toHaveBeenCalled();
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
test('should call setIFrameHeight if currentPath starts with "/some/other/path/"', () => {
|
|
1215
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1216
|
+
...defaultViewConfig,
|
|
1217
|
+
fullHeight: true,
|
|
1218
|
+
} as AppViewConfig) as any;
|
|
1219
|
+
const spySetIFrameHeight = jest
|
|
1220
|
+
.spyOn(appEmbed, 'setIFrameHeight')
|
|
1221
|
+
.mockImplementation(jest.fn());
|
|
1222
|
+
|
|
1223
|
+
appEmbed.render();
|
|
1224
|
+
appEmbed.setIframeHeightForNonEmbedLiveboard({
|
|
1225
|
+
data: { currentPath: '/some/other/path/' },
|
|
1226
|
+
type: 'Route',
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
expect(spySetIFrameHeight).toHaveBeenCalled();
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
test('should update iframe height correctly', async () => {
|
|
1233
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1234
|
+
...defaultViewConfig,
|
|
1235
|
+
fullHeight: true,
|
|
1236
|
+
} as AppViewConfig) as any;
|
|
1237
|
+
|
|
1238
|
+
// Set up the mock iframe
|
|
1239
|
+
appEmbed.iFrame = mockIFrame;
|
|
1240
|
+
document.body.appendChild(mockIFrame);
|
|
1241
|
+
|
|
1242
|
+
await appEmbed.render();
|
|
1243
|
+
const mockEvent = {
|
|
1244
|
+
data: 600,
|
|
1245
|
+
type: EmbedEvent.EmbedHeight,
|
|
1246
|
+
};
|
|
1247
|
+
appEmbed.updateIFrameHeight(mockEvent);
|
|
1248
|
+
|
|
1249
|
+
// Check if the iframe style was updated
|
|
1250
|
+
expect(mockIFrame.style.height).toBe('600px');
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
test('should handle updateIFrameHeight with default height', async () => {
|
|
1254
|
+
const appEmbed = new AppEmbed(getRootEl(), {
|
|
1255
|
+
...defaultViewConfig,
|
|
1256
|
+
fullHeight: true,
|
|
1257
|
+
} as AppViewConfig) as any;
|
|
1258
|
+
|
|
1259
|
+
// Set up the mock iframe
|
|
1260
|
+
appEmbed.iFrame = mockIFrame;
|
|
1261
|
+
document.body.appendChild(mockIFrame);
|
|
1262
|
+
|
|
1263
|
+
await appEmbed.render();
|
|
1264
|
+
const mockEvent = {
|
|
1265
|
+
data: 0, // This will make it use the scrollHeight
|
|
1266
|
+
type: EmbedEvent.EmbedHeight,
|
|
1267
|
+
};
|
|
1268
|
+
appEmbed.updateIFrameHeight(mockEvent);
|
|
1269
|
+
|
|
1270
|
+
// Should use the scrollHeight
|
|
1271
|
+
expect(mockIFrame.style.height).toBe('500px');
|
|
1272
|
+
});
|
|
1273
|
+
});
|
|
885
1274
|
});
|
package/src/embed/app.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { logger } from '../utils/logger';
|
|
12
|
-
import { getQueryParamString } from '../utils';
|
|
12
|
+
import { calculateVisibleElementData, getQueryParamString } from '../utils';
|
|
13
13
|
import {
|
|
14
14
|
Param,
|
|
15
15
|
DOMSelector,
|
|
@@ -23,7 +23,7 @@ import { V1Embed } from './ts-embed';
|
|
|
23
23
|
/**
|
|
24
24
|
* Pages within the ThoughtSpot app that can be embedded.
|
|
25
25
|
*/
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
export enum Page {
|
|
28
28
|
/**
|
|
29
29
|
* Home page
|
|
@@ -93,7 +93,7 @@ export enum PrimaryNavbarVersion {
|
|
|
93
93
|
* Sliding (v3) introduces a new left-side navigation hub featuring a tab switcher,
|
|
94
94
|
* along with updates to the top navigation bar.
|
|
95
95
|
* It serves as the foundational version of the PrimaryNavBar.
|
|
96
|
-
|
|
96
|
+
*/
|
|
97
97
|
Sliding = 'v3',
|
|
98
98
|
}
|
|
99
99
|
|
|
@@ -103,9 +103,9 @@ export enum PrimaryNavbarVersion {
|
|
|
103
103
|
*/
|
|
104
104
|
export enum HomePage {
|
|
105
105
|
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
* Modular (v2) introduces the updated Modular Home Experience.
|
|
107
|
+
* It serves as the foundational version of the home page.
|
|
108
|
+
*/
|
|
109
109
|
Modular = 'v2',
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -531,6 +531,48 @@ export interface AppViewConfig extends AllEmbedViewConfig {
|
|
|
531
531
|
* ```
|
|
532
532
|
*/
|
|
533
533
|
isLiveboardStylingAndGroupingEnabled?: boolean;
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* This flag is used to enable the full height lazy load data.
|
|
537
|
+
*
|
|
538
|
+
* @example
|
|
539
|
+
* ```js
|
|
540
|
+
* const embed = new AppEmbed('#embed-container', {
|
|
541
|
+
* // ...other options
|
|
542
|
+
* fullHeight: true,
|
|
543
|
+
* lazyLoadingForFullHeight: true,
|
|
544
|
+
* })
|
|
545
|
+
* ```
|
|
546
|
+
*
|
|
547
|
+
* @type {boolean}
|
|
548
|
+
* @default false
|
|
549
|
+
* @version SDK: 1.40.0 | ThoughtSpot:10.12.0.cl
|
|
550
|
+
*/
|
|
551
|
+
lazyLoadingForFullHeight?: boolean;
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* The margin to be used for lazy loading.
|
|
555
|
+
*
|
|
556
|
+
* For example, if the margin is set to '10px',
|
|
557
|
+
* the visualization will be loaded 10px before the its top edge is visible in the
|
|
558
|
+
* viewport.
|
|
559
|
+
*
|
|
560
|
+
* The format is similar to CSS margin.
|
|
561
|
+
*
|
|
562
|
+
* @example
|
|
563
|
+
* ```js
|
|
564
|
+
* const embed = new AppEmbed('#embed-container', {
|
|
565
|
+
* // ...other options
|
|
566
|
+
* fullHeight: true,
|
|
567
|
+
* lazyLoadingForFullHeight: true,
|
|
568
|
+
* // Using 0px, the visualization will be only loaded when its visible in the viewport.
|
|
569
|
+
* lazyLoadingMargin: '0px',
|
|
570
|
+
* })
|
|
571
|
+
* ```
|
|
572
|
+
* @type {string}
|
|
573
|
+
* @version SDK: 1.40.0 | ThoughtSpot:10.12.0.cl
|
|
574
|
+
*/
|
|
575
|
+
lazyLoadingMargin?: string;
|
|
534
576
|
}
|
|
535
577
|
|
|
536
578
|
/**
|
|
@@ -542,7 +584,7 @@ export class AppEmbed extends V1Embed {
|
|
|
542
584
|
|
|
543
585
|
private defaultHeight = '100%';
|
|
544
586
|
|
|
545
|
-
|
|
587
|
+
|
|
546
588
|
constructor(domSelector: DOMSelector, viewConfig: AppViewConfig) {
|
|
547
589
|
viewConfig.embedComponentType = 'AppEmbed';
|
|
548
590
|
super(domSelector, viewConfig);
|
|
@@ -550,6 +592,7 @@ export class AppEmbed extends V1Embed {
|
|
|
550
592
|
this.on(EmbedEvent.RouteChange, this.setIframeHeightForNonEmbedLiveboard);
|
|
551
593
|
this.on(EmbedEvent.EmbedHeight, this.updateIFrameHeight);
|
|
552
594
|
this.on(EmbedEvent.EmbedIframeCenter, this.embedIframeCenter);
|
|
595
|
+
this.on(EmbedEvent.RequestVisibleEmbedCoordinates, this.requestVisibleEmbedCoordinatesHandler);
|
|
553
596
|
}
|
|
554
597
|
}
|
|
555
598
|
|
|
@@ -583,7 +626,7 @@ export class AppEmbed extends V1Embed {
|
|
|
583
626
|
enable2ColumnLayout,
|
|
584
627
|
enableCustomColumnGroups = false,
|
|
585
628
|
isOnBeforeGetVizDataInterceptEnabled = false,
|
|
586
|
-
|
|
629
|
+
|
|
587
630
|
dataPanelCustomGroupsAccordionInitialState = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL,
|
|
588
631
|
collapseSearchBar = true,
|
|
589
632
|
isLiveboardCompactHeaderEnabled = false,
|
|
@@ -598,7 +641,7 @@ export class AppEmbed extends V1Embed {
|
|
|
598
641
|
isLiveboardStylingAndGroupingEnabled,
|
|
599
642
|
} = this.viewConfig;
|
|
600
643
|
|
|
601
|
-
let params = {};
|
|
644
|
+
let params: any = {};
|
|
602
645
|
params[Param.PrimaryNavHidden] = !showPrimaryNavbar;
|
|
603
646
|
params[Param.HideProfleAndHelp] = !!disableProfileAndHelp;
|
|
604
647
|
params[Param.HideApplicationSwitcher] = !!hideApplicationSwitcher;
|
|
@@ -631,6 +674,10 @@ export class AppEmbed extends V1Embed {
|
|
|
631
674
|
|
|
632
675
|
if (fullHeight === true) {
|
|
633
676
|
params[Param.fullHeight] = true;
|
|
677
|
+
if (this.viewConfig.lazyLoadingForFullHeight) {
|
|
678
|
+
params[Param.IsLazyLoadingForEmbedEnabled] = true;
|
|
679
|
+
params[Param.RootMarginForLazyLoad] = this.viewConfig.lazyLoadingMargin;
|
|
680
|
+
}
|
|
634
681
|
}
|
|
635
682
|
|
|
636
683
|
if (tag) {
|
|
@@ -656,7 +703,7 @@ export class AppEmbed extends V1Embed {
|
|
|
656
703
|
}
|
|
657
704
|
|
|
658
705
|
if (isOnBeforeGetVizDataInterceptEnabled) {
|
|
659
|
-
|
|
706
|
+
|
|
660
707
|
params[
|
|
661
708
|
Param.IsOnBeforeGetVizDataInterceptEnabled
|
|
662
709
|
] = isOnBeforeGetVizDataInterceptEnabled;
|
|
@@ -684,12 +731,12 @@ export class AppEmbed extends V1Embed {
|
|
|
684
731
|
|| dataPanelCustomGroupsAccordionInitialState
|
|
685
732
|
=== DataPanelCustomColumnGroupsAccordionState.EXPAND_FIRST
|
|
686
733
|
) {
|
|
687
|
-
|
|
734
|
+
|
|
688
735
|
params[
|
|
689
736
|
Param.DataPanelCustomGroupsAccordionInitialState
|
|
690
737
|
] = dataPanelCustomGroupsAccordionInitialState;
|
|
691
738
|
} else {
|
|
692
|
-
|
|
739
|
+
|
|
693
740
|
params[Param.DataPanelCustomGroupsAccordionInitialState] = DataPanelCustomColumnGroupsAccordionState.EXPAND_ALL;
|
|
694
741
|
}
|
|
695
742
|
|
|
@@ -715,6 +762,23 @@ export class AppEmbed extends V1Embed {
|
|
|
715
762
|
return queryParams;
|
|
716
763
|
}
|
|
717
764
|
|
|
765
|
+
private sendFullHeightLazyLoadData = () => {
|
|
766
|
+
const data = calculateVisibleElementData(this.iFrame);
|
|
767
|
+
this.trigger(HostEvent.VisibleEmbedCoordinates, data);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* This is a handler for the RequestVisibleEmbedCoordinates event.
|
|
772
|
+
* It is used to send the visible coordinates data to the host application.
|
|
773
|
+
* @param data The event payload
|
|
774
|
+
* @param responder The responder function
|
|
775
|
+
*/
|
|
776
|
+
private requestVisibleEmbedCoordinatesHandler = (data: MessagePayload, responder: any) => {
|
|
777
|
+
logger.info('Sending RequestVisibleEmbedCoordinates', data);
|
|
778
|
+
const visibleCoordinatesData = calculateVisibleElementData(this.iFrame);
|
|
779
|
+
responder({ type: EmbedEvent.RequestVisibleEmbedCoordinates, data: visibleCoordinatesData });
|
|
780
|
+
}
|
|
781
|
+
|
|
718
782
|
/**
|
|
719
783
|
* Constructs the URL of the ThoughtSpot app page to be rendered.
|
|
720
784
|
* @param pageId The ID of the page to be embedded.
|
|
@@ -737,6 +801,7 @@ export class AppEmbed extends V1Embed {
|
|
|
737
801
|
*/
|
|
738
802
|
protected updateIFrameHeight = (data: MessagePayload) => {
|
|
739
803
|
this.setIFrameHeight(Math.max(data.data, this.iFrame?.scrollHeight));
|
|
804
|
+
this.sendFullHeightLazyLoadData();
|
|
740
805
|
};
|
|
741
806
|
|
|
742
807
|
private embedIframeCenter = (data: MessagePayload, responder: any) => {
|
|
@@ -845,6 +910,34 @@ export class AppEmbed extends V1Embed {
|
|
|
845
910
|
}
|
|
846
911
|
}
|
|
847
912
|
|
|
913
|
+
/**
|
|
914
|
+
* Destroys the ThoughtSpot embed, and remove any nodes from the DOM.
|
|
915
|
+
* @version SDK: 1.39.0 | ThoughtSpot: 10.10.0.cl
|
|
916
|
+
*/
|
|
917
|
+
public destroy() {
|
|
918
|
+
super.destroy();
|
|
919
|
+
this.unregisterLazyLoadEvents();
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
private postRender() {
|
|
923
|
+
this.registerLazyLoadEvents();
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
private registerLazyLoadEvents() {
|
|
927
|
+
if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
|
|
928
|
+
// TODO: Use passive: true, install modernizr to check for passive
|
|
929
|
+
window.addEventListener('resize', this.sendFullHeightLazyLoadData);
|
|
930
|
+
window.addEventListener('scroll', this.sendFullHeightLazyLoadData, true);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
private unregisterLazyLoadEvents() {
|
|
935
|
+
if (this.viewConfig.fullHeight && this.viewConfig.lazyLoadingForFullHeight) {
|
|
936
|
+
window.removeEventListener('resize', this.sendFullHeightLazyLoadData);
|
|
937
|
+
window.removeEventListener('scroll', this.sendFullHeightLazyLoadData);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
848
941
|
/**
|
|
849
942
|
* Renders the embedded application pages in the ThoughtSpot app.
|
|
850
943
|
* @param renderOptions An object containing the page ID
|
|
@@ -856,6 +949,7 @@ export class AppEmbed extends V1Embed {
|
|
|
856
949
|
const src = this.getIFrameSrc();
|
|
857
950
|
await this.renderV1Embed(src);
|
|
858
951
|
|
|
952
|
+
this.postRender();
|
|
859
953
|
return this;
|
|
860
954
|
}
|
|
861
955
|
}
|