@quillsql/react 2.14.16 → 2.15.0

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.
Files changed (253) hide show
  1. package/dist/cjs/Chart.d.ts.map +1 -1
  2. package/dist/cjs/Chart.js +12 -0
  3. package/dist/cjs/ChartBuilder.d.ts +3 -2
  4. package/dist/cjs/ChartBuilder.d.ts.map +1 -1
  5. package/dist/cjs/ChartBuilder.js +112 -18
  6. package/dist/cjs/ChartEditor.d.ts.map +1 -1
  7. package/dist/cjs/ChartEditor.js +2 -0
  8. package/dist/cjs/Context.d.ts +6 -2
  9. package/dist/cjs/Context.d.ts.map +1 -1
  10. package/dist/cjs/Context.js +160 -33
  11. package/dist/cjs/Dashboard.d.ts.map +1 -1
  12. package/dist/cjs/Dashboard.js +99 -19
  13. package/dist/cjs/QuillProvider.d.ts +38 -1
  14. package/dist/cjs/QuillProvider.d.ts.map +1 -1
  15. package/dist/cjs/QuillProvider.js +2 -2
  16. package/dist/cjs/ReportBuilder.d.ts +7 -2
  17. package/dist/cjs/ReportBuilder.d.ts.map +1 -1
  18. package/dist/cjs/ReportBuilder.js +104 -1271
  19. package/dist/cjs/SQLEditor.d.ts +9 -2
  20. package/dist/cjs/SQLEditor.d.ts.map +1 -1
  21. package/dist/cjs/SQLEditor.js +67 -10
  22. package/dist/cjs/Table.d.ts.map +1 -1
  23. package/dist/cjs/Table.js +12 -0
  24. package/dist/cjs/components/Chart/InternalChart.d.ts.map +1 -1
  25. package/dist/cjs/components/Chart/InternalChart.js +24 -1
  26. package/dist/cjs/components/Dashboard/DashboardTemplate.d.ts.map +1 -1
  27. package/dist/cjs/components/Dashboard/DashboardTemplate.js +2 -1
  28. package/dist/cjs/components/Dashboard/DataLoader.d.ts.map +1 -1
  29. package/dist/cjs/components/Dashboard/DataLoader.js +73 -2
  30. package/dist/cjs/components/Dashboard/util.d.ts +2 -1
  31. package/dist/cjs/components/Dashboard/util.d.ts.map +1 -1
  32. package/dist/cjs/components/Dashboard/util.js +12 -1
  33. package/dist/cjs/components/QuillTable.d.ts +2 -1
  34. package/dist/cjs/components/QuillTable.d.ts.map +1 -1
  35. package/dist/cjs/components/QuillTable.js +2 -2
  36. package/dist/cjs/components/ReportBuilder/AddColumnModal.d.ts +2 -1
  37. package/dist/cjs/components/ReportBuilder/AddColumnModal.d.ts.map +1 -1
  38. package/dist/cjs/components/ReportBuilder/AddColumnModal.js +28 -3
  39. package/dist/cjs/components/ReportBuilder/ColumnComponent.d.ts +49 -0
  40. package/dist/cjs/components/ReportBuilder/ColumnComponent.d.ts.map +1 -0
  41. package/dist/cjs/components/ReportBuilder/ColumnComponent.js +46 -0
  42. package/dist/cjs/components/ReportBuilder/FilterComponent.d.ts +65 -0
  43. package/dist/cjs/components/ReportBuilder/FilterComponent.d.ts.map +1 -0
  44. package/dist/cjs/components/ReportBuilder/FilterComponent.js +51 -0
  45. package/dist/cjs/components/ReportBuilder/LimitComponent.d.ts +42 -0
  46. package/dist/cjs/components/ReportBuilder/LimitComponent.d.ts.map +1 -0
  47. package/dist/cjs/components/ReportBuilder/LimitComponent.js +50 -0
  48. package/dist/cjs/components/ReportBuilder/PivotComponent.d.ts +66 -0
  49. package/dist/cjs/components/ReportBuilder/PivotComponent.d.ts.map +1 -0
  50. package/dist/cjs/components/ReportBuilder/PivotComponent.js +47 -0
  51. package/dist/cjs/components/ReportBuilder/SaveReport.d.ts +162 -0
  52. package/dist/cjs/components/ReportBuilder/SaveReport.d.ts.map +1 -0
  53. package/dist/cjs/components/ReportBuilder/SaveReport.js +31 -0
  54. package/dist/cjs/components/ReportBuilder/SortComponent.d.ts +42 -0
  55. package/dist/cjs/components/ReportBuilder/SortComponent.d.ts.map +1 -0
  56. package/dist/cjs/components/ReportBuilder/SortComponent.js +50 -0
  57. package/dist/cjs/components/ReportBuilder/TableComponent.d.ts +28 -0
  58. package/dist/cjs/components/ReportBuilder/TableComponent.d.ts.map +1 -0
  59. package/dist/cjs/components/ReportBuilder/TableComponent.js +24 -0
  60. package/dist/cjs/components/ReportBuilder/ui.d.ts.map +1 -1
  61. package/dist/cjs/components/ReportBuilder/ui.js +3 -1
  62. package/dist/cjs/components/UiComponents.d.ts +5 -2
  63. package/dist/cjs/components/UiComponents.d.ts.map +1 -1
  64. package/dist/cjs/components/UiComponents.js +7 -5
  65. package/dist/cjs/hooks/useAskQuill.d.ts.map +1 -1
  66. package/dist/cjs/hooks/useAskQuill.js +38 -0
  67. package/dist/cjs/hooks/useDashboard.d.ts +1 -1
  68. package/dist/cjs/hooks/useDashboard.d.ts.map +1 -1
  69. package/dist/cjs/hooks/useDashboard.js +62 -6
  70. package/dist/cjs/hooks/useExport.d.ts.map +1 -1
  71. package/dist/cjs/hooks/useExport.js +5 -2
  72. package/dist/cjs/hooks/useLongLoading.d.ts +13 -0
  73. package/dist/cjs/hooks/useLongLoading.d.ts.map +1 -0
  74. package/dist/cjs/hooks/useLongLoading.js +67 -0
  75. package/dist/cjs/hooks/useQuill.d.ts.map +1 -1
  76. package/dist/cjs/hooks/useQuill.js +25 -1
  77. package/dist/cjs/hooks/useReportBuilder.d.ts +178 -0
  78. package/dist/cjs/hooks/useReportBuilder.d.ts.map +1 -0
  79. package/dist/cjs/hooks/useReportBuilder.js +1495 -0
  80. package/dist/cjs/hooks/useVirtualTables.d.ts.map +1 -1
  81. package/dist/cjs/hooks/useVirtualTables.js +27 -2
  82. package/dist/cjs/index.d.ts +11 -0
  83. package/dist/cjs/index.d.ts.map +1 -1
  84. package/dist/cjs/index.js +17 -1
  85. package/dist/cjs/internals/ReportBuilder/PivotForm.d.ts +14 -1
  86. package/dist/cjs/internals/ReportBuilder/PivotForm.d.ts.map +1 -1
  87. package/dist/cjs/internals/ReportBuilder/PivotForm.js +86 -3
  88. package/dist/cjs/internals/ReportBuilder/PivotList.d.ts.map +1 -1
  89. package/dist/cjs/internals/ReportBuilder/PivotList.js +3 -3
  90. package/dist/cjs/internals/ReportBuilder/PivotModal.d.ts +18 -2
  91. package/dist/cjs/internals/ReportBuilder/PivotModal.d.ts.map +1 -1
  92. package/dist/cjs/internals/ReportBuilder/PivotModal.js +438 -147
  93. package/dist/cjs/models/Filter.d.ts +1 -1
  94. package/dist/cjs/models/Filter.d.ts.map +1 -1
  95. package/dist/cjs/models/Filter.js +3 -3
  96. package/dist/cjs/utils/astFilterProcessing.d.ts.map +1 -1
  97. package/dist/cjs/utils/astFilterProcessing.js +25 -4
  98. package/dist/cjs/utils/astProcessing.d.ts +4 -2
  99. package/dist/cjs/utils/astProcessing.d.ts.map +1 -1
  100. package/dist/cjs/utils/astProcessing.js +25 -2
  101. package/dist/cjs/utils/client.d.ts +2 -1
  102. package/dist/cjs/utils/client.d.ts.map +1 -1
  103. package/dist/cjs/utils/client.js +12 -1
  104. package/dist/cjs/utils/dashboard.d.ts +3 -1
  105. package/dist/cjs/utils/dashboard.d.ts.map +1 -1
  106. package/dist/cjs/utils/dashboard.js +44 -3
  107. package/dist/cjs/utils/filterProcessing.d.ts +2 -1
  108. package/dist/cjs/utils/filterProcessing.d.ts.map +1 -1
  109. package/dist/cjs/utils/filterProcessing.js +12 -1
  110. package/dist/cjs/utils/pivotConstructor.d.ts.map +1 -1
  111. package/dist/cjs/utils/pivotConstructor.js +15 -10
  112. package/dist/cjs/utils/pivotProcessing.d.ts.map +1 -1
  113. package/dist/cjs/utils/pivotProcessing.js +13 -3
  114. package/dist/cjs/utils/queryConstructor.d.ts.map +1 -1
  115. package/dist/cjs/utils/queryConstructor.js +30 -16
  116. package/dist/cjs/utils/report.d.ts +10 -5
  117. package/dist/cjs/utils/report.d.ts.map +1 -1
  118. package/dist/cjs/utils/report.js +55 -7
  119. package/dist/cjs/utils/reportBuilder.d.ts.map +1 -1
  120. package/dist/cjs/utils/reportBuilder.js +5 -2
  121. package/dist/cjs/utils/schema.d.ts +5 -2
  122. package/dist/cjs/utils/schema.d.ts.map +1 -1
  123. package/dist/cjs/utils/schema.js +14 -2
  124. package/dist/cjs/utils/tableProcessing.d.ts +17 -10
  125. package/dist/cjs/utils/tableProcessing.d.ts.map +1 -1
  126. package/dist/cjs/utils/tableProcessing.js +99 -17
  127. package/dist/esm/Chart.d.ts.map +1 -1
  128. package/dist/esm/Chart.js +13 -1
  129. package/dist/esm/ChartBuilder.d.ts +3 -2
  130. package/dist/esm/ChartBuilder.d.ts.map +1 -1
  131. package/dist/esm/ChartBuilder.js +114 -20
  132. package/dist/esm/ChartEditor.d.ts.map +1 -1
  133. package/dist/esm/ChartEditor.js +3 -1
  134. package/dist/esm/Context.d.ts +6 -2
  135. package/dist/esm/Context.d.ts.map +1 -1
  136. package/dist/esm/Context.js +159 -32
  137. package/dist/esm/Dashboard.d.ts.map +1 -1
  138. package/dist/esm/Dashboard.js +100 -20
  139. package/dist/esm/QuillProvider.d.ts +38 -1
  140. package/dist/esm/QuillProvider.d.ts.map +1 -1
  141. package/dist/esm/QuillProvider.js +2 -2
  142. package/dist/esm/ReportBuilder.d.ts +7 -2
  143. package/dist/esm/ReportBuilder.d.ts.map +1 -1
  144. package/dist/esm/ReportBuilder.js +107 -1271
  145. package/dist/esm/SQLEditor.d.ts +9 -2
  146. package/dist/esm/SQLEditor.d.ts.map +1 -1
  147. package/dist/esm/SQLEditor.js +68 -11
  148. package/dist/esm/Table.d.ts.map +1 -1
  149. package/dist/esm/Table.js +13 -1
  150. package/dist/esm/components/Chart/InternalChart.d.ts.map +1 -1
  151. package/dist/esm/components/Chart/InternalChart.js +25 -2
  152. package/dist/esm/components/Dashboard/DashboardTemplate.d.ts.map +1 -1
  153. package/dist/esm/components/Dashboard/DashboardTemplate.js +3 -2
  154. package/dist/esm/components/Dashboard/DataLoader.d.ts.map +1 -1
  155. package/dist/esm/components/Dashboard/DataLoader.js +74 -3
  156. package/dist/esm/components/Dashboard/util.d.ts +2 -1
  157. package/dist/esm/components/Dashboard/util.d.ts.map +1 -1
  158. package/dist/esm/components/Dashboard/util.js +12 -1
  159. package/dist/esm/components/QuillTable.d.ts +2 -1
  160. package/dist/esm/components/QuillTable.d.ts.map +1 -1
  161. package/dist/esm/components/QuillTable.js +2 -2
  162. package/dist/esm/components/ReportBuilder/AddColumnModal.d.ts +2 -1
  163. package/dist/esm/components/ReportBuilder/AddColumnModal.d.ts.map +1 -1
  164. package/dist/esm/components/ReportBuilder/AddColumnModal.js +29 -4
  165. package/dist/esm/components/ReportBuilder/ColumnComponent.d.ts +49 -0
  166. package/dist/esm/components/ReportBuilder/ColumnComponent.d.ts.map +1 -0
  167. package/dist/esm/components/ReportBuilder/ColumnComponent.js +39 -0
  168. package/dist/esm/components/ReportBuilder/FilterComponent.d.ts +65 -0
  169. package/dist/esm/components/ReportBuilder/FilterComponent.d.ts.map +1 -0
  170. package/dist/esm/components/ReportBuilder/FilterComponent.js +44 -0
  171. package/dist/esm/components/ReportBuilder/LimitComponent.d.ts +42 -0
  172. package/dist/esm/components/ReportBuilder/LimitComponent.d.ts.map +1 -0
  173. package/dist/esm/components/ReportBuilder/LimitComponent.js +46 -0
  174. package/dist/esm/components/ReportBuilder/PivotComponent.d.ts +66 -0
  175. package/dist/esm/components/ReportBuilder/PivotComponent.d.ts.map +1 -0
  176. package/dist/esm/components/ReportBuilder/PivotComponent.js +40 -0
  177. package/dist/esm/components/ReportBuilder/SaveReport.d.ts +162 -0
  178. package/dist/esm/components/ReportBuilder/SaveReport.d.ts.map +1 -0
  179. package/dist/esm/components/ReportBuilder/SaveReport.js +31 -0
  180. package/dist/esm/components/ReportBuilder/SortComponent.d.ts +42 -0
  181. package/dist/esm/components/ReportBuilder/SortComponent.d.ts.map +1 -0
  182. package/dist/esm/components/ReportBuilder/SortComponent.js +46 -0
  183. package/dist/esm/components/ReportBuilder/TableComponent.d.ts +28 -0
  184. package/dist/esm/components/ReportBuilder/TableComponent.d.ts.map +1 -0
  185. package/dist/esm/components/ReportBuilder/TableComponent.js +20 -0
  186. package/dist/esm/components/ReportBuilder/ui.d.ts.map +1 -1
  187. package/dist/esm/components/ReportBuilder/ui.js +4 -2
  188. package/dist/esm/components/UiComponents.d.ts +5 -2
  189. package/dist/esm/components/UiComponents.d.ts.map +1 -1
  190. package/dist/esm/components/UiComponents.js +7 -5
  191. package/dist/esm/hooks/useAskQuill.d.ts.map +1 -1
  192. package/dist/esm/hooks/useAskQuill.js +39 -1
  193. package/dist/esm/hooks/useDashboard.d.ts +1 -1
  194. package/dist/esm/hooks/useDashboard.d.ts.map +1 -1
  195. package/dist/esm/hooks/useDashboard.js +63 -7
  196. package/dist/esm/hooks/useExport.d.ts.map +1 -1
  197. package/dist/esm/hooks/useExport.js +6 -3
  198. package/dist/esm/hooks/useLongLoading.d.ts +13 -0
  199. package/dist/esm/hooks/useLongLoading.d.ts.map +1 -0
  200. package/dist/esm/hooks/useLongLoading.js +64 -0
  201. package/dist/esm/hooks/useQuill.d.ts.map +1 -1
  202. package/dist/esm/hooks/useQuill.js +26 -2
  203. package/dist/esm/hooks/useReportBuilder.d.ts +178 -0
  204. package/dist/esm/hooks/useReportBuilder.d.ts.map +1 -0
  205. package/dist/esm/hooks/useReportBuilder.js +1490 -0
  206. package/dist/esm/hooks/useVirtualTables.d.ts.map +1 -1
  207. package/dist/esm/hooks/useVirtualTables.js +28 -3
  208. package/dist/esm/index.d.ts +11 -0
  209. package/dist/esm/index.d.ts.map +1 -1
  210. package/dist/esm/index.js +8 -0
  211. package/dist/esm/internals/ReportBuilder/PivotForm.d.ts +14 -1
  212. package/dist/esm/internals/ReportBuilder/PivotForm.d.ts.map +1 -1
  213. package/dist/esm/internals/ReportBuilder/PivotForm.js +87 -4
  214. package/dist/esm/internals/ReportBuilder/PivotList.d.ts.map +1 -1
  215. package/dist/esm/internals/ReportBuilder/PivotList.js +3 -3
  216. package/dist/esm/internals/ReportBuilder/PivotModal.d.ts +18 -2
  217. package/dist/esm/internals/ReportBuilder/PivotModal.d.ts.map +1 -1
  218. package/dist/esm/internals/ReportBuilder/PivotModal.js +440 -149
  219. package/dist/esm/models/Filter.d.ts +1 -1
  220. package/dist/esm/models/Filter.d.ts.map +1 -1
  221. package/dist/esm/models/Filter.js +3 -3
  222. package/dist/esm/utils/astFilterProcessing.d.ts.map +1 -1
  223. package/dist/esm/utils/astFilterProcessing.js +25 -4
  224. package/dist/esm/utils/astProcessing.d.ts +4 -2
  225. package/dist/esm/utils/astProcessing.d.ts.map +1 -1
  226. package/dist/esm/utils/astProcessing.js +25 -2
  227. package/dist/esm/utils/client.d.ts +2 -1
  228. package/dist/esm/utils/client.d.ts.map +1 -1
  229. package/dist/esm/utils/client.js +12 -1
  230. package/dist/esm/utils/dashboard.d.ts +3 -1
  231. package/dist/esm/utils/dashboard.d.ts.map +1 -1
  232. package/dist/esm/utils/dashboard.js +44 -3
  233. package/dist/esm/utils/filterProcessing.d.ts +2 -1
  234. package/dist/esm/utils/filterProcessing.d.ts.map +1 -1
  235. package/dist/esm/utils/filterProcessing.js +12 -1
  236. package/dist/esm/utils/pivotConstructor.d.ts.map +1 -1
  237. package/dist/esm/utils/pivotConstructor.js +15 -10
  238. package/dist/esm/utils/pivotProcessing.d.ts.map +1 -1
  239. package/dist/esm/utils/pivotProcessing.js +13 -3
  240. package/dist/esm/utils/queryConstructor.d.ts.map +1 -1
  241. package/dist/esm/utils/queryConstructor.js +30 -16
  242. package/dist/esm/utils/report.d.ts +10 -5
  243. package/dist/esm/utils/report.d.ts.map +1 -1
  244. package/dist/esm/utils/report.js +55 -7
  245. package/dist/esm/utils/reportBuilder.d.ts.map +1 -1
  246. package/dist/esm/utils/reportBuilder.js +5 -2
  247. package/dist/esm/utils/schema.d.ts +5 -2
  248. package/dist/esm/utils/schema.d.ts.map +1 -1
  249. package/dist/esm/utils/schema.js +14 -2
  250. package/dist/esm/utils/tableProcessing.d.ts +17 -10
  251. package/dist/esm/utils/tableProcessing.d.ts.map +1 -1
  252. package/dist/esm/utils/tableProcessing.js +99 -17
  253. package/package.json +1 -1
