datajunction-ui 0.0.44 → 0.0.45

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.
@@ -324,37 +324,6 @@ describe('QueryPlannerPage', () => {
324
324
  expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
325
325
  });
326
326
  });
327
-
328
- it('shows clear button when search has value', async () => {
329
- renderPage();
330
-
331
- await waitFor(() => {
332
- expect(mockDjClient.metrics).toHaveBeenCalled();
333
- });
334
-
335
- const searchInput = screen.getByPlaceholderText('Search metrics...');
336
- fireEvent.change(searchInput, { target: { value: 'test' } });
337
-
338
- // Clear button should appear
339
- const clearButton = screen.getAllByText('×')[0];
340
- expect(clearButton).toBeInTheDocument();
341
- });
342
-
343
- it('clears search when clear button is clicked', async () => {
344
- renderPage();
345
-
346
- await waitFor(() => {
347
- expect(mockDjClient.metrics).toHaveBeenCalled();
348
- });
349
-
350
- const searchInput = screen.getByPlaceholderText('Search metrics...');
351
- fireEvent.change(searchInput, { target: { value: 'test' } });
352
-
353
- const clearButton = screen.getAllByText('×')[0];
354
- fireEvent.click(clearButton);
355
-
356
- expect(searchInput.value).toBe('');
357
- });
358
327
  });
359
328
 
