@quillsql/react 2.14.13 → 2.14.14

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