@@ -0,0 +1,1490 @@
1
+ import { useContext, useEffect, useMemo, useState } from 'react';
2
+ import { TEMP_REPORT_ID } from '../models/Report';
3
+ import { EMPTY_INTERNAL_REPORT, fetchReportBuilderDataFromAST, formatRowsFromReport, } from '../utils/report';
4
+ import { fetchResultsByQuery, fetchTableByAST, getUniqueStringValuesByTable, } from '../utils/tableProcessing';
5
+ import { EMPTY_REPORT_BUILDER_STATE, } from '../models/ReportBuilder';
6
+ import { SchemaDataContext, FetchContext, EventTrackingContext, TenantContext, ClientContext, } from '../Context';
7
+ import { SINGLE_TENANT } from '../utils/constants';
8
+ import { useAllReports, useDashboards, useDashboardInternal, } from './useDashboard';
9
+ import { convertCustomFilter, convertInternalFilterToFilter, } from '../models/Filter';
10
+ import { createSelectStarFromAst, fetchAndProcessASTFromPrompt, fetchASTFromQuillReport, } from '../utils/astProcessing';
11
+ import { cleanDashboardItem } from '../utils/dashboard';
12
+ import { fetchSqlQuery } from '../utils/dataFetcher';
13
+ import { filterSentence } from '../utils/filterProcessing';
14
+ import { shouldFetchMore } from '../utils/paginationProcessing';
15
+ import { isValidPivot, pivotFormData } from '../utils/pivotProcessing';
16
+ import { validatedReportBuilderState, setTypesOnPivot, formatRows, reportBuilderStateToAst, isValidPivotForReport, astToReportBuilderState, } from '../utils/reportBuilder';
17
+ import { getSchemaForeignKeyMapping } from '../utils/schema';
18
+ import { useLongLoading } from './useLongLoading';
19
+ import { convertQueryToSelectStar } from '../components/ReportBuilder/convert';
20
+ export const useReportBuilderInternal = ({ reportId, initialTableName, destinationDashboard, pivotRecommendationsEnabled, onSaveChanges, rowsPerPage, rowsPerRequest, }) => {
21
+ const { allReportsById } = useAllReports();
22
+ const [schemaData] = useContext(SchemaDataContext);
23
+ const { dashboards } = useDashboards();
24
+ const { data, isLoading: dashboardIsLoading, reload, } = useDashboardInternal(destinationDashboard);
25
+ const { getToken } = useContext(FetchContext);
26
+ const { eventTracking } = useContext(EventTrackingContext);
27
+ const destinationDashboardConfig = useMemo(() => {
28
+ return dashboards?.find((d) => d.name === destinationDashboard);
29
+ }, [dashboards, destinationDashboard]);
30
+ const filteredSchema = useMemo(() => {
31
+ return schemaData.schemaWithCustomFields?.filter((table) => {
32
+ return (destinationDashboardConfig?.tenantKeys?.[0] === SINGLE_TENANT ||
33
+ !table.ownerTenantFields ||
34
+ table.ownerTenantFields?.length === 0 ||
35
+ table.ownerTenantFields?.includes(destinationDashboardConfig?.tenantKeys?.[0] ?? ''));
36
+ });
37
+ }, [
38
+ schemaData.schemaWithCustomFields,
39
+ destinationDashboardConfig?.tenantKeys,
40
+ ]);
41
+ const { tenants } = useContext(TenantContext);
42
+ const [client] = useContext(ClientContext);
43
+ const _rowsPerRequest = rowsPerRequest ?? 100;
44
+ const _rowsPerPage = Math.min(rowsPerPage ?? 20, _rowsPerRequest);
45
+ // Consts
46
+ const REPORT_BUILDER_PAGINATION = {
47
+ page: 0,
48
+ rowsPerPage: _rowsPerPage,
49
+ rowsPerRequest: _rowsPerRequest,
50
+ };
51
+ // ReportBuilder UI States
52
+ const [openPopover, setOpenPopover] = useState(null);
53
+ const [aiPrompt, setAiPrompt] = useState('');
54
+ const [reportBuilderLoading, setReportBuilderLoading] = useState(false);
55
+ const [tableLoading, setTableLoading] = useState(false);
56
+ const [errorMessage, setErrorMessage] = useState('');
57
+ const [unresolvedReportMessage, setUnresolvedReportMessage] = useState('');
58
+ // Core Report states
59
+ // Table order matters for joins. For now, assumed to have one 'primary' table (the first one)
60
+ const [tables, setTables] = useState([]);
61
+ const [columns, setColumns] = useState([]);
62
+ const [filterStack, setFilterStack] = useState([]);
63
+ const [pivot, setPivot] = useState(null);
64
+ const [sort, setSort] = useState([]);
65
+ const [limit, setLimit] = useState(null);
66
+ const reportBuilderState = useMemo(() => {
67
+ return {
68
+ tables,
69
+ columns,
70
+ filterStack,
71
+ pivot,
72
+ sort,
73
+ limit,
74
+ };
75
+ }, [columns, filterStack, limit, pivot, sort, tables]);
76
+ // For undo/redo
77
+ const [stateStack, setStateStack] = useState([]);
78
+ const [poppedStateStack, setPoppedStateStack] = useState([]);
79
+ // Other Report states
80
+ const [activeQuery, setActiveQuery] = useState('');
81
+ const [queryOutOfSync, setQueryOutOfSync] = useState(false);
82
+ const [unfilteredUniqueValues, setUnfilteredUniqueValues] = useState({}); // unique values before filtering
83
+ const [unfilteredUniqueValuesIsLoading, setUnfilteredUniqueValuesIsLoading] = useState(false);
84
+ const [filteredUniqueValues, setFilteredUniqueValues] = useState(null); // unique values after filtering
85
+ const [filteredUniqueValuesIsLoading, setFilteredUniqueValuesIsLoading] = useState(false);
86
+ const [columnUniqueValues, setColumnUniqueValues] = useState({});
87
+ const [dateRanges, setDateRanges] = useState(null);
88
+ const [tempReport, setTempReport] = useState({
89
+ ...EMPTY_INTERNAL_REPORT,
90
+ pagination: REPORT_BUILDER_PAGINATION,
91
+ });
92
+ const [currentProcessing, setCurrentProcessing] = useState({
93
+ page: REPORT_BUILDER_PAGINATION,
94
+ });
95
+ const [previousPage, setPreviousPage] = useState(0);
96
+ // Table display states
97
+ const [reportColumns, setReportColumns] = useState([]);
98
+ const [reportRows, setReportRows] = useState([]);
99
+ const [formattedRows, setFormattedRows] = useState([]);
100
+ const [pivotData, setPivotData] = useState(null);
101
+ const [numberOfRows, setNumberOfRows] = useState(0);
102
+ const [rowCountIsLoading, setRowCountIsLoading] = useState(false);
103
+ const reportColumnsToStateColumns = useMemo(() => {
104
+ const positionMap = {};
105
+ columns.forEach((column, index) => {
106
+ positionMap[column.field] = index;
107
+ if (column.alias) {
108
+ positionMap[column.alias] = index;
109
+ }
110
+ });
111
+ // Sort reportColumns based on the position in columns
112
+ return [...reportColumns]
113
+ .filter((reportColumn) => positionMap[reportColumn.field] !== undefined)
114
+ .sort((a, b) => {
115
+ const posA = positionMap[a.field];
116
+ const posB = positionMap[b.field];
117
+ if (posA !== undefined && posB !== undefined) {
118
+ return posA - posB;
119
+ }
120
+ else {
121
+ return 0;
122
+ }
123
+ });
124
+ }, [columns, reportColumns]);
125
+ // Pivot form states
126
+ const [pivotRowField, setPivotRowField] = useState(undefined);
127
+ const [pivotColumnField, setPivotColumnField] = useState(undefined);
128
+ const [pivotAggregations, setPivotAggregations] = useState([]);
129
+ const [pivotLimit, setPivotLimit] = useState(undefined);
130
+ const [pivotSort, setPivotSort] = useState(undefined);
131
+ const [pivotHint, setPivotHint] = useState('');
132
+ const [pivotError, setPivotError] = useState('');
133
+ const [createdPivots, setCreatedPivots] = useState([]);
134
+ const [recommendedPivots, setRecommendedPivots] = useState([]);
135
+ const [pivotPopUpTitle, setPivotPopUpTitle] = useState('Add pivot');
136
+ const [showPivotPopover, setShowPivotPopover] = useState(false);
137
+ const [isEditingPivot, setIsEditingPivot] = useState(false);
138
+ const [selectedPivotIndex, setSelectedPivotIndex] = useState(-1);
139
+ const [pivotRecommendationsEnabledState, setPivotRecommendationsEnabledState,] = useState(pivotRecommendationsEnabled);
140
+ // Ask AI
141
+ const [askAILoading, setAskAILoading] = useState(false);
142
+ const loading = reportBuilderLoading || tableLoading;
143
+ useLongLoading(reportBuilderLoading, {
144
+ origin: 'ReportBuilder',
145
+ loadDescription: 'Loading report builder',
146
+ });
147
+ useLongLoading(tableLoading, {
148
+ origin: 'ReportBuilder',
149
+ loadDescription: 'Loading table',
150
+ });
151
+ useLongLoading(askAILoading, {
152
+ origin: 'ReportBuilder',
153
+ loadDescription: 'Loading ask AI',
154
+ });
155
+ const isSelectStar = useMemo(() => {
156
+ if (tables.length === 1) {
157
+ // Check if all columns are selected
158
+ const totalColumnLength = tables.reduce((acc, table) => {
159
+ const tableColumns = filteredSchema.find((t) => t.name === table.name)?.columns.length ??
160
+ 0;
161
+ return acc + tableColumns;
162
+ }, 0);
163
+ return totalColumnLength === columns.length;
164
+ }
165
+ else {
166
+ // TODO: Implement this to work with joins
167
+ // SELECT * won't work if joined table has shared column name
168
+ return false;
169
+ }
170
+ }, [tables, columns, filteredSchema]);
171
+ const mssqlSortWarning = useMemo(() => {
172
+ if (!client || client?.databaseType !== 'mssql') {
173
+ return undefined;
174
+ }
175
+ else if (!pivot && !limit) {
176
+ return 'Please add a limit.';
177
+ }
178
+ }, [client, limit, pivot]);
179
+ const foreignKeyMap = useMemo(() => {
180
+ return getSchemaForeignKeyMapping(filteredSchema);
181
+ }, [filteredSchema]);
182
+ // State changing functions
183
+ const clearAllState = (resetStateStack = true) => {
184
+ setActiveQuery('');
185
+ setQueryOutOfSync(false);
186
+ handleMultiStateChange({
187
+ state: EMPTY_REPORT_BUILDER_STATE,
188
+ fetchData: false,
189
+ updateStateStack: true,
190
+ });
191
+ if (resetStateStack) {
192
+ setStateStack([]);
193
+ setPoppedStateStack([]);
194
+ }
195
+ setFilteredUniqueValues(null);
196
+ setUnfilteredUniqueValues({});
197
+ setColumnUniqueValues({});
198
+ setDateRanges(null);
199
+ setTempReport({
200
+ ...EMPTY_INTERNAL_REPORT,
201
+ pagination: REPORT_BUILDER_PAGINATION,
202
+ });
203
+ resetProcessing();
204
+ setReportColumns([]);
205
+ setReportRows([]);
206
+ setFormattedRows([]);
207
+ setPivotData(null);
208
+ setNumberOfRows(0);
209
+ setRowCountIsLoading(false);
210
+ setPivotRowField(undefined);
211
+ setPivotColumnField(undefined);
212
+ setPivotAggregations([]);
213
+ setCreatedPivots([]);
214
+ setRecommendedPivots([]);
215
+ setSelectedPivotIndex(-1);
216
+ setAskAILoading(false);
217
+ setReportBuilderLoading(false);
218
+ setTableLoading(false);
219
+ setPivotError('');
220
+ setErrorMessage('');
221
+ setUnresolvedReportMessage('');
222
+ setPivotHint('');
223
+ };
224
+ const handleTablesChange = (newTables, updateStateStack = true) => {
225
+ setTables(newTables);
226
+ if (updateStateStack) {
227
+ setStateStack((prevStack) => [
228
+ ...prevStack,
229
+ { ...reportBuilderState, tables: newTables },
230
+ ]);
231
+ setPoppedStateStack([]);
232
+ }
233
+ };
234
+ const handleColumnsChange = (newColumns, fetchData, updateStateStack = true, bypassConfirmation = false) => {
235
+ if (newColumns.length > 0) {
236
+ eventTracking?.addBreadcrumb?.({
237
+ message: 'Changed columns in ReportBuilder',
238
+ data: {
239
+ columns: newColumns,
240
+ },
241
+ category: 'log',
242
+ level: 'info',
243
+ timestamp: Date.now(),
244
+ });
245
+ }
246
+ const validReportBuilderState = bypassConfirmation
247
+ ? { ...reportBuilderState, columns: newColumns }
248
+ : validatedReportBuilderState({
249
+ ...reportBuilderState,
250
+ columns: newColumns,
251
+ }, foreignKeyMap);
252
+ if (validReportBuilderState.tables.length === 0 && !bypassConfirmation) {
253
+ if (!confirm('Removing all columns will clear all state. Are you sure you want to continue?')) {
254
+ return;
255
+ }
256
+ else {
257
+ handleMultiStateChange({
258
+ state: EMPTY_REPORT_BUILDER_STATE,
259
+ fetchData: false,
260
+ updateStateStack: true,
261
+ });
262
+ return;
263
+ }
264
+ }
265
+ const tablesAffected = tables.length - validReportBuilderState.tables.length >= 2;
266
+ const filtersAffected = validReportBuilderState.filterStack.length !== filterStack.length;
267
+ const deletedTables = tables
268
+ .filter((table) => {
269
+ return !validReportBuilderState.tables.some((t) => t.name === table.name);
270
+ })
271
+ .map((table) => table.name);
272
+ const deletedFilters = filterStack
273
+ .filter((filter) => filter.value)
274
+ .filter((filter) => {
275
+ return deletedTables.some((table) => table === filter.value.table);
276
+ })
277
+ .map((filter) => filterSentence(filter.value));
278
+ if (tablesAffected && filtersAffected) {
279
+ if (!confirm(`Removing this column will remove the following table${deletedTables.length > 1 ? 's' : ''}:
280
+ ${deletedTables.join(', ')}.
281
+ It will also remove the following filter${deletedFilters.length > 1 ? 's' : ''}:
282
+ ${deletedFilters.join(', ')}.
283
+ Are you sure you want to continue?
284
+ `
285
+ .replace(/\s+/g, ' ')
286
+ .trim())) {
287
+ return;
288
+ }
289
+ }
290
+ else if (tablesAffected) {
291
+ if (!confirm(`Removing this column will remove the following table${deletedTables.length > 1 ? 's' : ''}:
292
+ ${deletedTables.join(', ')}.
293
+ Are you sure you want to continue?
294
+ `
295
+ .replace(/\s+/g, ' ')
296
+ .trim())) {
297
+ return;
298
+ }
299
+ }
300
+ else if (filtersAffected) {
301
+ if (!confirm(`Removing this column will remove the following filter${deletedFilters.length > 1 ? 's' : ''}:
302
+ ${deletedFilters.join(', ')}.
303
+ Are you sure you want to continue?
304
+ `
305
+ .replace(/\s+/g, ' ')
306
+ .trim())) {
307
+ return;
308
+ }
309
+ }
310
+ if (validReportBuilderState.tables.length !== tables.length) {
311
+ handleTablesChange(validReportBuilderState.tables, false);
312
+ fetchData = true;
313
+ }
314
+ if (validReportBuilderState.filterStack.length !== filterStack.length) {
315
+ handleFilterStackChange(validReportBuilderState.filterStack, false, false);
316
+ fetchData = true;
317
+ }
318
+ if (validReportBuilderState.sort.length !== sort.length) {
319
+ handleSortChange(validReportBuilderState.sort, false, false);
320
+ fetchData = true;
321
+ }
322
+ if (pivot && !validReportBuilderState.pivot) {
323
+ handlePivotChange(validReportBuilderState.pivot, false, false);
324
+ }
325
+ setColumns(validReportBuilderState.columns);
326
+ if (updateStateStack) {
327
+ setStateStack((prevStack) => [...prevStack, validReportBuilderState]);
328
+ setPoppedStateStack([]);
329
+ }
330
+ if (fetchData) {
331
+ fetchDataFromReportBuilderState({ state: validReportBuilderState });
332
+ }
333
+ else {
334
+ setQueryOutOfSync(true);
335
+ }
336
+ };
337
+ const handleFilterInsertion = (newFilter) => {
338
+ const newFilterStack = [...filterStack];
339
+ eventTracking?.addBreadcrumb?.({
340
+ message: 'Inserted filter in ReportBuilder',
341
+ data: {
342
+ filter: newFilter,
343
+ },
344
+ category: 'log',
345
+ level: 'info',
346
+ timestamp: Date.now(),
347
+ });
348
+ if (newFilterStack.length > 0) {
349
+ const tabNode = {
350
+ leaf: false,
351
+ operator: 'and',
352
+ leftNode: null,
353
+ rightNode: null,
354
+ };
355
+ newFilterStack.push(tabNode);
356
+ }
357
+ const newItem = {
358
+ leaf: true,
359
+ operator: null,
360
+ leftNode: null,
361
+ rightNode: null,
362
+ value: newFilter,
363
+ };
364
+ newFilterStack.push(newItem);
365
+ handleFilterStackChange(newFilterStack, true);
366
+ };
367
+ const handleFilterStackChange = (newFilterStack, fetchData, updateStateStack = true) => {
368
+ if (newFilterStack.length > 0 || filterStack.length > 0) {
369
+ eventTracking?.addBreadcrumb?.({
370
+ message: 'Changed filter stack in ReportBuilder',
371
+ data: {
372
+ filterStack: newFilterStack,
373
+ },
374
+ category: 'log',
375
+ level: 'info',
376
+ timestamp: Date.now(),
377
+ });
378
+ }
379
+ setFilterStack(newFilterStack);
380
+ if (newFilterStack.length === 0) {
381
+ setFilteredUniqueValues(null);
382
+ }
383
+ if (updateStateStack) {
384
+ setStateStack((prevStack) => [
385
+ ...prevStack,
386
+ { ...reportBuilderState, filterStack: newFilterStack },
387
+ ]);
388
+ setPoppedStateStack([]);
389
+ }
390
+ if (fetchData) {
391
+ fetchDataFromReportBuilderState({
392
+ state: {
393
+ ...reportBuilderState,
394
+ filterStack: newFilterStack,
395
+ },
396
+ filtersChanged: true,
397
+ });
398
+ }
399
+ };
400
+ const handlePivotChange = (newPivot, fetchData, updateStateStack = true) => {
401
+ if (newPivot || pivot) {
402
+ eventTracking?.addBreadcrumb?.({
403
+ message: 'Changed pivot in ReportBuilder',
404
+ data: {
405
+ pivot: newPivot,
406
+ },
407
+ category: 'log',
408
+ level: 'info',
409
+ timestamp: Date.now(),
410
+ });
411
+ }
412
+ setPivot(newPivot ? setTypesOnPivot(newPivot, reportColumns) : null);
413
+ setPivotHint('');
414
+ setPivotError('');
415
+ if (!newPivot) {
416
+ setPivotData(null);
417
+ if (!fetchData) {
418
+ const formattedRows = formatRows(reportRows, reportColumns, false);
419
+ setFormattedRows(formattedRows);
420
+ }
421
+ }
422
+ if (updateStateStack) {
423
+ setStateStack((prevStack) => [
424
+ ...prevStack,
425
+ {
426
+ ...reportBuilderState,
427
+ pivot: newPivot,
428
+ },
429
+ ]);
430
+ setPoppedStateStack([]);
431
+ }
432
+ if (fetchData) {
433
+ fetchDataFromReportBuilderState({
434
+ state: {
435
+ ...reportBuilderState,
436
+ pivot: newPivot,
437
+ },
438
+ });
439
+ }
440
+ };
441
+ const updatePivot = async (changeField, fieldKey) => {
442
+ if (!client || !pivot) {
443
+ return;
444
+ }
445
+ let newPivot = { ...pivot };
446
+ setPivotError('');
447
+ if (fieldKey === 'sort') {
448
+ if (changeField &&
449
+ typeof changeField === 'object' &&
450
+ 'sortField' in changeField &&
451
+ 'sortDirection' in changeField) {
452
+ newPivot.sort = true;
453
+ newPivot.sortField = changeField.sortField;
454
+ newPivot.sortDirection = changeField.sortDirection;
455
+ }
456
+ else {
457
+ newPivot.sort = false;
458
+ newPivot.sortField = undefined;
459
+ newPivot.sortDirection = undefined;
460
+ }
461
+ }
462
+ else {
463
+ // @ts-ignore
464
+ newPivot[fieldKey] = changeField;
465
+ }
466
+ if (fieldKey === 'rowField') {
467
+ if (changeField === '' || changeField === undefined) {
468
+ setPivotColumnField(undefined);
469
+ // set all percentage aggregations to undefined
470
+ setPivotAggregations(pivotAggregations.map((agg) => ({
471
+ ...agg,
472
+ aggregationType: agg.aggregationType === 'percentage'
473
+ ? undefined
474
+ : agg.aggregationType,
475
+ })));
476
+ }
477
+ }
478
+ newPivot = setTypesOnPivot(newPivot, reportColumns);
479
+ if (newPivot.aggregations?.length === 0 ||
480
+ newPivot.aggregations?.some((agg) => !agg.aggregationType) ||
481
+ newPivot.aggregations?.some((agg) => !agg.valueField &&
482
+ agg.aggregationType !== 'count' &&
483
+ agg.aggregationType !== 'percentage')) {
484
+ return;
485
+ }
486
+ const { valid, reason } = isValidPivot(newPivot);
487
+ if (!valid) {
488
+ setPivotError(reason);
489
+ return;
490
+ }
491
+ handlePivotChange(newPivot, true);
492
+ };
493
+ const handleSortChange = (newSort, fetchData, updateStateStack = true) => {
494
+ if (newSort.length > 0 || sort.length > 0) {
495
+ eventTracking?.addBreadcrumb?.({
496
+ message: 'Changed sort in ReportBuilder',
497
+ data: {
498
+ sort: newSort,
499
+ },
500
+ category: 'log',
501
+ level: 'info',
502
+ timestamp: Date.now(),
503
+ });
504
+ }
505
+ setSort(newSort);
506
+ if (updateStateStack) {
507
+ setStateStack((prevStack) => [
508
+ ...prevStack,
509
+ { ...reportBuilderState, sort: newSort },
510
+ ]);
511
+ setPoppedStateStack([]);
512
+ }
513
+ if (fetchData) {
514
+ fetchDataFromReportBuilderState({
515
+ state: {
516
+ ...reportBuilderState,
517
+ sort: newSort,
518
+ },
519
+ });
520
+ }
521
+ };
522
+ const handleLimitChange = (newLimit, fetchData, updateStateStack = true) => {
523
+ if (newLimit || limit) {
524
+ eventTracking?.addBreadcrumb?.({
525
+ message: 'Changed limit in ReportBuilder',
526
+ data: {
527
+ limit: newLimit,
528
+ },
529
+ category: 'log',
530
+ level: 'info',
531
+ timestamp: Date.now(),
532
+ });
533
+ }
534
+ setLimit(newLimit);
535
+ if (updateStateStack) {
536
+ setStateStack((prevStack) => [
537
+ ...prevStack,
538
+ { ...reportBuilderState, limit: newLimit },
539
+ ]);
540
+ setPoppedStateStack([]);
541
+ }
542
+ if (fetchData) {
543
+ fetchDataFromReportBuilderState({
544
+ state: {
545
+ ...reportBuilderState,
546
+ limit: newLimit,
547
+ },
548
+ limitChanged: true,
549
+ });
550
+ }
551
+ };
552
+ const handleMultiStateChange = ({ state, fetchData, updateStateStack = true, report, skipPivotColumnFetch = false, }) => {
553
+ if (state.tables !== undefined) {
554
+ handleTablesChange(state.tables, false);
555
+ }
556
+ if (state.columns !== undefined) {
557
+ handleColumnsChange(state.columns, false, false, true);
558
+ }
559
+ if (state.filterStack !== undefined) {
560
+ handleFilterStackChange(state.filterStack, false, false);
561
+ }
562
+ if (state.pivot !== undefined) {
563
+ handlePivotChange(state.pivot, false, false);
564
+ }
565
+ if (state.sort !== undefined) {
566
+ handleSortChange(state.sort, false, false);
567
+ }
568
+ if (state.limit !== undefined) {
569
+ handleLimitChange(state.limit, false, false);
570
+ }
571
+ if (updateStateStack) {
572
+ setStateStack((prevStack) => [
573
+ ...prevStack,
574
+ { ...reportBuilderState, ...state },
575
+ ]);
576
+ setPoppedStateStack([]);
577
+ }
578
+ eventTracking?.addBreadcrumb?.({
579
+ message: 'Changed state in ReportBuilder',
580
+ data: {
581
+ state: { ...reportBuilderState, ...state },
582
+ },
583
+ category: 'log',
584
+ level: 'info',
585
+ timestamp: Date.now(),
586
+ });
587
+ if (fetchData) {
588
+ fetchDataFromReportBuilderState({
589
+ state: { ...reportBuilderState, ...state },
590
+ filtersChanged: !!state.filterStack,
591
+ limitChanged: !!state.limit,
592
+ tablesChanged: !!state.tables,
593
+ report,
594
+ skipPivotColumnFetch,
595
+ });
596
+ }
597
+ };
598
+ const handleUndo = () => {
599
+ if (stateStack.length <= 1) {
600
+ return;
601
+ }
602
+ const previousState = stateStack[stateStack.length - 2];
603
+ setPoppedStateStack((prevStack) => [
604
+ ...prevStack,
605
+ stateStack[stateStack.length - 1],
606
+ ]);
607
+ setStateStack((prevStack) => prevStack.slice(0, -1));
608
+ // Only fetch data if the previous state has columns
609
+ handleMultiStateChange({
610
+ state: previousState,
611
+ fetchData: previousState.columns.length > 0,
612
+ updateStateStack: false,
613
+ });
614
+ };
615
+ const handleRedo = () => {
616
+ if (poppedStateStack.length === 0) {
617
+ return;
618
+ }
619
+ const lastState = poppedStateStack[poppedStateStack.length - 1];
620
+ setStateStack((prevStack) => [...prevStack, lastState]);
621
+ setPoppedStateStack((prevStack) => prevStack.slice(0, -1));
622
+ // Only fetch data if the last state has columns
623
+ handleMultiStateChange({
624
+ state: lastState,
625
+ fetchData: lastState.columns.length > 0,
626
+ updateStateStack: false,
627
+ });
628
+ };
629
+ const fetchDataFromReportBuilderState = ({ state, filtersChanged, limitChanged, tablesChanged, report, skipPivotColumnFetch, }) => {
630
+ if (!client) {
631
+ return;
632
+ }
633
+ const ast = reportBuilderStateToAst(state, client.databaseType?.toLowerCase() || 'postgresql');
634
+ fetchReportFromASTHelper({
635
+ ast,
636
+ pivot: state.pivot,
637
+ previousReport: report,
638
+ requiresNewFilteredUniqueValues: filtersChanged || limitChanged,
639
+ requiresNewUnfilteredUniqueValues: tablesChanged,
640
+ skipPivotColumnFetch,
641
+ });
642
+ };
643
+ const fetchReportFromASTHelper = async ({ ast, pivot, previousReport = tempReport, requiresNewFilteredUniqueValues = false, requiresNewUnfilteredUniqueValues = false, skipPivotColumnFetch = false, }) => {
644
+ let reportBuilderInfo = undefined;
645
+ setErrorMessage('');
646
+ const schema = filteredSchema;
647
+ try {
648
+ if (!client || reportBuilderLoading) {
649
+ return;
650
+ }
651
+ setReportBuilderLoading(true);
652
+ reportBuilderInfo = await fetchReportBuilderDataFromAST({
653
+ baseAst: ast,
654
+ schema,
655
+ client,
656
+ tenants,
657
+ pivot: pivot ?? undefined,
658
+ skipPivotColumnFetch,
659
+ previousRelevant: {
660
+ uniqueStringsByTable: unfilteredUniqueValues,
661
+ dateRanges: dateRanges ?? {},
662
+ uniqueStringsByColumn: requiresNewFilteredUniqueValues
663
+ ? {}
664
+ : columnUniqueValues,
665
+ },
666
+ requiresNewFilteredUniqueValues,
667
+ report: previousReport,
668
+ customFields: schemaData.customFields,
669
+ skipUniqueValues: true,
670
+ skipRowCount: true,
671
+ processing: { page: REPORT_BUILDER_PAGINATION },
672
+ dashboardName: destinationDashboard,
673
+ getToken,
674
+ eventTracking,
675
+ });
676
+ if (reportBuilderInfo.error) {
677
+ throw new Error(reportBuilderInfo.error);
678
+ }
679
+ }
680
+ catch (err) {
681
+ eventTracking?.logError?.({
682
+ type: 'bug', // TODO: determine type
683
+ severity: 'high',
684
+ message: 'Error fetching report',
685
+ errorMessage: err.message,
686
+ errorStack: err.stack,
687
+ errorData: {
688
+ caller: 'ReportBuilder',
689
+ function: 'fetchReportFromASTHelper',
690
+ },
691
+ });
692
+ if (err instanceof Error) {
693
+ setErrorMessage(err.message);
694
+ setReportBuilderLoading(false);
695
+ return { error: true, message: err.message, rows: [] };
696
+ }
697
+ setReportBuilderLoading(false);
698
+ setErrorMessage('Failed to fetch');
699
+ return { error: true, message: 'Failed to fetch', rows: [] };
700
+ }
701
+ if (!reportBuilderInfo) {
702
+ setReportBuilderLoading(false);
703
+ setErrorMessage('Failed to fetch');
704
+ return;
705
+ }
706
+ const cleanedReport = await cleanDashboardItem({
707
+ item: reportBuilderInfo.report,
708
+ dashboardFilters: [],
709
+ getToken,
710
+ client,
711
+ customFields: schemaData.customFields,
712
+ skipPivotFetch: true,
713
+ tenants,
714
+ eventTracking,
715
+ });
716
+ // set tempReport
717
+ setTempReport({
718
+ ...cleanedReport,
719
+ pagination: REPORT_BUILDER_PAGINATION,
720
+ });
721
+ setActiveQuery(reportBuilderInfo.query);
722
+ setQueryOutOfSync(false);
723
+ // table data
724
+ fetchRowCountFromAST(ast, ast.where);
725
+ setReportRows(reportBuilderInfo.rows);
726
+ setFormattedRows(reportBuilderInfo.formattedRows);
727
+ resetProcessing();
728
+ if (!(client.databaseType?.toLowerCase() === 'bigquery') ||
729
+ (reportBuilderInfo.rows && reportBuilderInfo.rows.length > 0)) {
730
+ setReportColumns(reportBuilderInfo.columns);
731
+ }
732
+ setPivotData(reportBuilderInfo.pivotData);
733
+ if (reportBuilderInfo.pivot) {
734
+ setPivotRowField(reportBuilderInfo.pivot.rowField);
735
+ setPivotColumnField(reportBuilderInfo.pivot.columnField);
736
+ setPivotAggregations(reportBuilderInfo.pivot.aggregations ?? [
737
+ {
738
+ valueField: reportBuilderInfo.pivot.valueField,
739
+ valueField2: reportBuilderInfo.pivot.valueField2,
740
+ aggregationType: reportBuilderInfo.pivot.aggregationType,
741
+ },
742
+ ]);
743
+ setPivotLimit(reportBuilderInfo.pivot.rowLimit);
744
+ setPivotSort(reportBuilderInfo.pivot.sort &&
745
+ reportBuilderInfo.pivot.sortField &&
746
+ reportBuilderInfo.pivot.sortDirection
747
+ ? {
748
+ sortField: reportBuilderInfo.pivot.sortField,
749
+ sortDirection: reportBuilderInfo.pivot.sortDirection.toUpperCase(),
750
+ }
751
+ : undefined);
752
+ }
753
+ setPivot(reportBuilderInfo.pivot);
754
+ setPivotHint(reportBuilderInfo.pivotHint);
755
+ setDateRanges(reportBuilderInfo.dateRanges);
756
+ const tableNames = reportBuilderInfo.tables;
757
+ const columnInfo = tableNames.flatMap((table) => {
758
+ const tableInfo = schema.find((tableInfo) => tableInfo.name === table);
759
+ if (!tableInfo) {
760
+ return [];
761
+ }
762
+ return tableInfo.columns.map((col) => ({ ...col, table }));
763
+ });
764
+ setReportBuilderLoading(false);
765
+ // fetch unique values after everything else since it is the most expensive
766
+ try {
767
+ let uniqueStrings = filteredUniqueValues ?? unfilteredUniqueValues;
768
+ let uniqueValuesByColumn = columnUniqueValues;
769
+ if (requiresNewUnfilteredUniqueValues) {
770
+ fetchGlobalUniqueValues(columnInfo, tableNames);
771
+ }
772
+ if (requiresNewFilteredUniqueValues) {
773
+ if (reportBuilderInfo.pivot && !skipPivotColumnFetch) {
774
+ // if there's a pivot, these values would have had to been fetched
775
+ uniqueStrings = reportBuilderInfo.uniqueStringsByTable;
776
+ uniqueValuesByColumn = reportBuilderInfo.uniqueStringsByColumn;
777
+ }
778
+ else {
779
+ setFilteredUniqueValuesIsLoading(true);
780
+ const starQuery = await fetchSqlQuery(createSelectStarFromAst(ast), client, getToken);
781
+ uniqueStrings = await getUniqueStringValuesByTable({
782
+ columns: columnInfo,
783
+ tables: tableNames,
784
+ client,
785
+ tenants,
786
+ customFields: schemaData.customFields ?? undefined,
787
+ withExceededColumns: true,
788
+ dashboardName: destinationDashboard,
789
+ queryTemplate: starQuery.query,
790
+ getToken,
791
+ eventTracking,
792
+ });
793
+ uniqueValuesByColumn = {};
794
+ reportBuilderInfo.reportBuilderColumns.forEach((col) => {
795
+ uniqueValuesByColumn[col.alias || col.field] =
796
+ uniqueStrings[col.table || '']?.[col.field] ?? [];
797
+ });
798
+ }
799
+ }
800
+ setFilteredUniqueValues(uniqueStrings);
801
+ setFilteredUniqueValuesIsLoading(false);
802
+ setColumnUniqueValues(uniqueValuesByColumn);
803
+ }
804
+ catch (err) {
805
+ eventTracking?.logError?.({
806
+ type: 'bug', // TODO: determine type
807
+ severity: 'high',
808
+ message: 'Error fetching unique values',
809
+ errorMessage: err.message,
810
+ errorStack: err.stack,
811
+ errorData: {
812
+ caller: 'ReportBuilder',
813
+ function: 'fetchReportFromASTHelper',
814
+ },
815
+ });
816
+ if (err instanceof Error) {
817
+ setErrorMessage(err.message);
818
+ return { error: true, message: err.message, rows: [] };
819
+ }
820
+ setErrorMessage('Failed to fetch unique values');
821
+ return {
822
+ error: true,
823
+ message: 'Failed to fetch unique values',
824
+ rows: [],
825
+ };
826
+ }
827
+ };
828
+ const fetchRowCountFromAST = async (ast, where) => {
829
+ setRowCountIsLoading(true);
830
+ if (!client) {
831
+ return;
832
+ }
833
+ const tableData = await fetchTableByAST({ ...ast, where }, client, getToken, tenants, eventTracking, destinationDashboard, { page: REPORT_BUILDER_PAGINATION }, schemaData.customFields, false, true);
834
+ if (tableData.rowCount) {
835
+ setNumberOfRows(tableData.rowCount);
836
+ // @ts-ignore
837
+ setTempReport((tempReport) => ({
838
+ ...tempReport,
839
+ rowCount: tableData.rowCount,
840
+ }));
841
+ }
842
+ setRowCountIsLoading(false);
843
+ };
844
+ const fetchAstFromPromptHelper = async (overridePrompt) => {
845
+ let astInfo = {};
846
+ const prompt = overridePrompt || aiPrompt;
847
+ if (!client) {
848
+ return;
849
+ }
850
+ if (!prompt) {
851
+ setErrorMessage('Please supply a prompt.');
852
+ return;
853
+ }
854
+ try {
855
+ setReportBuilderLoading(true);
856
+ setAskAILoading(true);
857
+ setErrorMessage('');
858
+ astInfo = await fetchAndProcessASTFromPrompt({
859
+ aiPrompt: prompt,
860
+ schema: filteredSchema,
861
+ client,
862
+ prevPivot: pivot ?? undefined,
863
+ currentQuery: activeQuery,
864
+ prevTable: tables?.[0]?.name,
865
+ dashboardName: destinationDashboard,
866
+ tenants,
867
+ getToken,
868
+ eventTracking,
869
+ });
870
+ if (astInfo.error) {
871
+ throw new Error(astInfo.error);
872
+ }
873
+ }
874
+ catch (err) {
875
+ eventTracking?.logError?.({
876
+ type: 'bug', // TODO: determine type
877
+ severity: 'high',
878
+ message: 'Error fetching ast',
879
+ errorMessage: err.message,
880
+ errorStack: err.stack,
881
+ errorData: {
882
+ caller: 'ReportBuilder',
883
+ function: 'fetchAstFromPromptHelper',
884
+ },
885
+ });
886
+ if (err instanceof Error) {
887
+ setErrorMessage(err.message);
888
+ }
889
+ setReportBuilderLoading(false);
890
+ setAskAILoading(false);
891
+ return;
892
+ }
893
+ // check if pivot works with ReportBuilder constraints
894
+ if (Object.keys(columnUniqueValues).length > 0 &&
895
+ astInfo.pivot &&
896
+ !isValidPivotForReport(astInfo.pivot, columnUniqueValues, reportColumns)) {
897
+ astInfo.pivot = null;
898
+ astInfo.ast.groupby = null;
899
+ }
900
+ setAskAILoading(false);
901
+ const newState = astToReportBuilderState(astInfo.ast, client.databaseType || 'postgresql', filteredSchema);
902
+ handleMultiStateChange({
903
+ state: { ...newState, pivot: astInfo.pivot },
904
+ fetchData: true,
905
+ });
906
+ };
907
+ const fetchGlobalUniqueValues = async (columns, tables) => {
908
+ if (!client) {
909
+ return;
910
+ }
911
+ setUnfilteredUniqueValuesIsLoading(true);
912
+ const uniqueStrings = await getUniqueStringValuesByTable({
913
+ columns,
914
+ tables,
915
+ client,
916
+ getToken,
917
+ eventTracking,
918
+ tenants,
919
+ customFields: schemaData.customFields ?? undefined,
920
+ withExceededColumns: true,
921
+ dashboardName: destinationDashboard,
922
+ });
923
+ setUnfilteredUniqueValues(uniqueStrings);
924
+ setUnfilteredUniqueValuesIsLoading(false);
925
+ };
926
+ const fetchQueryFromReportBuilderState = async (state) => {
927
+ if (!client) {
928
+ return '';
929
+ }
930
+ const ast = reportBuilderStateToAst(state, client.databaseType?.toLowerCase() || 'postgresql');
931
+ const query = await fetchSqlQuery(ast, client, getToken);
932
+ setActiveQuery(query.query);
933
+ return query.query;
934
+ };
935
+ const resetProcessing = () => {
936
+ setCurrentProcessing({ page: REPORT_BUILDER_PAGINATION });
937
+ setPreviousPage(0);
938
+ };
939
+ const handlePagination = async (processing) => {
940
+ try {
941
+ if (!client) {
942
+ return;
943
+ }
944
+ const isPivotPagination = !!(pivot && pivotData);
945
+ setErrorMessage('');
946
+ setTableLoading(true);
947
+ const tableInfo = await fetchResultsByQuery({
948
+ query: isPivotPagination ? pivotData.pivotQuery : activeQuery,
949
+ comparisonQuery: pivot && pivotData ? pivotData.comparisonPivotQuery : undefined,
950
+ client,
951
+ tenants,
952
+ processing,
953
+ customFields: schemaData.customFields,
954
+ rowsOnly: true,
955
+ dashboardName: destinationDashboard,
956
+ pivot: pivot,
957
+ getPivotRowCount: false,
958
+ getToken,
959
+ eventTracking,
960
+ });
961
+ if (tableInfo.error) {
962
+ throw new Error(tableInfo.error);
963
+ }
964
+ else if (tableInfo.rows.length === 0) {
965
+ throw new Error('No data found');
966
+ }
967
+ if (!isPivotPagination) {
968
+ const tempRows = [...reportRows, ...tableInfo.rows];
969
+ setReportRows(tempRows);
970
+ setFormattedRows(formatRowsFromReport({ rows: tempRows, columns: tableInfo.columns }));
971
+ setTempReport((tempReport) => ({
972
+ ...tempReport,
973
+ rows: tempRows,
974
+ rowCount: tableInfo.rowCount ?? tempReport.rowCount,
975
+ }));
976
+ }
977
+ else {
978
+ const tempRows = [...pivotData.rows, ...tableInfo.rows];
979
+ setPivotData((oldPivotData) => {
980
+ if (oldPivotData) {
981
+ return {
982
+ ...oldPivotData,
983
+ rows: tempRows,
984
+ columns: tableInfo.columns,
985
+ };
986
+ }
987
+ return null;
988
+ });
989
+ setFormattedRows(formatRowsFromReport({ rows: tempRows, columns: tableInfo.columns }));
990
+ }
991
+ setTableLoading(false);
992
+ }
993
+ catch (e) {
994
+ eventTracking?.logError?.({
995
+ type: 'bug', // TODO: determine type
996
+ severity: 'high',
997
+ message: 'Error fetching report',
998
+ errorMessage: e.message,
999
+ errorStack: e.stack,
1000
+ errorData: {
1001
+ caller: 'ReportBuilder',
1002
+ function: 'handlePagination',
1003
+ },
1004
+ });
1005
+ setTableLoading(false);
1006
+ setErrorMessage('Failed to run SQL query: ' + e.message);
1007
+ setReportRows([]);
1008
+ setReportColumns([]);
1009
+ return;
1010
+ }
1011
+ };
1012
+ const onPageChange = (page) => {
1013
+ const pagination = REPORT_BUILDER_PAGINATION;
1014
+ if (currentProcessing.page &&
1015
+ shouldFetchMore(pagination, page, previousPage, pivotData ? pivotData.rows.length : reportRows.length)) {
1016
+ const newPagination = { ...currentProcessing.page, page };
1017
+ const updatedProcessing = { ...currentProcessing, page: newPagination };
1018
+ setCurrentProcessing(updatedProcessing);
1019
+ handlePagination(updatedProcessing);
1020
+ }
1021
+ if (page > previousPage) {
1022
+ setPreviousPage(page);
1023
+ }
1024
+ };
1025
+ const onSortChange = (newSort, isPivotSort, isDelete) => {
1026
+ if (!newSort.field) {
1027
+ return;
1028
+ }
1029
+ if (pivot && isPivotSort) {
1030
+ let newPivot = null;
1031
+ if (isDelete) {
1032
+ newPivot = {
1033
+ ...pivot,
1034
+ sort: undefined,
1035
+ sortField: undefined,
1036
+ sortDirection: undefined,
1037
+ sortFieldType: undefined,
1038
+ };
1039
+ }
1040
+ else {
1041
+ newPivot = {
1042
+ ...pivot,
1043
+ sort: true,
1044
+ sortField: newSort.field,
1045
+ sortDirection: newSort.direction,
1046
+ sortFieldType: reportColumns.find((col) => col.field === newSort.field)?.fieldType,
1047
+ };
1048
+ }
1049
+ handlePivotChange(newPivot, true);
1050
+ }
1051
+ else {
1052
+ const updatedSort = [...sort];
1053
+ const existingSortIndex = updatedSort.findIndex((item) => item.field === newSort.field);
1054
+ if (isDelete) {
1055
+ if (existingSortIndex !== -1) {
1056
+ updatedSort.splice(existingSortIndex, 1);
1057
+ }
1058
+ }
1059
+ else if (existingSortIndex !== -1) {
1060
+ updatedSort[existingSortIndex] = {
1061
+ field: newSort.field,
1062
+ direction: newSort.direction,
1063
+ };
1064
+ }
1065
+ else {
1066
+ updatedSort.push({
1067
+ field: newSort.field,
1068
+ direction: newSort.direction,
1069
+ });
1070
+ }
1071
+ handleSortChange(updatedSort, true);
1072
+ }
1073
+ };
1074
+ const onLimitChange = (limit, isPivotLimit) => {
1075
+ if (limit) {
1076
+ if (pivot && isPivotLimit) {
1077
+ const newPivot = { ...pivot, rowLimit: limit };
1078
+ handlePivotChange(newPivot, true);
1079
+ }
1080
+ else {
1081
+ handleLimitChange({ value: limit }, true);
1082
+ }
1083
+ }
1084
+ else {
1085
+ if (pivot && isPivotLimit) {
1086
+ const newPivot = { ...pivot, rowLimit: undefined };
1087
+ handlePivotChange(newPivot, true);
1088
+ }
1089
+ else {
1090
+ handleLimitChange(null, true);
1091
+ }
1092
+ }
1093
+ };
1094
+ // Button events
1095
+ const onSaveQuery = async () => {
1096
+ let tempReportQuery = activeQuery;
1097
+ if (queryOutOfSync) {
1098
+ tempReportQuery =
1099
+ await fetchQueryFromReportBuilderState(reportBuilderState);
1100
+ }
1101
+ const tempReportColumns = columns
1102
+ .map((column) => {
1103
+ return reportColumnsToStateColumns.find((col) => col.field === (column.alias || column.field));
1104
+ })
1105
+ .filter((col) => col !== undefined);
1106
+ let customFieldColumns = [];
1107
+ if (client && isSelectStar && schemaData.customFields) {
1108
+ customFieldColumns = tables.flatMap((table) => {
1109
+ return (schemaData.customFields?.[table.name || ''] || []).map((field) => field.field);
1110
+ });
1111
+ }
1112
+ setTempReport({
1113
+ ...tempReport,
1114
+ ...(pivot
1115
+ ? pivotFormData(pivot, reportColumnsToStateColumns, tempReport, tempReport.chartType, pivotData ?? undefined)
1116
+ : {}),
1117
+ id: TEMP_REPORT_ID,
1118
+ dashboardName: destinationDashboard,
1119
+ pivot: pivot,
1120
+ yAxisFields: tempReport?.pivot && !pivot ? [] : tempReport?.yAxisFields,
1121
+ columns: isSelectStar
1122
+ ? // if SELECT *, filter out custom fields from tabular view
1123
+ // so Automatic Custom Fields can be applied
1124
+ tempReportColumns.filter((col) => {
1125
+ return !customFieldColumns.includes(col.field);
1126
+ })
1127
+ : tempReportColumns,
1128
+ columnInternal: isSelectStar
1129
+ ? // if SELECT *, filter out custom fields from tabular view
1130
+ // so Automatic Custom Fields can be applied
1131
+ tempReportColumns.filter((col) => {
1132
+ return !customFieldColumns.includes(col.field);
1133
+ })
1134
+ : tempReportColumns,
1135
+ queryString: isSelectStar
1136
+ ? convertQueryToSelectStar(tempReportQuery)
1137
+ : tempReportQuery,
1138
+ includeCustomFields: isSelectStar,
1139
+ rows: reportRows,
1140
+ pivotRows: pivotData?.rows,
1141
+ pivotColumns: pivotData?.columns,
1142
+ pivotRowCount: pivotData?.rowCount,
1143
+ pivotQuery: pivotData?.pivotQuery,
1144
+ comparisonPivotQuery: pivotData?.comparisonPivotQuery,
1145
+ flags: tempReport?.flags,
1146
+ chartType: 'table',
1147
+ });
1148
+ };
1149
+ const onSaveReport = async () => {
1150
+ let tempReportQuery = activeQuery;
1151
+ if (queryOutOfSync) {
1152
+ tempReportQuery =
1153
+ await fetchQueryFromReportBuilderState(reportBuilderState);
1154
+ }
1155
+ onSaveChanges && onSaveChanges();
1156
+ const tempReportColumns = columns
1157
+ .map((column) => {
1158
+ return reportColumnsToStateColumns.find((col) => col.field === (column.alias || column.field));
1159
+ })
1160
+ .filter((col) => col !== undefined);
1161
+ let customFieldColumns = [];
1162
+ if (client && isSelectStar && schemaData.customFields) {
1163
+ customFieldColumns = tables.flatMap((table) => {
1164
+ return (schemaData.customFields?.[table.name || ''] || []).map((field) => field.field);
1165
+ });
1166
+ }
1167
+ setTempReport({
1168
+ ...tempReport,
1169
+ ...(pivot
1170
+ ? pivotFormData(pivot, reportColumnsToStateColumns, tempReport, tempReport.chartType, pivotData ?? undefined)
1171
+ : {}),
1172
+ id: TEMP_REPORT_ID,
1173
+ dashboardName: destinationDashboard,
1174
+ pivot: pivot,
1175
+ yAxisFields: tempReport?.pivot && !pivot ? [] : tempReport?.yAxisFields,
1176
+ columns: isSelectStar
1177
+ ? // if SELECT *, filter out custom fields from tabular view
1178
+ // so Automatic Custom Fields can be applied
1179
+ tempReportColumns.filter((col) => {
1180
+ return !customFieldColumns.includes(col.field);
1181
+ })
1182
+ : tempReportColumns,
1183
+ columnInternal: isSelectStar
1184
+ ? // if SELECT *, filter out custom fields from tabular view
1185
+ // so Automatic Custom Fields can be applied
1186
+ tempReportColumns.filter((col) => {
1187
+ return !customFieldColumns.includes(col.field);
1188
+ })
1189
+ : tempReportColumns,
1190
+ queryString: isSelectStar
1191
+ ? convertQueryToSelectStar(tempReportQuery)
1192
+ : tempReportQuery,
1193
+ includeCustomFields: isSelectStar,
1194
+ rows: reportRows,
1195
+ pivotRows: pivotData?.rows,
1196
+ pivotColumns: pivotData?.columns,
1197
+ pivotRowCount: pivotData?.rowCount,
1198
+ pivotQuery: pivotData?.pivotQuery,
1199
+ comparisonPivotQuery: pivotData?.comparisonPivotQuery,
1200
+ flags: tempReport?.flags,
1201
+ });
1202
+ };
1203
+ useEffect(() => {
1204
+ if (!client) {
1205
+ return;
1206
+ }
1207
+ if (client.featureFlags?.['recommendedPivotsDisabled'] !== undefined) {
1208
+ setPivotRecommendationsEnabledState(!client.featureFlags?.['recommendedPivotsDisabled']);
1209
+ }
1210
+ if (!initialTableName && !reportId && client.publicKey) {
1211
+ clearAllState();
1212
+ }
1213
+ }, [client]);
1214
+ // Initialize ReportBuilder with a report
1215
+ useEffect(() => {
1216
+ const loadChart = async () => {
1217
+ let report;
1218
+ if (!client) {
1219
+ return;
1220
+ }
1221
+ try {
1222
+ if (!reportId) {
1223
+ throw new Error('Report ID is required');
1224
+ }
1225
+ report = allReportsById[reportId];
1226
+ if (!report) {
1227
+ console.log('no report');
1228
+ throw new Error('Report not found');
1229
+ }
1230
+ const { ast: newAst, pivot: newPivot } = await fetchASTFromQuillReport(report, client, filteredSchema, getToken, eventTracking);
1231
+ const initialState = astToReportBuilderState(newAst, client.databaseType || 'postgresql', filteredSchema);
1232
+ setTempReport(report);
1233
+ handleMultiStateChange({
1234
+ state: {
1235
+ ...initialState,
1236
+ pivot: newPivot ?? null,
1237
+ },
1238
+ fetchData: true,
1239
+ report,
1240
+ skipPivotColumnFetch: true,
1241
+ });
1242
+ }
1243
+ catch (err) {
1244
+ console.error(err);
1245
+ eventTracking?.logError?.({
1246
+ type: 'bug', // TODO: determine type
1247
+ severity: 'high',
1248
+ message: 'Error fetching report',
1249
+ errorMessage: err.message,
1250
+ errorStack: err.stack,
1251
+ errorData: {
1252
+ caller: 'ReportBuilder',
1253
+ function: 'loadChart',
1254
+ },
1255
+ });
1256
+ setErrorMessage('Error when loading chart');
1257
+ }
1258
+ };
1259
+ if (reportId && client) {
1260
+ loadChart();
1261
+ }
1262
+ }, [allReportsById[reportId || ''], client]);
1263
+ useEffect(() => {
1264
+ if (initialTableName) {
1265
+ const tableColumns = filteredSchema.find((table) => {
1266
+ return table.name === initialTableName;
1267
+ })?.columns ?? [];
1268
+ if (tableColumns.length > 0) {
1269
+ handleMultiStateChange({
1270
+ state: {
1271
+ ...EMPTY_REPORT_BUILDER_STATE,
1272
+ tables: [{ name: initialTableName }],
1273
+ columns: tableColumns.map((col) => ({
1274
+ field: col.field,
1275
+ table: initialTableName,
1276
+ })),
1277
+ },
1278
+ fetchData: true,
1279
+ });
1280
+ }
1281
+ }
1282
+ }, [filteredSchema, initialTableName]);
1283
+ useEffect(() => {
1284
+ if (!data && !dashboardIsLoading) {
1285
+ // need dashboard to be in Context for filteredSchema
1286
+ reload();
1287
+ }
1288
+ }, [data, dashboardIsLoading]);
1289
+ return {
1290
+ // Core state
1291
+ columns,
1292
+ tables,
1293
+ sort,
1294
+ limit,
1295
+ filterStack,
1296
+ state: reportBuilderState,
1297
+ activeQuery,
1298
+ tempReport,
1299
+ stateStack,
1300
+ poppedStateStack,
1301
+ // UI state
1302
+ openPopover,
1303
+ setOpenPopover,
1304
+ errorMessage,
1305
+ unresolvedReportMessage,
1306
+ loading,
1307
+ tableLoading,
1308
+ askAILoading,
1309
+ rowCountIsLoading,
1310
+ unfilteredUniqueValuesIsLoading,
1311
+ filteredUniqueValuesIsLoading,
1312
+ mssqlSortWarning,
1313
+ // Data and results
1314
+ reportColumnsToStateColumns,
1315
+ reportRows,
1316
+ formattedRows,
1317
+ numberOfRows,
1318
+ unfilteredUniqueValues,
1319
+ filteredUniqueValues,
1320
+ columnUniqueValues,
1321
+ rowsPerPage: _rowsPerPage,
1322
+ rowsPerRequest: _rowsPerRequest,
1323
+ // Pivot state and handlers
1324
+ pivot,
1325
+ pivotData,
1326
+ pivotRowField,
1327
+ pivotColumnField,
1328
+ pivotAggregations,
1329
+ pivotLimit,
1330
+ pivotSort,
1331
+ pivotHint,
1332
+ pivotError,
1333
+ createdPivots,
1334
+ recommendedPivots,
1335
+ pivotPopUpTitle,
1336
+ showPivotPopover,
1337
+ isEditingPivot,
1338
+ selectedPivotIndex,
1339
+ pivotRecommendationsEnabledState,
1340
+ setCreatedPivots,
1341
+ setRecommendedPivots,
1342
+ setPivotPopUpTitle,
1343
+ setShowPivotPopover,
1344
+ setIsEditingPivot,
1345
+ setSelectedPivotIndex,
1346
+ setPivotRowField,
1347
+ setPivotColumnField,
1348
+ setPivotAggregations,
1349
+ setPivotLimit,
1350
+ setPivotSort,
1351
+ setPivotError,
1352
+ handlePivotChange,
1353
+ updatePivot,
1354
+ // Schema and client info
1355
+ filteredSchema,
1356
+ foreignKeyMap,
1357
+ schemaData,
1358
+ client,
1359
+ getToken,
1360
+ reportId,
1361
+ initialTableName,
1362
+ destinationDashboard,
1363
+ // AI features
1364
+ aiPrompt,
1365
+ setAiPrompt,
1366
+ fetchAstFromPromptHelper,
1367
+ // Action handlers
1368
+ clearAllState,
1369
+ handleMultiStateChange,
1370
+ handleTablesChange,
1371
+ handleColumnsChange,
1372
+ handleFilterInsertion,
1373
+ handleFilterStackChange,
1374
+ handleUndo,
1375
+ handleRedo,
1376
+ onSortChange,
1377
+ onLimitChange,
1378
+ handleSortChange,
1379
+ handleLimitChange,
1380
+ onPageChange,
1381
+ onSaveQuery,
1382
+ onSaveReport,
1383
+ };
1384
+ };
1385
+ export const useReportBuilder = ({ reportId, ownerDashboard, config, }) => {
1386
+ const reportBuilder = useReportBuilderInternal({
1387
+ reportId,
1388
+ initialTableName: config?.initialTableName,
1389
+ destinationDashboard: ownerDashboard,
1390
+ pivotRecommendationsEnabled: config?.pivotRecommendationsEnabled,
1391
+ onSaveChanges: config?.onSaveChanges,
1392
+ rowsPerPage: config?.rowsPerPage,
1393
+ rowsPerRequest: config?.rowsPerRequest,
1394
+ });
1395
+ // Column selection actions
1396
+ const setColumns = (newColumns) => {
1397
+ reportBuilder.handleColumnsChange(newColumns.map((col) => ({
1398
+ field: col.field,
1399
+ table: col.table,
1400
+ })), true);
1401
+ };
1402
+ // // Filter actions
1403
+ const setFilters = (filters) => {
1404
+ const internalFilters = filters.map(convertCustomFilter);
1405
+ // Create a filter tree with all filters chained with AND
1406
+ const filterStack = internalFilters.reduce((stack, filter, index) => {
1407
+ // Create leaf node for current filter
1408
+ const filterNode = {
1409
+ leaf: true,
1410
+ operator: null,
1411
+ leftNode: null,
1412
+ rightNode: null,
1413
+ value: filter,
1414
+ };
1415
+ // If not first filter, add an AND node
1416
+ if (index > 0) {
1417
+ stack.push({
1418
+ leaf: false,
1419
+ operator: 'and',
1420
+ leftNode: null,
1421
+ rightNode: null,
1422
+ });
1423
+ }
1424
+ stack.push(filterNode);
1425
+ return stack;
1426
+ }, []);
1427
+ reportBuilder.handleFilterStackChange(filterStack, true);
1428
+ };
1429
+ // // Pivot actions
1430
+ const setPivot = (newPivot) => {
1431
+ reportBuilder.handlePivotChange(newPivot, true);
1432
+ };
1433
+ const setSort = (sort) => {
1434
+ reportBuilder.handleSortChange(sort, true);
1435
+ };
1436
+ // // Limit actions
1437
+ const setLimit = (limit) => {
1438
+ reportBuilder.handleLimitChange(limit, true);
1439
+ };
1440
+ // // AI actions
1441
+ const applyAIPrompt = async (prompt) => {
1442
+ reportBuilder.setAiPrompt(prompt);
1443
+ await reportBuilder.fetchAstFromPromptHelper(prompt);
1444
+ };
1445
+ const cleanedSchema = useMemo(() => {
1446
+ return reportBuilder.filteredSchema.reduce((acc, table) => {
1447
+ acc[table.name] = table.columns;
1448
+ return acc;
1449
+ }, {});
1450
+ }, [reportBuilder.filteredSchema]);
1451
+ const cleanedFilterStack = useMemo(() => {
1452
+ return reportBuilder.filterStack
1453
+ .map((filter) => {
1454
+ return filter.value
1455
+ ? convertInternalFilterToFilter(filter.value)
1456
+ : null;
1457
+ })
1458
+ .filter((filter) => filter !== null);
1459
+ }, [reportBuilder.filterStack]);
1460
+ return {
1461
+ reportBuilder,
1462
+ state: {
1463
+ schema: cleanedSchema,
1464
+ columns: reportBuilder.columns,
1465
+ filters: cleanedFilterStack,
1466
+ pivot: reportBuilder.pivot,
1467
+ sort: reportBuilder.sort,
1468
+ limit: reportBuilder.limit,
1469
+ reportBuilderLoading: reportBuilder.loading,
1470
+ tableLoading: reportBuilder.tableLoading,
1471
+ tableRows: reportBuilder.formattedRows,
1472
+ tableColumns: reportBuilder.pivot && reportBuilder.pivotData
1473
+ ? reportBuilder.pivotData.columns
1474
+ : reportBuilder.reportColumnsToStateColumns,
1475
+ tableRowsCount: reportBuilder.pivot && reportBuilder.pivotData
1476
+ ? reportBuilder.pivotData.rowCount
1477
+ : reportBuilder.numberOfRows,
1478
+ errorMessage: reportBuilder.errorMessage,
1479
+ },
1480
+ actions: {
1481
+ setColumns,
1482
+ setFilters,
1483
+ setPivot,
1484
+ setSort,
1485
+ setLimit,
1486
+ onPageChange: reportBuilder.onPageChange,
1487
+ applyAIPrompt,
1488
+ },
1489
+ };
1490
+ };