360
329
  describe('Cube Preset Loading', () => {
@@ -678,7 +647,7 @@ describe('QueryPlannerPage', () => {
678
647
  });
679
648
 
680
649
  describe('Clear Selection', () => {
681
- it('clears all selections when Clear all is clicked', async () => {
650
+ it('clears all selections when Clear is clicked', async () => {
682
651
  mockDjClient.cubeForPlanner.mockResolvedValue(mockCubeData);
683
652
 
684
653
  renderPage();
@@ -700,8 +669,8 @@ describe('QueryPlannerPage', () => {
700
669
  expect(mockDjClient.commonDimensions).toHaveBeenCalled();
701
670
  });
702
671
 
703
- // Click Clear all
704
- const clearButton = screen.getByText('Clear all');
672
+ // Click the global Clear button (clear-all-btn class)
673
+ const clearButton = document.querySelector('.clear-all-btn');
705
674
  fireEvent.click(clearButton);
706
675
 
707
676
  // Should show "Load from Cube" again (cube unloaded)
@@ -739,4 +708,721 @@ describe('QueryPlannerPage', () => {
739
708
  });
740
709
  });
741
710
  });
711
+
712
+ describe('Filter Handling', () => {
713
+ it('adds a filter when add filter button is clicked', async () => {
714
+ renderPage();
715
+
716
+ await waitFor(() => {
717
+ expect(mockDjClient.metrics).toHaveBeenCalled();
718
+ });
719
+
720
+ // Find filter input and add a filter
721
+ const filterInput = screen.getByPlaceholderText(
722
+ /e\.g\. v3\.date\.date_id/i,
723
+ );
724
+ fireEvent.change(filterInput, {
725
+ target: { value: "date_id >= '2024-01-01'" },
726
+ });
727
+
728
+ const addButton = screen.getByText('Add');
729
+ fireEvent.click(addButton);
730
+
731
+ // Filter should be added (check for chip)
732
+ await waitFor(() => {
733
+ expect(screen.getByText("date_id >= '2024-01-01'")).toBeInTheDocument();
734
+ });
735
+ });
736
+
737
+ it('removes a filter when remove button is clicked', async () => {
738
+ renderPage();
739
+
740
+ await waitFor(() => {
741
+ expect(mockDjClient.metrics).toHaveBeenCalled();
742
+ });
743
+
744
+ // Add a filter first
745
+ const filterInput = screen.getByPlaceholderText(
746
+ /e\.g\. v3\.date\.date_id/i,
747
+ );
748
+ fireEvent.change(filterInput, {
749
+ target: { value: "status = 'active'" },
750
+ });
751
+ fireEvent.click(screen.getByText('Add'));
752
+
753
+ await waitFor(() => {
754
+ expect(screen.getByText("status = 'active'")).toBeInTheDocument();
755
+ });
756
+
757
+ // Remove the filter (button has generic title "Remove filter")
758
+ const removeButton = screen.getByTitle('Remove filter');
759
+ fireEvent.click(removeButton);
760
+
761
+ await waitFor(() => {
762
+ expect(screen.queryByText("status = 'active'")).not.toBeInTheDocument();
763
+ });
764
+ });
765
+
766
+ it('disables add button when filter input is empty', async () => {
767
+ renderPage();
768
+
769
+ await waitFor(() => {
770
+ expect(mockDjClient.metrics).toHaveBeenCalled();
771
+ });
772
+
773
+ const addButton = screen.getByText('Add');
774
+ expect(addButton).toBeDisabled();
775
+ });
776
+ });
777
+
778
+ describe('Run Query', () => {
779
+ beforeEach(() => {
780
+ mockDjClient.metricsV3.mockResolvedValue({
781
+ sql: 'SELECT * FROM metrics',
782
+ dialect: 'SPARK',
783
+ cube_name: null,
784
+ });
785
+ });
786
+
787
+ it('shows Run Query button when metrics and dimensions selected', async () => {
788
+ renderPage();
789
+
790
+ await waitFor(() => {
791
+ expect(mockDjClient.metrics).toHaveBeenCalled();
792
+ });
793
+
794
+ // Select metric
795
+ fireEvent.click(screen.getByText('default'));
796
+ await waitFor(() => {
797
+ fireEvent.click(
798
+ screen.getByRole('checkbox', { name: /num_repair_orders/i }),
799
+ );
800
+ });
801
+
802
+ await waitFor(() => {
803
+ expect(mockDjClient.commonDimensions).toHaveBeenCalled();
804
+ });
805
+
806
+ // Select dimension
807
+ fireEvent.click(screen.getByRole('checkbox', { name: /dateint/i }));
808
+
809
+ // Run Query button should exist
810
+ await waitFor(() => {
811
+ expect(screen.getByText('Run Query')).toBeInTheDocument();
812
+ });
813
+ });
814
+
815
+ it('disables Run Query button when no metrics selected', async () => {
816
+ renderPage();
817
+
818
+ await waitFor(() => {
819
+ expect(mockDjClient.metrics).toHaveBeenCalled();
820
+ });
821
+
822
+ const runButton = screen.getByText('Run Query');
823
+ expect(runButton).toBeDisabled();
824
+ });
825
+ });
826
+
827
+ describe('Materialization Handlers', () => {
828
+ const mockMeasuresResult = {
829
+ grainGroups: [
830
+ {
831
+ node: 'default.repair_orders',
832
+ grain_columns: ['default.date_dim.dateint'],
833
+ measures: [
834
+ { name: 'sum_revenue', expression: 'SUM(revenue)' },
835
+ { name: 'count_orders', expression: 'COUNT(*)' },
836
+ ],
837
+ },
838
+ ],
839
+ metricFormulas: [
840
+ {
841
+ metric: 'default.num_repair_orders',
842
+ formula: 'default.repair_orders.count_orders',
843
+ },
844
+ ],
845
+ };
846
+
847
+ beforeEach(() => {
848
+ mockDjClient.measuresV3.mockResolvedValue(mockMeasuresResult);
849
+ mockDjClient.metricsV3.mockResolvedValue({
850
+ sql: 'SELECT * FROM metrics',
851
+ dialect: 'SPARK',
852
+ cube_name: null,
853
+ });
854
+ mockDjClient.listPreaggs.mockResolvedValue({ items: [] });
855
+ mockDjClient.getNodeColumnsWithPartitions.mockResolvedValue({
856
+ columns: [],
857
+ temporalPartitions: [],
858
+ });
859
+ });
860
+
861
+ it('calls planPreaggs when materialization is planned', async () => {
862
+ mockDjClient.planPreaggs.mockResolvedValue({
863
+ preaggs: [
864
+ {
865
+ id: 1,
866
+ node_name: 'default.repair_orders',
867
+ grain_columns: ['default.date_dim.dateint'],
868
+ },
869
+ ],
870
+ });
871
+
872
+ renderPage();
873
+
874
+ await waitFor(() => {
875
+ expect(mockDjClient.metrics).toHaveBeenCalled();
876
+ });
877
+
878
+ // Select metric and dimension
879
+ fireEvent.click(screen.getByText('default'));
880
+ await waitFor(() => {
881
+ fireEvent.click(
882
+ screen.getByRole('checkbox', { name: /num_repair_orders/i }),
883
+ );
884
+ });
885
+
886
+ await waitFor(() => {
887
+ expect(mockDjClient.commonDimensions).toHaveBeenCalled();
888
+ });
889
+
890
+ fireEvent.click(screen.getByRole('checkbox', { name: /dateint/i }));
891
+
892
+ await waitFor(() => {
893
+ expect(mockDjClient.measuresV3).toHaveBeenCalled();
894
+ });
895
+ });
896
+
897
+ it('handles planPreaggs error gracefully', async () => {
898
+ mockDjClient.planPreaggs.mockResolvedValue({
899
+ _error: true,
900
+ message: 'Failed to plan',
901
+ });
902
+
903
+ renderPage();
904
+
905
+ await waitFor(() => {
906
+ expect(mockDjClient.metrics).toHaveBeenCalled();
907
+ });
908
+ });
909
+
910
+ it('calls materializePreagg when workflow is created', async () => {
911
+ mockDjClient.planPreaggs.mockResolvedValue({
912
+ preaggs: [
913
+ {
914
+ id: 123,
915
+ node_name: 'default.repair_orders',
916
+ grain_columns: ['default.date_dim.dateint'],
917
+ },
918
+ ],
919
+ });
920
+ mockDjClient.materializePreagg.mockResolvedValue({
921
+ workflow_urls: ['http://workflow.example.com'],
922
+ workflow_status: 'active',
923
+ });
924
+
925
+ renderPage();
926
+
927
+ await waitFor(() => {
928
+ expect(mockDjClient.metrics).toHaveBeenCalled();
929
+ });
930
+ });
931
+
932
+ it('handles materializePreagg error', async () => {
933
+ mockDjClient.materializePreagg.mockResolvedValue({
934
+ _error: true,
935
+ message: 'Failed to create workflow',
936
+ });
937
+
938
+ renderPage();
939
+
940
+ await waitFor(() => {
941
+ expect(mockDjClient.metrics).toHaveBeenCalled();
942
+ });
943
+ });
944
+
945
+ it('calls runPreaggBackfill when backfill is triggered', async () => {
946
+ mockDjClient.runPreaggBackfill.mockResolvedValue({
947
+ job_url: 'http://job.example.com',
948
+ });
949
+
950
+ renderPage();
951
+
952
+ await waitFor(() => {
953
+ expect(mockDjClient.metrics).toHaveBeenCalled();
954
+ });
955
+ });
956
+
957
+ it('handles runPreaggBackfill error', async () => {
958
+ mockDjClient.runPreaggBackfill.mockResolvedValue({
959
+ _error: true,
960
+ message: 'Backfill failed',
961
+ });
962
+
963
+ renderPage();
964
+
965
+ await waitFor(() => {
966
+ expect(mockDjClient.metrics).toHaveBeenCalled();
967
+ });
968
+ });
969
+
970
+ it('calls updatePreaggConfig for config updates', async () => {
971
+ mockDjClient.updatePreaggConfig.mockResolvedValue({
972
+ id: 123,
973
+ strategy: 'incremental_time',
974
+ schedule: '0 6 * * *',
975
+ });
976
+
977
+ renderPage();
978
+
979
+ await waitFor(() => {
980
+ expect(mockDjClient.metrics).toHaveBeenCalled();
981
+ });
982
+ });
983
+
984
+ it('handles updatePreaggConfig error', async () => {
985
+ mockDjClient.updatePreaggConfig.mockResolvedValue({
986
+ _error: true,
987
+ message: 'Config update failed',
988
+ });
989
+
990
+ renderPage();
991
+
992
+ await waitFor(() => {
993
+ expect(mockDjClient.metrics).toHaveBeenCalled();
994
+ });
995
+ });
996
+
997
+ it('calls deactivatePreaggWorkflow when workflow is deactivated', async () => {
998
+ mockDjClient.deactivatePreaggWorkflow.mockResolvedValue({
999
+ success: true,
1000
+ });
1001
+
1002
+ renderPage();
1003
+
1004
+ await waitFor(() => {
1005
+ expect(mockDjClient.metrics).toHaveBeenCalled();
1006
+ });
1007
+ });
1008
+
1009
+ it('handles deactivatePreaggWorkflow error', async () => {
1010
+ mockDjClient.deactivatePreaggWorkflow.mockResolvedValue({
1011
+ _error: true,
1012
+ message: 'Failed to deactivate',
1013
+ });
1014
+
1015
+ renderPage();
1016
+
1017
+ await waitFor(() => {
1018
+ expect(mockDjClient.metrics).toHaveBeenCalled();
1019
+ });
1020
+ });
1021
+ });
1022
+
1023
+ describe('Cube Materialization Flow', () => {
1024
+ beforeEach(() => {
1025
+ mockDjClient.listCubesForPreset.mockResolvedValue([
1026
+ { name: 'default.test_cube', display_name: 'Test Cube' },
1027
+ ]);
1028
+ mockDjClient.cubeForPlanner.mockResolvedValue({
1029
+ name: 'default.test_cube',
1030
+ display_name: 'Test Cube',
1031
+ cube_node_metrics: ['default.num_repair_orders'],
1032
+ cube_node_dimensions: ['default.date_dim.dateint'],
1033
+ cubeMaterialization: {
1034
+ strategy: 'full',
1035
+ schedule: '0 6 * * *',
1036
+ lookbackWindow: null,
1037
+ druidDatasource: 'test_ds',
1038
+ preaggTables: [],
1039
+ workflowUrls: ['http://workflow.url'],
1040
+ },
1041
+ availability: null,
1042
+ });
1043
+ });
1044
+
1045
+ it('calls createCube when new cube is created', async () => {
1046
+ mockDjClient.createCube.mockResolvedValue({
1047
+ status: 200,
1048
+ json: { name: 'new.cube' },
1049
+ });
1050
+
1051
+ renderPage();
1052
+
1053
+ await waitFor(() => {
1054
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1055
+ });
1056
+ });
1057
+
1058
+ it('handles createCube error - cube already exists', async () => {
1059
+ mockDjClient.createCube.mockResolvedValue({
1060
+ status: 400,
1061
+ json: { message: 'Cube already exists' },
1062
+ });
1063
+
1064
+ renderPage();
1065
+
1066
+ await waitFor(() => {
1067
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1068
+ });
1069
+ });
1070
+
1071
+ it('handles createCube error - other error', async () => {
1072
+ mockDjClient.createCube.mockResolvedValue({
1073
+ status: 500,
1074
+ json: { message: 'Server error' },
1075
+ });
1076
+
1077
+ renderPage();
1078
+
1079
+ await waitFor(() => {
1080
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1081
+ });
1082
+ });
1083
+
1084
+ it('calls materializeCubeV2 for cube materialization', async () => {
1085
+ mockDjClient.materializeCubeV2.mockResolvedValue({
1086
+ status: 200,
1087
+ json: {
1088
+ workflow_urls: ['http://cube-workflow.example.com'],
1089
+ },
1090
+ });
1091
+
1092
+ renderPage();
1093
+
1094
+ await waitFor(() => {
1095
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1096
+ });
1097
+ });
1098
+
1099
+ it('handles materializeCubeV2 error', async () => {
1100
+ mockDjClient.materializeCubeV2.mockResolvedValue({
1101
+ status: 500,
1102
+ json: { message: 'Cube materialization failed' },
1103
+ });
1104
+
1105
+ renderPage();
1106
+
1107
+ await waitFor(() => {
1108
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1109
+ });
1110
+ });
1111
+
1112
+ it('calls refreshCubeWorkflow for config updates', async () => {
1113
+ mockDjClient.refreshCubeWorkflow.mockResolvedValue({
1114
+ status: 200,
1115
+ json: { workflow_urls: ['http://updated-workflow.url'] },
1116
+ });
1117
+
1118
+ renderPage();
1119
+
1120
+ await waitFor(() => {
1121
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1122
+ });
1123
+ });
1124
+
1125
+ it('handles refreshCubeWorkflow error', async () => {
1126
+ mockDjClient.refreshCubeWorkflow.mockResolvedValue({
1127
+ status: 500,
1128
+ json: { message: 'Refresh failed' },
1129
+ });
1130
+
1131
+ renderPage();
1132
+
1133
+ await waitFor(() => {
1134
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1135
+ });
1136
+ });
1137
+
1138
+ it('calls deactivateCubeWorkflow when deactivating', async () => {
1139
+ mockDjClient.deactivateCubeWorkflow.mockResolvedValue({
1140
+ status: 200,
1141
+ json: { success: true },
1142
+ });
1143
+
1144
+ renderPage();
1145
+
1146
+ await waitFor(() => {
1147
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1148
+ });
1149
+
1150
+ // Load cube first
1151
+ fireEvent.click(screen.getByText('Load from Cube'));
1152
+ fireEvent.click(screen.getByText('Test Cube'));
1153
+
1154
+ await waitFor(() => {
1155
+ expect(mockDjClient.cubeForPlanner).toHaveBeenCalled();
1156
+ });
1157
+ });
1158
+
1159
+ it('handles deactivateCubeWorkflow error', async () => {
1160
+ mockDjClient.deactivateCubeWorkflow.mockResolvedValue({
1161
+ status: 500,
1162
+ json: { message: 'Deactivation failed' },
1163
+ });
1164
+
1165
+ renderPage();
1166
+
1167
+ await waitFor(() => {
1168
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1169
+ });
1170
+ });
1171
+
1172
+ it('calls runCubeBackfill for backfill', async () => {
1173
+ mockDjClient.runCubeBackfill.mockResolvedValue({
1174
+ job_url: 'http://backfill-job.example.com',
1175
+ });
1176
+
1177
+ renderPage();
1178
+
1179
+ await waitFor(() => {
1180
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1181
+ });
1182
+
1183
+ // Load cube
1184
+ fireEvent.click(screen.getByText('Load from Cube'));
1185
+ fireEvent.click(screen.getByText('Test Cube'));
1186
+
1187
+ await waitFor(() => {
1188
+ expect(mockDjClient.cubeForPlanner).toHaveBeenCalled();
1189
+ });
1190
+ });
1191
+
1192
+ it('handles runCubeBackfill error', async () => {
1193
+ mockDjClient.runCubeBackfill.mockResolvedValue({
1194
+ _error: true,
1195
+ message: 'Backfill failed',
1196
+ });
1197
+
1198
+ renderPage();
1199
+
1200
+ await waitFor(() => {
1201
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1202
+ });
1203
+ });
1204
+
1205
+ it('handles runCubeBackfill with missing cube name', async () => {
1206
+ // Don't load any cube - loadedCubeName will be null
1207
+ renderPage();
1208
+
1209
+ await waitFor(() => {
1210
+ expect(mockDjClient.metrics).toHaveBeenCalled();
1211
+ });
1212
+
1213
+ // No cube loaded, so runCubeBackfill shouldn't be callable
1214
+ });
1215
+ });
1216
+
1217
+ describe('Workflow Status Updates', () => {
1218
+ const mockMeasuresWithPreagg = {
1219
+ grainGroups: [
1220
+ {
1221
+ node: 'default.repair_orders',
1222
+ grain_columns: ['default.date_dim.dateint'],
1223
+ measures: [{ name: 'count_orders', expression: 'COUNT(*)' }],
1224
+ },
1225
+ ],
1226
+ metricFormulas: [
1227
+ {
1228
+ metric: 'default.num_repair_orders',
1229
+ formula: 'count_orders',
1230
+ },
1231
+ ],
1232
+ };
1233
+
1234
+ beforeEach(() => {
1235
+ mockDjClient.measuresV3.mockResolvedValue(mockMeasuresWithPreagg);
1236
+ mockDjClient.metricsV3.mockResolvedValue({
1237
+ sql: 'SELECT * FROM metrics',
1238
+ dialect: 'SPARK',
1239
+ cube_name: null,
1240
+ });
1241
+ mockDjClient.listPreaggs.mockResolvedValue({ items: [] });
1242
+ });
1243
+
1244
+ it('updates state when workflow is created successfully', async () => {
1245
+ mockDjClient.materializePreagg.mockResolvedValue({
1246
+ workflow_urls: ['http://new-workflow.example.com'],
1247
+ workflow_status: 'active',
1248
+ });
1249
+
1250
+ renderPage();
1251
+
1252
+ await waitFor(() => {
1253
+ expect(mockDjClient.metrics).toHaveBeenCalled();
1254
+ });
1255
+ });
1256
+
1257
+ it('updates state when workflow is deactivated', async () => {
1258
+ mockDjClient.deactivatePreaggWorkflow.mockResolvedValue({
1259
+ success: true,
1260
+ });
1261
+
1262
+ renderPage();
1263
+
1264
+ await waitFor(() => {
1265
+ expect(mockDjClient.metrics).toHaveBeenCalled();
1266
+ });
1267
+ });
1268
+
1269
+ it('handles network errors in workflow operations', async () => {
1270
+ mockDjClient.materializePreagg.mockRejectedValue(
1271
+ new Error('Network error'),
1272
+ );
1273
+
1274
+ renderPage();
1275
+
1276
+ await waitFor(() => {
1277
+ expect(mockDjClient.metrics).toHaveBeenCalled();
1278
+ });
1279
+ });
1280
+ });
1281
+
1282
+ describe('Raw SQL Fetching', () => {
1283
+ it('fetches raw SQL when needed', async () => {
1284
+ mockDjClient.metricsV3.mockResolvedValue({
1285
+ sql: 'SELECT * FROM raw_tables',
1286
+ dialect: 'SPARK',
1287
+ cube_name: null,
1288
+ });
1289
+
1290
+ renderPage();
1291
+
1292
+ await waitFor(() => {
1293
+ expect(mockDjClient.metrics).toHaveBeenCalled();
1294
+ });
1295
+
1296
+ // Select metric and dimension to trigger SQL fetch
1297
+ fireEvent.click(screen.getByText('default'));
1298
+ await waitFor(() => {
1299
+ fireEvent.click(
1300
+ screen.getByRole('checkbox', { name: /num_repair_orders/i }),
1301
+ );
1302
+ });
1303
+
1304
+ await waitFor(() => {
1305
+ expect(mockDjClient.commonDimensions).toHaveBeenCalled();
1306
+ });
1307
+
1308
+ fireEvent.click(screen.getByRole('checkbox', { name: /dateint/i }));
1309
+
1310
+ await waitFor(() => {
1311
+ expect(mockDjClient.metricsV3).toHaveBeenCalled();
1312
+ });
1313
+ });
1314
+ });
1315
+
1316
+ describe('Cube Workflow Handlers', () => {
1317
+ beforeEach(() => {
1318
+ mockDjClient.listCubesForPreset.mockResolvedValue([
1319
+ { name: 'default.test_cube', display_name: 'Test Cube' },
1320
+ ]);
1321
+ });
1322
+
1323
+ it('handles cube deactivation', async () => {
1324
+ mockDjClient.cubeForPlanner.mockResolvedValue({
1325
+ name: 'default.test_cube',
1326
+ display_name: 'Test Cube',
1327
+ cube_node_metrics: ['default.num_repair_orders'],
1328
+ cube_node_dimensions: ['default.date_dim.dateint'],
1329
+ cubeMaterialization: {
1330
+ strategy: 'full',
1331
+ schedule: '0 6 * * *',
1332
+ lookbackWindow: null,
1333
+ druidDatasource: 'test_ds',
1334
+ preaggTables: [],
1335
+ workflowUrls: ['http://workflow.url'],
1336
+ },
1337
+ availability: null,
1338
+ });
1339
+ mockDjClient.deactivateCubeWorkflow.mockResolvedValue({ success: true });
1340
+
1341
+ renderPage();
1342
+
1343
+ await waitFor(() => {
1344
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1345
+ });
1346
+
1347
+ // Load cube
1348
+ fireEvent.click(screen.getByText('Load from Cube'));
1349
+ fireEvent.click(screen.getByText('Test Cube'));
1350
+
1351
+ await waitFor(() => {
1352
+ expect(mockDjClient.cubeForPlanner).toHaveBeenCalled();
1353
+ });
1354
+ });
1355
+
1356
+ it('handles cube backfill', async () => {
1357
+ mockDjClient.cubeForPlanner.mockResolvedValue({
1358
+ name: 'default.test_cube',
1359
+ display_name: 'Test Cube',
1360
+ cube_node_metrics: ['default.num_repair_orders'],
1361
+ cube_node_dimensions: ['default.date_dim.dateint'],
1362
+ cubeMaterialization: {
1363
+ strategy: 'full',
1364
+ schedule: '0 6 * * *',
1365
+ lookbackWindow: null,
1366
+ druidDatasource: 'test_ds',
1367
+ preaggTables: [],
1368
+ workflowUrls: [],
1369
+ },
1370
+ availability: null,
1371
+ });
1372
+ mockDjClient.runCubeBackfill.mockResolvedValue({ job_url: 'http://job' });
1373
+
1374
+ renderPage();
1375
+
1376
+ await waitFor(() => {
1377
+ expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
1378
+ });
1379
+
1380
+ // Load cube
1381
+ fireEvent.click(screen.getByText('Load from Cube'));
1382
+ fireEvent.click(screen.getByText('Test Cube'));
1383
+
1384
+ await waitFor(() => {
1385
+ expect(mockDjClient.cubeForPlanner).toHaveBeenCalled();
1386
+ });
1387
+ });
1388
+ });
1389
+
1390
+ describe('Partition Handling', () => {
1391
+ it('fetches node partitions when needed', async () => {
1392
+ mockDjClient.getNodeColumnsWithPartitions.mockResolvedValue({
1393
+ columns: [{ name: 'date_col', type: 'date' }],
1394
+ temporalPartitions: [{ column: 'date_col' }],
1395
+ });
1396
+
1397
+ renderPage();
1398
+
1399
+ await waitFor(() => {
1400
+ expect(mockDjClient.metrics).toHaveBeenCalled();
1401
+ });
1402
+ });
1403
+
1404
+ it('handles setPartition call', async () => {
1405
+ mockDjClient.setPartition.mockResolvedValue({ success: true });
1406
+
1407
+ renderPage();
1408
+
1409
+ await waitFor(() => {
1410
+ expect(mockDjClient.metrics).toHaveBeenCalled();
1411
+ });
1412
+ });
1413
+ });
1414
+
1415
+ describe('Results View Navigation', () => {
1416
+ it('shows results view hint when no selection', async () => {
1417
+ renderPage();
1418
+
1419
+ await waitFor(() => {
1420
+ expect(mockDjClient.metrics).toHaveBeenCalled();
1421
+ });
1422
+
1423
+ expect(
1424
+ screen.getByText('Select metrics and dimensions to run a query'),
1425
+ ).toBeInTheDocument();
1426
+ });
1427
+ });
742
1428
  });