@quillsql/react 2.13.42 → 2.13.44

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 (257) hide show
  1. package/dist/cjs/Chart.d.ts.map +1 -1
  2. package/dist/cjs/Chart.js +2 -2
  3. package/dist/cjs/ChartBuilder.d.ts +1 -0
  4. package/dist/cjs/ChartBuilder.d.ts.map +1 -1
  5. package/dist/cjs/ChartBuilder.js +48 -14
  6. package/dist/cjs/ChartEditor.d.ts +1 -1
  7. package/dist/cjs/ChartEditor.d.ts.map +1 -1
  8. package/dist/cjs/ChartEditor.js +2 -2
  9. package/dist/cjs/Context.d.ts.map +1 -1
  10. package/dist/cjs/Context.js +37 -26
  11. package/dist/cjs/Dashboard.d.ts +2 -2
  12. package/dist/cjs/Dashboard.d.ts.map +1 -1
  13. package/dist/cjs/Dashboard.js +115 -99
  14. package/dist/cjs/ReportBuilder.d.ts.map +1 -1
  15. package/dist/cjs/ReportBuilder.js +871 -1574
  16. package/dist/cjs/SQLEditor.d.ts.map +1 -1
  17. package/dist/cjs/SQLEditor.js +23 -4
  18. package/dist/cjs/Table.d.ts.map +1 -1
  19. package/dist/cjs/components/Chart/CustomReferenceLine.js +1 -1
  20. package/dist/cjs/components/Chart/GaugeChart.d.ts.map +1 -1
  21. package/dist/cjs/components/Chart/GaugeChart.js +64 -12
  22. package/dist/cjs/components/Chart/InternalChart.js +1 -1
  23. package/dist/cjs/components/Chart/MapChart.d.ts.map +1 -1
  24. package/dist/cjs/components/Chart/MapChart.js +65 -7
  25. package/dist/cjs/components/Dashboard/DashboardFilter.d.ts.map +1 -1
  26. package/dist/cjs/components/Dashboard/DashboardFilter.js +3 -3
  27. package/dist/cjs/components/Dashboard/DashboardSection.d.ts +1 -1
  28. package/dist/cjs/components/Dashboard/DashboardSection.d.ts.map +1 -1
  29. package/dist/cjs/components/Dashboard/DashboardSection.js +20 -19
  30. package/dist/cjs/components/Dashboard/MetricComponent.d.ts.map +1 -1
  31. package/dist/cjs/components/Dashboard/MetricComponent.js +1 -0
  32. package/dist/cjs/components/Dashboard/TableComponent.d.ts.map +1 -1
  33. package/dist/cjs/components/Dashboard/TableComponent.js +5 -1
  34. package/dist/cjs/components/QuillMultiSelectWithCombo.d.ts.map +1 -1
  35. package/dist/cjs/components/QuillMultiSelectWithCombo.js +58 -8
  36. package/dist/cjs/components/QuillSelect.d.ts.map +1 -1
  37. package/dist/cjs/components/QuillSelect.js +91 -14
  38. package/dist/cjs/components/QuillSelectWithCombo.js +6 -6
  39. package/dist/cjs/components/ReportBuilder/AddColumnModal.d.ts +6 -16
  40. package/dist/cjs/components/ReportBuilder/AddColumnModal.d.ts.map +1 -1
  41. package/dist/cjs/components/ReportBuilder/AddColumnModal.js +172 -75
  42. package/dist/cjs/components/ReportBuilder/AddSortPopover.d.ts +1 -5
  43. package/dist/cjs/components/ReportBuilder/AddSortPopover.d.ts.map +1 -1
  44. package/dist/cjs/components/ReportBuilder/AddSortPopover.js +2 -9
  45. package/dist/cjs/components/ReportBuilder/DraggableColumns.d.ts +17 -0
  46. package/dist/cjs/components/ReportBuilder/DraggableColumns.d.ts.map +1 -0
  47. package/dist/cjs/components/ReportBuilder/DraggableColumns.js +52 -0
  48. package/dist/cjs/components/ReportBuilder/DraggableItem.d.ts +17 -0
  49. package/dist/cjs/components/ReportBuilder/DraggableItem.d.ts.map +1 -0
  50. package/dist/cjs/components/ReportBuilder/DraggableItem.js +17 -0
  51. package/dist/cjs/components/ReportBuilder/FilterModal.d.ts +6 -12
  52. package/dist/cjs/components/ReportBuilder/FilterModal.d.ts.map +1 -1
  53. package/dist/cjs/components/ReportBuilder/FilterModal.js +29 -20
  54. package/dist/cjs/components/ReportBuilder/FilterStack.d.ts +8 -17
  55. package/dist/cjs/components/ReportBuilder/FilterStack.d.ts.map +1 -1
  56. package/dist/cjs/components/ReportBuilder/FilterStack.js +20 -86
  57. package/dist/cjs/components/ReportBuilder/convert.js +6 -6
  58. package/dist/cjs/components/ReportBuilder/ui.d.ts +8 -9
  59. package/dist/cjs/components/ReportBuilder/ui.d.ts.map +1 -1
  60. package/dist/cjs/components/ReportBuilder/ui.js +30 -8
  61. package/dist/cjs/components/ReportBuilder/util.d.ts +2 -5
  62. package/dist/cjs/components/ReportBuilder/util.d.ts.map +1 -1
  63. package/dist/cjs/components/ReportBuilder/util.js +1 -1
  64. package/dist/cjs/components/UiComponents.d.ts +3 -2
  65. package/dist/cjs/components/UiComponents.d.ts.map +1 -1
  66. package/dist/cjs/components/UiComponents.js +13 -13
  67. package/dist/cjs/hooks/useAskQuill.d.ts +1 -1
  68. package/dist/cjs/hooks/useAskQuill.d.ts.map +1 -1
  69. package/dist/cjs/hooks/useAskQuill.js +14 -12
  70. package/dist/cjs/hooks/useDashboard.d.ts +5 -3
  71. package/dist/cjs/hooks/useDashboard.d.ts.map +1 -1
  72. package/dist/cjs/hooks/useDashboard.js +8 -6
  73. package/dist/cjs/hooks/useOnClickOutside.d.ts +1 -0
  74. package/dist/cjs/hooks/useOnClickOutside.d.ts.map +1 -1
  75. package/dist/cjs/hooks/useOnClickOutside.js +33 -0
  76. package/dist/cjs/internals/ReportBuilder/PivotForm.d.ts +2 -1
  77. package/dist/cjs/internals/ReportBuilder/PivotForm.d.ts.map +1 -1
  78. package/dist/cjs/internals/ReportBuilder/PivotForm.js +22 -15
  79. package/dist/cjs/internals/ReportBuilder/PivotModal.d.ts +7 -5
  80. package/dist/cjs/internals/ReportBuilder/PivotModal.d.ts.map +1 -1
  81. package/dist/cjs/internals/ReportBuilder/PivotModal.js +100 -48
  82. package/dist/cjs/models/Client.d.ts +4 -0
  83. package/dist/cjs/models/Client.d.ts.map +1 -1
  84. package/dist/cjs/models/Dashboard.d.ts +1 -0
  85. package/dist/cjs/models/Dashboard.d.ts.map +1 -1
  86. package/dist/cjs/models/Report.d.ts +2 -0
  87. package/dist/cjs/models/Report.d.ts.map +1 -1
  88. package/dist/cjs/models/ReportBuilder.d.ts +46 -0
  89. package/dist/cjs/models/ReportBuilder.d.ts.map +1 -0
  90. package/dist/cjs/models/ReportBuilder.js +19 -0
  91. package/dist/cjs/models/Tables.d.ts +5 -5
  92. package/dist/cjs/models/Tables.d.ts.map +1 -1
  93. package/dist/cjs/utils/astFilterProcessing.d.ts +4 -0
  94. package/dist/cjs/utils/astFilterProcessing.d.ts.map +1 -1
  95. package/dist/cjs/utils/astFilterProcessing.js +301 -97
  96. package/dist/cjs/utils/astProcessing.d.ts +14 -4
  97. package/dist/cjs/utils/astProcessing.d.ts.map +1 -1
  98. package/dist/cjs/utils/astProcessing.js +38 -4
  99. package/dist/cjs/utils/dashboard.d.ts.map +1 -1
  100. package/dist/cjs/utils/dashboard.js +18 -3
  101. package/dist/cjs/utils/dataFetcher.d.ts.map +1 -1
  102. package/dist/cjs/utils/dataFetcher.js +4 -3
  103. package/dist/cjs/utils/filterProcessing.d.ts +2 -11
  104. package/dist/cjs/utils/filterProcessing.d.ts.map +1 -1
  105. package/dist/cjs/utils/filterProcessing.js +4 -16
  106. package/dist/cjs/utils/pivotConstructor.d.ts +2 -1
  107. package/dist/cjs/utils/pivotConstructor.d.ts.map +1 -1
  108. package/dist/cjs/utils/pivotConstructor.js +4 -2
  109. package/dist/cjs/utils/pivotProcessing.d.ts +17 -7
  110. package/dist/cjs/utils/pivotProcessing.d.ts.map +1 -1
  111. package/dist/cjs/utils/pivotProcessing.js +60 -51
  112. package/dist/cjs/utils/queryConstructor.d.ts.map +1 -1
  113. package/dist/cjs/utils/queryConstructor.js +15 -2
  114. package/dist/cjs/utils/report.d.ts +12 -9
  115. package/dist/cjs/utils/report.d.ts.map +1 -1
  116. package/dist/cjs/utils/report.js +114 -47
  117. package/dist/cjs/utils/reportBuilder.d.ts +88 -0
  118. package/dist/cjs/utils/reportBuilder.d.ts.map +1 -0
  119. package/dist/cjs/utils/reportBuilder.js +395 -0
  120. package/dist/cjs/utils/schema.d.ts +7 -0
  121. package/dist/cjs/utils/schema.d.ts.map +1 -1
  122. package/dist/cjs/utils/schema.js +36 -2
  123. package/dist/cjs/utils/tableProcessing.d.ts +59 -29
  124. package/dist/cjs/utils/tableProcessing.d.ts.map +1 -1
  125. package/dist/cjs/utils/tableProcessing.js +75 -90
  126. package/dist/cjs/utils/ui.d.ts +2 -0
  127. package/dist/cjs/utils/ui.d.ts.map +1 -0
  128. package/dist/cjs/utils/ui.js +18 -0
  129. package/dist/esm/Chart.d.ts.map +1 -1
  130. package/dist/esm/Chart.js +2 -2
  131. package/dist/esm/ChartBuilder.d.ts +1 -0
  132. package/dist/esm/ChartBuilder.d.ts.map +1 -1
  133. package/dist/esm/ChartBuilder.js +49 -15
  134. package/dist/esm/ChartEditor.d.ts +1 -1
  135. package/dist/esm/ChartEditor.d.ts.map +1 -1
  136. package/dist/esm/ChartEditor.js +2 -2
  137. package/dist/esm/Context.d.ts.map +1 -1
  138. package/dist/esm/Context.js +37 -26
  139. package/dist/esm/Dashboard.d.ts +2 -2
  140. package/dist/esm/Dashboard.d.ts.map +1 -1
  141. package/dist/esm/Dashboard.js +119 -103
  142. package/dist/esm/ReportBuilder.d.ts.map +1 -1
  143. package/dist/esm/ReportBuilder.js +878 -1581
  144. package/dist/esm/SQLEditor.d.ts.map +1 -1
  145. package/dist/esm/SQLEditor.js +23 -4
  146. package/dist/esm/Table.d.ts.map +1 -1
  147. package/dist/esm/components/Chart/CustomReferenceLine.js +1 -1
  148. package/dist/esm/components/Chart/GaugeChart.d.ts.map +1 -1
  149. package/dist/esm/components/Chart/GaugeChart.js +27 -8
  150. package/dist/esm/components/Chart/InternalChart.js +1 -1
  151. package/dist/esm/components/Chart/MapChart.d.ts.map +1 -1
  152. package/dist/esm/components/Chart/MapChart.js +30 -5
  153. package/dist/esm/components/Dashboard/DashboardFilter.d.ts.map +1 -1
  154. package/dist/esm/components/Dashboard/DashboardFilter.js +3 -3
  155. package/dist/esm/components/Dashboard/DashboardSection.d.ts +1 -1
  156. package/dist/esm/components/Dashboard/DashboardSection.d.ts.map +1 -1
  157. package/dist/esm/components/Dashboard/DashboardSection.js +21 -20
  158. package/dist/esm/components/Dashboard/MetricComponent.d.ts.map +1 -1
  159. package/dist/esm/components/Dashboard/MetricComponent.js +1 -0
  160. package/dist/esm/components/Dashboard/TableComponent.d.ts.map +1 -1
  161. package/dist/esm/components/Dashboard/TableComponent.js +5 -1
  162. package/dist/esm/components/QuillMultiSelectWithCombo.d.ts.map +1 -1
  163. package/dist/esm/components/QuillMultiSelectWithCombo.js +57 -7
  164. package/dist/esm/components/QuillSelect.d.ts.map +1 -1
  165. package/dist/esm/components/QuillSelect.js +58 -14
  166. package/dist/esm/components/QuillSelectWithCombo.js +6 -6
  167. package/dist/esm/components/ReportBuilder/AddColumnModal.d.ts +6 -16
  168. package/dist/esm/components/ReportBuilder/AddColumnModal.d.ts.map +1 -1
  169. package/dist/esm/components/ReportBuilder/AddColumnModal.js +173 -76
  170. package/dist/esm/components/ReportBuilder/AddSortPopover.d.ts +1 -5
  171. package/dist/esm/components/ReportBuilder/AddSortPopover.d.ts.map +1 -1
  172. package/dist/esm/components/ReportBuilder/AddSortPopover.js +2 -9
  173. package/dist/esm/components/ReportBuilder/DraggableColumns.d.ts +17 -0
  174. package/dist/esm/components/ReportBuilder/DraggableColumns.d.ts.map +1 -0
  175. package/dist/esm/components/ReportBuilder/DraggableColumns.js +46 -0
  176. package/dist/esm/components/ReportBuilder/DraggableItem.d.ts +17 -0
  177. package/dist/esm/components/ReportBuilder/DraggableItem.d.ts.map +1 -0
  178. package/dist/esm/components/ReportBuilder/DraggableItem.js +14 -0
  179. package/dist/esm/components/ReportBuilder/FilterModal.d.ts +6 -12
  180. package/dist/esm/components/ReportBuilder/FilterModal.d.ts.map +1 -1
  181. package/dist/esm/components/ReportBuilder/FilterModal.js +29 -20
  182. package/dist/esm/components/ReportBuilder/FilterStack.d.ts +8 -17
  183. package/dist/esm/components/ReportBuilder/FilterStack.d.ts.map +1 -1
  184. package/dist/esm/components/ReportBuilder/FilterStack.js +21 -87
  185. package/dist/esm/components/ReportBuilder/convert.js +6 -6
  186. package/dist/esm/components/ReportBuilder/ui.d.ts +8 -9
  187. package/dist/esm/components/ReportBuilder/ui.d.ts.map +1 -1
  188. package/dist/esm/components/ReportBuilder/ui.js +33 -11
  189. package/dist/esm/components/ReportBuilder/util.d.ts +2 -5
  190. package/dist/esm/components/ReportBuilder/util.d.ts.map +1 -1
  191. package/dist/esm/components/ReportBuilder/util.js +1 -1
  192. package/dist/esm/components/UiComponents.d.ts +3 -2
  193. package/dist/esm/components/UiComponents.d.ts.map +1 -1
  194. package/dist/esm/components/UiComponents.js +13 -13
  195. package/dist/esm/hooks/useAskQuill.d.ts +1 -1
  196. package/dist/esm/hooks/useAskQuill.d.ts.map +1 -1
  197. package/dist/esm/hooks/useAskQuill.js +14 -12
  198. package/dist/esm/hooks/useDashboard.d.ts +5 -3
  199. package/dist/esm/hooks/useDashboard.d.ts.map +1 -1
  200. package/dist/esm/hooks/useDashboard.js +8 -6
  201. package/dist/esm/hooks/useOnClickOutside.d.ts +1 -0
  202. package/dist/esm/hooks/useOnClickOutside.d.ts.map +1 -1
  203. package/dist/esm/hooks/useOnClickOutside.js +31 -0
  204. package/dist/esm/internals/ReportBuilder/PivotForm.d.ts +2 -1
  205. package/dist/esm/internals/ReportBuilder/PivotForm.d.ts.map +1 -1
  206. package/dist/esm/internals/ReportBuilder/PivotForm.js +23 -16
  207. package/dist/esm/internals/ReportBuilder/PivotModal.d.ts +7 -5
  208. package/dist/esm/internals/ReportBuilder/PivotModal.d.ts.map +1 -1
  209. package/dist/esm/internals/ReportBuilder/PivotModal.js +101 -49
  210. package/dist/esm/models/Client.d.ts +4 -0
  211. package/dist/esm/models/Client.d.ts.map +1 -1
  212. package/dist/esm/models/Dashboard.d.ts +1 -0
  213. package/dist/esm/models/Dashboard.d.ts.map +1 -1
  214. package/dist/esm/models/Report.d.ts +2 -0
  215. package/dist/esm/models/Report.d.ts.map +1 -1
  216. package/dist/esm/models/ReportBuilder.d.ts +46 -0
  217. package/dist/esm/models/ReportBuilder.d.ts.map +1 -0
  218. package/dist/esm/models/ReportBuilder.js +16 -0
  219. package/dist/esm/models/Tables.d.ts +5 -5
  220. package/dist/esm/models/Tables.d.ts.map +1 -1
  221. package/dist/esm/utils/astFilterProcessing.d.ts +4 -0
  222. package/dist/esm/utils/astFilterProcessing.d.ts.map +1 -1
  223. package/dist/esm/utils/astFilterProcessing.js +300 -97
  224. package/dist/esm/utils/astProcessing.d.ts +14 -4
  225. package/dist/esm/utils/astProcessing.d.ts.map +1 -1
  226. package/dist/esm/utils/astProcessing.js +38 -5
  227. package/dist/esm/utils/dashboard.d.ts.map +1 -1
  228. package/dist/esm/utils/dashboard.js +18 -3
  229. package/dist/esm/utils/dataFetcher.d.ts.map +1 -1
  230. package/dist/esm/utils/dataFetcher.js +4 -3
  231. package/dist/esm/utils/filterProcessing.d.ts +2 -11
  232. package/dist/esm/utils/filterProcessing.d.ts.map +1 -1
  233. package/dist/esm/utils/filterProcessing.js +4 -15
  234. package/dist/esm/utils/pivotConstructor.d.ts +2 -1
  235. package/dist/esm/utils/pivotConstructor.d.ts.map +1 -1
  236. package/dist/esm/utils/pivotConstructor.js +4 -2
  237. package/dist/esm/utils/pivotProcessing.d.ts +17 -7
  238. package/dist/esm/utils/pivotProcessing.d.ts.map +1 -1
  239. package/dist/esm/utils/pivotProcessing.js +58 -49
  240. package/dist/esm/utils/queryConstructor.d.ts.map +1 -1
  241. package/dist/esm/utils/queryConstructor.js +15 -2
  242. package/dist/esm/utils/report.d.ts +12 -9
  243. package/dist/esm/utils/report.d.ts.map +1 -1
  244. package/dist/esm/utils/report.js +116 -46
  245. package/dist/esm/utils/reportBuilder.d.ts +88 -0
  246. package/dist/esm/utils/reportBuilder.d.ts.map +1 -0
  247. package/dist/esm/utils/reportBuilder.js +386 -0
  248. package/dist/esm/utils/schema.d.ts +7 -0
  249. package/dist/esm/utils/schema.d.ts.map +1 -1
  250. package/dist/esm/utils/schema.js +34 -1
  251. package/dist/esm/utils/tableProcessing.d.ts +59 -29
  252. package/dist/esm/utils/tableProcessing.d.ts.map +1 -1
  253. package/dist/esm/utils/tableProcessing.js +71 -86
  254. package/dist/esm/utils/ui.d.ts +2 -0
  255. package/dist/esm/utils/ui.d.ts.map +1 -0
  256. package/dist/esm/utils/ui.js +14 -0
  257. package/package.json +5 -2
@@ -1,46 +1,36 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
3
- import { MemoizedButton, MemoizedDeleteButton, MemoizedHeader, MemoizedLabel, MemoizedSecondaryButton, MemoizedText, MemoizedPopover, QuillTabs, MemoizedModal, QuillChartBuilderInputRowContainer, QuillChartBuilderInputColumnContainer, MemoizedSubHeader, QuillErrorMessageComponent, QuillPivotRowContainer, QuillPivotColumnContainer, QuillColumnSearchEmptyState, QuillChartBuilderFormContainer, QuillLoadingComponent, QuillTableReportBuilderComponent, QuillChartBuilderCheckboxComponent, } from './components/UiComponents';
4
- import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, } from '@dnd-kit/core';
5
- import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable, } from '@dnd-kit/sortable';
6
- import { CSS as DND_CSS } from '@dnd-kit/utilities';
7
- import { ClientContext, DashboardContext, DashboardFiltersContext, SchemaDataContext, TenantContext, ThemeContext, } from './Context';
8
- import { getTableNames, isNumericColumnType, } from './components/ReportBuilder/ast';
2
+ import { useContext, useEffect, useMemo, useRef, useState, } from 'react';
3
+ import { MemoizedButton, MemoizedDeleteButton, MemoizedHeader, MemoizedLabel, MemoizedSecondaryButton, MemoizedText, MemoizedPopover, QuillTabs, MemoizedModal, QuillChartBuilderInputRowContainer, QuillChartBuilderInputColumnContainer, MemoizedSubHeader, QuillErrorMessageComponent, QuillPivotRowContainer, QuillPivotColumnContainer, QuillColumnSearchEmptyState, QuillChartBuilderFormContainer, QuillLoadingComponent, QuillTableReportBuilderComponent, QuillChartBuilderCheckboxComponent, QuillToolTip, } from './components/UiComponents';
4
+ import { ClientContext, DashboardContext, SchemaDataContext, TenantContext, ThemeContext, } from './Context';
9
5
  import { ChartBuilderWithModal } from './ChartBuilder';
10
6
  import { QuillTextInput } from './components/UiComponents';
11
7
  import { QuillSidebar, CustomContainer, QuillSelectColumn, QuillDraggableColumn, QuillSidebarHeading, QuillFilterPopover, QuillSortPopover, QuillLimitPopover, } from './components/ReportBuilder/ui';
12
- import { deepCopy } from './components/ReportBuilder/util';
13
- import { hashCode } from './utils/crypto';
14
- import { defaultAST, defaultColumn, defaultEntry, defaultNumericComparison, defaultTable, } from './components/ReportBuilder/constants';
15
- import AddColumnModal from './components/ReportBuilder/AddColumnModal';
16
8
  import { AddSortPopover, SortSentence, } from './components/ReportBuilder/AddSortPopover';
17
- import { PivotModal, generatePivotTable, isDateField, } from './internals/ReportBuilder/PivotModal';
9
+ import { PivotModal } from './internals/ReportBuilder/PivotModal';
18
10
  import { snakeAndCamelCaseToTitleCase } from './utils/textProcessing';
19
11
  import { AddLimitPopover, LimitSentence, } from './components/ReportBuilder/AddLimitPopover';
20
12
  import { updateFirstChildWidth } from './utils/width';
21
13
  import { QuillSelectComponent } from './components/QuillSelect';
22
14
  import { QuillCard } from './components/QuillCard';
23
- import { DATE_FORMAT_TYPES, quillFormat } from './utils/valueFormatter';
24
- import { getPossiblePivotFieldOptions, isValidPivot, pivotToSql, } from './utils/pivotProcessing';
25
- import { getUniqueValuesByColumns, getQueryDateRangeByColumns, fetchResultsByQuery, getUniqueStringValues, fetchTableByAST, } from './utils/tableProcessing';
26
- import { createBasicSelectASTFromColumns, fetchAndProcessASTFromPrompt, fetchASTFromQuillReport, } from './utils/astProcessing';
15
+ import { isValidPivot, pivotFormData, } from './utils/pivotProcessing';
16
+ import { fetchResultsByQuery, fetchTableByAST, getUniqueStringValuesByTable, } from './utils/tableProcessing';
17
+ import { createSelectStarFromAst, fetchAndProcessASTFromPrompt, fetchASTFromQuillReport, } from './utils/astProcessing';
27
18
  import PivotForm from './internals/ReportBuilder/PivotForm';
28
- import { getDateBucketFromRange, getDateFormatFromBucket } from './utils/dates';
29
19
  import FilterModal from './components/ReportBuilder/FilterModal';
30
- import { astToFilterTree, filterTreeToAst, getFieldFromExpression, } from './utils/astFilterProcessing';
31
- import useAstToFilterTree from './hooks/useAstToFilterTree';
32
- import { filterStackToFilterTree, uniqueValuesToStringMap, } from './utils/filterProcessing';
33
20
  import { QuillMultiSelectComponentWithCombo } from './components/QuillMultiSelectWithCombo';
34
21
  import { DEFAULT_PAGINATION, shouldFetchMore, } from './utils/paginationProcessing';
35
22
  import { EMPTY_INTERNAL_REPORT, fetchReportBuilderDataFromAST, formatRowsFromReport, } from './utils/report';
36
23
  import { TEMP_REPORT_ID, } from './models/Report';
37
- import equal from 'fast-deep-equal';
38
24
  import FilterStack from './components/ReportBuilder/FilterStack';
39
- import { QUILL_SERVER } from './utils/constants';
25
+ import { SINGLE_TENANT } from './utils/constants';
40
26
  import { convertQueryToSelectStar } from './components/ReportBuilder/convert';
41
- import { disambiguatedValueField } from './utils/pivotConstructor';
42
- import { isDateType, isNumberType } from './utils/columnProcessing';
43
27
  import { cleanDashboardItem } from './utils/dashboard';
28
+ import { useDashboards } from './hooks/useDashboard';
29
+ import { EMPTY_REPORT_BUILDER_STATE, } from './models/ReportBuilder';
30
+ import DraggableColumns from './components/ReportBuilder/DraggableColumns';
31
+ import NewAddColumnModal from './components/ReportBuilder/AddColumnModal';
32
+ import { astToReportBuilderState, formatRows, isValidPivotForReport, reportBuilderStateToAst, setTypesOnPivot, } from './utils/reportBuilder';
33
+ import { fetchSqlQuery } from './utils/dataFetcher';
44
34
  /**
45
35
  * Quill Report Builder
46
36
  *
@@ -73,173 +63,369 @@ import { cleanDashboardItem } from './utils/dashboard';
73
63
  * @see https://docs.quillsql.com/components/report-builder
74
64
  */
75
65
  export default function ReportBuilder({ initialTableName = '', onSubmitEditReport = () => void null, onSubmitCreateReport = () => void null, onSubmitSaveQuery = () => void null, onDiscardChanges = undefined, onSaveChanges = undefined, onCloseChartBuilder = undefined, destinationDashboard, chartBuilderTitle = undefined, organizationName = '', ButtonComponent = MemoizedButton, SecondaryButtonComponent = MemoizedSecondaryButton, DeleteButtonComponent = MemoizedDeleteButton, ModalComponent = MemoizedModal, TextInputComponent = QuillTextInput, SelectComponent = QuillSelectComponent, MultiSelectComponent = QuillMultiSelectComponentWithCombo, TableComponent = QuillTableReportBuilderComponent, PopoverComponent = MemoizedPopover, TabsComponent = QuillTabs, CheckboxComponent = QuillChartBuilderCheckboxComponent, SidebarComponent = QuillSidebar, ContainerComponent = CustomContainer, SelectColumnComponent = QuillSelectColumn, DraggableColumnComponent = QuillDraggableColumn, SidebarHeadingComponent = QuillSidebarHeading, FilterPopoverComponent = QuillFilterPopover, SortPopoverComponent = QuillSortPopover, LimitPopoverComponent = QuillLimitPopover, CardComponent = QuillCard, LabelComponent = MemoizedLabel, HeaderComponent = MemoizedHeader, SubHeaderComponent = MemoizedSubHeader, TextComponent = MemoizedText, ErrorMessageComponent = QuillErrorMessageComponent, ChartBuilderInputRowContainer = QuillChartBuilderInputRowContainer, ChartBuilderInputColumnContainer = QuillChartBuilderInputColumnContainer, PivotRowContainer = QuillPivotRowContainer, PivotColumnContainer = QuillPivotColumnContainer, LoadingComponent = QuillLoadingComponent, ColumnSearchEmptyState = QuillColumnSearchEmptyState, ChartBuilderFormContainer = QuillChartBuilderFormContainer, ChartBuilderModalComponent = MemoizedModal, isAdminEnabled = false, isAIEnabled = true, containerStyle, className, pivotRecommendationsEnabled = true, reportId, hideCopySQL = true, isChartBuilderHorizontalView = true, onClickChartElement, }) {
66
+ /**
67
+ * The state of the ReportBuilder is based off of an AST, which is a representation of a sql query
68
+ * ASTs for the same query can vary between database types, so we create a layer of abstraction on top of it
69
+ * An AST consists of these main parts:
70
+ * - from: the table(s) that the query is pulling data from
71
+ * - columns: the columns that the query is selecting
72
+ * - where: the conditions that the query is filtering by (filters)
73
+ * - groupby: the columns that the query is grouping by (these are represented by pivots)
74
+ * - orderby: the columns that the query is ordering by (sort)
75
+ * - limit: the number of rows that the query is limiting to (limit)
76
+ *
77
+ * The ReportBuilder maintains the state of these parts of the AST and assembles the AST when required
78
+ *
79
+ * User Interactions
80
+ * - Add Tables
81
+ * - Add Columns
82
+ * - Add Filters
83
+ * - Add Pivot
84
+ * - Add Sort
85
+ * - Add Limit
86
+ *
87
+ * - Two modes
88
+ * - Automatic: Run Query after every change
89
+ * - Manual: Run Query after user clicks "Run Query"
90
+ * */
91
+ // Notable ReportBuilder props
92
+ // intitialTableName: the table that the ReportBuilder will start with
93
+ // isAdminEnabled: whether the ReportBuilder is in admin mode
94
+ // isAIEnabled: whether the ReportBuilder's AI features are enabled
95
+ // pivotRecommendationsEnabled: whether the PivotModal's AI features are enabled
96
+ // reportId: a report id that the Report Builder will query from and modify
97
+ // onSubmitCreateReport: a callback function that will trigger when a new chart is saved
98
+ // onSubmitEditReport: a callback function that will trigger when a chart is edited
99
+ // onDiscardChanges: a callback function that will trigger when changes are discarded
100
+ // onCloseChartBuilder: a callback function that will trigger when the chart builder is closed
101
+ // hideCopySQL: hide the copy SQL button
102
+ // isChartBuilderHorizontalView: whether the chart builder is in horizontal view mode
103
+ // onClickChartElement: a callback function triggered when a chart element is clicked
104
+ // Contexts
76
105
  const [dashboard] = useContext(DashboardContext);
77
106
  const [schemaData] = useContext(SchemaDataContext);
78
- const { dashboardFilters } = useContext(DashboardFiltersContext);
107
+ const { dashboards } = useDashboards();
108
+ const destinationDashboardConfig = useMemo(() => {
109
+ return dashboards?.find((d) => d.name === destinationDashboard);
110
+ }, [dashboards, destinationDashboard]);
111
+ const filteredSchema = useMemo(() => {
112
+ return schemaData.schemaWithCustomFields?.filter((table) => {
113
+ return (destinationDashboardConfig?.tenantKeys?.[0] === SINGLE_TENANT ||
114
+ table.ownerTenantFields?.length === 0 ||
115
+ table.ownerTenantFields?.includes(destinationDashboardConfig?.tenantKeys?.[0] ?? ''));
116
+ });
117
+ }, [schemaData.schemaWithCustomFields, destinationDashboardConfig?.tenantKeys]);
79
118
  const { tenants } = useContext(TenantContext);
80
- const specificDashboardFilters = useMemo(() => {
81
- if (!reportId)
82
- return [];
83
- const dashboardName = dashboard[reportId]?.dashboardName;
84
- if (!dashboardName)
85
- return [];
86
- return Object.values(dashboardFilters[dashboardName] ?? {}).map((f) => f.filter);
87
- }, [dashboardFilters, reportId, dashboard]);
119
+ const [theme] = useContext(ThemeContext);
88
120
  const [client] = useContext(ClientContext);
89
- const [reportInfo, setReportInfo] = useState(null);
90
- const [aiPrompt, setAiPrompt] = useState('');
91
- const [errorMessage, setErrorMessage] = useState('');
92
- const [baseAst, setBaseAst] = useState(null);
93
- const [formData, setFormData] = useState(null);
94
- const [orderedColumnNames, setOrderedColumnNames] = useState([]);
95
- const [selectedColumns, setSelectedColumns] = useState([]);
96
- const [selectedOrderedColumns, setSelectedOrderedColumns] = useState([]);
97
- const [isSaveQueryModalOpen, setIsSaveQueryModalOpen] = useState(false);
98
- const [activeQuery, setActiveQuery] = useState('');
99
- const [, setActiveEditItem] = useState(null);
100
- const [openPopover, setOpenPopover] = useState(null);
101
- const [loading, setLoading] = useState(!!initialTableName);
102
- const [isChartBuilderOpen, setIsChartBuilderOpen] = useState(false);
103
- const [isCopying, setIsCopying] = useState(false);
104
- const [dataDisplayed, setDataDisplayed] = useState(false);
105
- const [rows, setRows] = useState([]);
106
- const [formattedRows, setFormattedRows] = useState([]);
107
- const [columns, setColumns] = useState([]);
121
+ // Refs
122
+ const parentRef = useRef(null);
123
+ // Consts
108
124
  const REPORT_BUILDER_PAGINATION = {
109
125
  page: 0,
110
126
  rowsPerPage: 20,
111
127
  rowsPerRequest: 100,
112
128
  };
129
+ // ReportBuilder UI States
130
+ const [isChartBuilderOpen, setIsChartBuilderOpen] = useState(false);
131
+ const [filtersEnabled, setFiltersEnabled] = useState(!!reportId);
132
+ const [isSaveQueryModalOpen, setIsSaveQueryModalOpen] = useState(false);
133
+ const [openPopover, setOpenPopover] = useState(null);
134
+ const [aiPrompt, setAiPrompt] = useState('');
135
+ const [reportBuilderLoading, setReportBuilderLoading] = useState(false);
136
+ const [tableLoading, setTableLoading] = useState(false);
137
+ const [errorMessage, setErrorMessage] = useState('');
138
+ const [unresolvedReportMessage, setUnresolvedReportMessage] = useState('');
139
+ // Core Report states
140
+ const [tables, setTables] = useState([]);
141
+ const [columns, setColumns] = useState([]);
142
+ const [filterStack, setFilterStack] = useState([]);
143
+ const [pivot, setPivot] = useState(null);
144
+ const [sort, setSort] = useState([]);
145
+ const [limit, setLimit] = useState(null);
146
+ const reportBuilderState = useMemo(() => {
147
+ return {
148
+ tables,
149
+ columns,
150
+ filterStack,
151
+ pivot,
152
+ sort,
153
+ limit,
154
+ };
155
+ }, [columns, filterStack, limit, pivot, sort, tables]);
156
+ const undoButtonEnabled = false; // TODO: enable
157
+ const [stateStack, setStateStack] = useState([]);
158
+ // Other Report states
159
+ const [activeQuery, setActiveQuery] = useState('');
160
+ const [unfilteredUniqueValues, setUnfilteredUniqueValues] = useState({}); // unique values before filtering
161
+ const [unfilteredUniqueValuesIsLoading, setUnfilteredUniqueValuesIsLoading] = useState(false);
162
+ const [filteredUniqueValues, setFilteredUniqueValues] = useState(null); // unique values after filtering
163
+ const [filteredUniqueValuesIsLoading, setFilteredUniqueValuesIsLoading] = useState(false);
164
+ const [columnUniqueValues, setColumnUniqueValues] = useState({});
165
+ const [dateRanges, setDateRanges] = useState(null);
113
166
  const [tempReport, setTempReport] = useState({
114
167
  ...EMPTY_INTERNAL_REPORT,
115
168
  pagination: REPORT_BUILDER_PAGINATION,
116
169
  });
117
- const [uniqueValues, setUniqueValues] = useState({});
118
- const [uniqueValuesIsLoading, setUniqueValuesIsLoading] = useState(false);
119
- const [pivot, setPivot] = useState(null);
170
+ const [currentProcessing, setCurrentProcessing] = useState({
171
+ page: REPORT_BUILDER_PAGINATION,
172
+ });
173
+ const [previousPage, setPreviousPage] = useState(0);
174
+ const [isCopying, setIsCopying] = useState(false);
175
+ const [sortOrLimitWasReset, setSortOrLimitWasReset] = useState(false);
176
+ // Table display states
177
+ const [reportColumns, setReportColumns] = useState([]);
178
+ const [reportRows, setReportRows] = useState([]);
179
+ const [formattedRows, setFormattedRows] = useState([]);
120
180
  const [pivotData, setPivotData] = useState(null);
181
+ const [numberOfRows, setNumberOfRows] = useState(0);
182
+ const [rowCountIsLoading, setRowCountIsLoading] = useState(false);
183
+ const reportColumnsToStateColumns = (() => {
184
+ const positionMap = {};
185
+ columns.forEach((column, index) => {
186
+ positionMap[column.field] = index;
187
+ if (column.alias) {
188
+ positionMap[column.alias] = index;
189
+ }
190
+ });
191
+ // Sort reportColumns based on the position in columns
192
+ return [...reportColumns]
193
+ .filter((reportColumn) => positionMap[reportColumn.field] !== undefined)
194
+ .sort((a, b) => {
195
+ const posA = positionMap[a.field];
196
+ const posB = positionMap[b.field];
197
+ return posA && posB ? posA - posB : 0;
198
+ });
199
+ })();
200
+ // Pivot form states
201
+ const [pivotRowField, setPivotRowField] = useState(undefined);
202
+ const [pivotColumnField, setPivotColumnField] = useState(undefined);
203
+ const [pivotAggregations, setPivotAggregations] = useState([]);
204
+ const [pivotHint, setPivotHint] = useState('');
205
+ const [pivotError, setPivotError] = useState('');
121
206
  const [createdPivots, setCreatedPivots] = useState([]);
122
207
  const [recommendedPivots, setRecommendedPivots] = useState([]);
123
208
  const [pivotPopUpTitle, setPivotPopUpTitle] = useState('Add pivot');
124
209
  const [showPivotPopover, setShowPivotPopover] = useState(false);
125
210
  const [isEditingPivot, setIsEditingPivot] = useState(false);
126
- const [askedAQuestion, setAskedAQuestion] = useState(false);
127
- const [askAILoading, setAskAILoading] = useState(false);
128
211
  const [selectedPivotIndex, setSelectedPivotIndex] = useState(-1);
129
- const [initialLoad, setInitialLoad] = useState(!!initialTableName || !!reportId);
130
- const [currentTable, setCurrentTable] = useState(initialTableName || '');
131
- const parentRef = useRef(null);
212
+ const [pivotRecommendationsEnabledState, setPivotRecommendationsEnabledState,] = useState(pivotRecommendationsEnabled);
213
+ // Ask AI
214
+ const [askAILoading, setAskAILoading] = useState(false);
132
215
  const askAIContainerRef = useRef(null);
133
- const askAILoadingContainerRef = useRef(null);
134
216
  const [askAIInputWidth, setAskAIInputWidth] = useState(-1);
135
- const [askAILoadingContainerWidth, setAskAILoadingContainerWidth] = useState(-1);
136
- const [theme] = useContext(ThemeContext);
137
- const [pivotRowField, setPivotRowField] = useState(undefined);
138
- const [pivotColumnField, setPivotColumnField] = useState(undefined);
139
- const [pivotAggregations, setPivotAggregations] = useState(reportInfo?.pivot?.aggregations ?? [
140
- {
141
- valueField: reportInfo?.pivot?.valueField,
142
- valueField2: reportInfo?.pivot?.valueField2,
143
- aggregationType: reportInfo?.pivot?.aggregationType,
144
- },
145
- ]);
146
- const [pivotHint, setPivotHint] = useState('');
147
- /* eslint-disable-next-line */
148
- const [pivotError, setPivotError] = useState(undefined);
149
- const [dateRanges, setDateRanges] = useState(null);
150
- const [fieldValuesMap, setFieldValuesMap] = useState({}); // Mapping of unique values per field, used in string filter 'in' and 'not in'
151
- const [globalUniqueValues, setGlobalUniqueValues] = useState({});
152
- const [globalUniqueValuesIsLoading, setGlobalUniqueValuesIsLoading] = useState(false);
153
- const { filterTree, filterStack } = useAstToFilterTree(formData, client, columns); // Stores the state of filters
154
- const [removingFilter, setRemovingFilter] = useState(false);
155
- const [pivotRecommendationsEnabledState, setPivotRecommendationsEnabledState,] = useState(pivotRecommendationsEnabled);
156
- const [unresolvedReportMessage, setUnresolvedReportMessage] = useState('');
157
- const dashboardName = useMemo(() => {
158
- if (destinationDashboard) {
159
- return destinationDashboard;
217
+ const loading = reportBuilderLoading || tableLoading;
218
+ const isSelectStar = useMemo(() => {
219
+ if (tables.length === 1) {
220
+ // Check if all columns are selected
221
+ const totalColumnLength = tables.reduce((acc, table) => {
222
+ const tableColumns = filteredSchema.find((t) => t.name === table.name)
223
+ ?.columns.length ?? 0;
224
+ return acc + tableColumns;
225
+ }, 0);
226
+ return totalColumnLength === columns.length;
160
227
  }
161
- if (!reportId) {
162
- return undefined;
228
+ else {
229
+ // TODO: Implement this to work with joins
230
+ // SELECT * won't work if joined table has shared column name
231
+ return false;
163
232
  }
164
- return dashboard[reportId]?.dashboardName;
165
- }, [reportId, dashboard, destinationDashboard]);
166
- const [filtersEnabled, setFiltersEnabled] = useState(!!reportId);
233
+ }, [tables, columns, filteredSchema]);
167
234
  const mssqlSortWarning = useMemo(() => {
168
235
  if (!client || client?.databaseType !== 'mssql') {
169
236
  return undefined;
170
237
  }
171
- else if (!pivot && !baseAst?.top) {
238
+ else if (!pivot && !limit) {
172
239
  return 'Please add a limit.';
173
240
  }
174
- }, [client, baseAst, pivot]);
175
- useEffect(() => {
176
- if (!client) {
177
- return;
241
+ }, [client, limit, pivot]);
242
+ // State changing functions
243
+ const clearAllState = () => {
244
+ setActiveQuery('');
245
+ handleMultiStateChange(EMPTY_REPORT_BUILDER_STATE);
246
+ setStateStack([]);
247
+ setFilteredUniqueValues(null);
248
+ setUnfilteredUniqueValues({});
249
+ setColumnUniqueValues({});
250
+ setDateRanges(null);
251
+ setTempReport({
252
+ ...EMPTY_INTERNAL_REPORT,
253
+ pagination: REPORT_BUILDER_PAGINATION,
254
+ });
255
+ resetProcessing();
256
+ setReportColumns([]);
257
+ setReportRows([]);
258
+ setFormattedRows([]);
259
+ setPivotData(null);
260
+ setNumberOfRows(0);
261
+ setRowCountIsLoading(false);
262
+ setPivotRowField(undefined);
263
+ setPivotColumnField(undefined);
264
+ setPivotAggregations([]);
265
+ setCreatedPivots([]);
266
+ setRecommendedPivots([]);
267
+ setSelectedPivotIndex(-1);
268
+ setAskAILoading(false);
269
+ setReportBuilderLoading(false);
270
+ setTableLoading(false);
271
+ setPivotError('');
272
+ setErrorMessage('');
273
+ setUnresolvedReportMessage('');
274
+ setPivotHint('');
275
+ };
276
+ const copySQLToClipboard = () => {
277
+ const query = pivot && pivotData ? pivotData.pivotQuery : activeQuery;
278
+ setIsCopying(true);
279
+ navigator.clipboard.writeText(query);
280
+ setTimeout(() => setIsCopying(false), 800);
281
+ };
282
+ const handleTablesChange = (newTables, updateStateStack = true) => {
283
+ setTables(newTables);
284
+ if (updateStateStack) {
285
+ setStateStack((prevStack) => [
286
+ ...prevStack,
287
+ { ...reportBuilderState, tables: newTables },
288
+ ]);
178
289
  }
179
- if (client.featureFlags?.['recommendedPivotsDisabled'] !== undefined) {
180
- setPivotRecommendationsEnabledState(!client.featureFlags?.['recommendedPivotsDisabled']);
290
+ };
291
+ const handleColumnsChange = (newColumns, fetchData, updateStateStack = true) => {
292
+ // If pivot is using removed columns, remove pivot
293
+ let removePivot = false;
294
+ if (pivot) {
295
+ const referencedColumns = [];
296
+ if (pivot.rowField) {
297
+ referencedColumns.push(pivot.rowField);
298
+ }
299
+ if (pivot.columnField) {
300
+ referencedColumns.push(pivot.columnField);
301
+ }
302
+ if (pivot.aggregations) {
303
+ pivot.aggregations.forEach((agg) => {
304
+ if (agg.valueField) {
305
+ referencedColumns.push(agg.valueField);
306
+ }
307
+ if (agg.valueField2) {
308
+ referencedColumns.push(agg.valueField2);
309
+ }
310
+ });
311
+ }
312
+ const columnMapping = referencedColumns
313
+ .map((col) => {
314
+ const column = columns.find((c) => (c.alias || c.field) === col);
315
+ return column;
316
+ })
317
+ .filter((c) => c !== undefined);
318
+ removePivot = !columnMapping.every((col) => newColumns.some((c) => (c.alias || c.field) === (col.alias || col.field) &&
319
+ c.table === col.table));
320
+ if (removePivot) {
321
+ handlePivotChange(null);
322
+ }
181
323
  }
182
- if (!initialLoad && client.publicKey) {
183
- clearAllState();
324
+ setColumns(newColumns);
325
+ if (updateStateStack) {
326
+ setStateStack((prevStack) => [
327
+ ...prevStack,
328
+ { ...reportBuilderState, columns: newColumns },
329
+ ]);
184
330
  }
185
- }, [client]);
186
- useEffect(() => {
187
- updateFirstChildWidth(askAIContainerRef, setAskAIInputWidth, { gap: 12 });
188
- updateFirstChildWidth(askAILoadingContainerRef, setAskAILoadingContainerWidth, { gap: 12 });
189
- }, [dataDisplayed]);
190
- useEffect(() => {
191
- if (isChartBuilderOpen === false) {
192
- onCloseChartBuilder && onCloseChartBuilder();
331
+ if (fetchData) {
332
+ fetchDataFromReportBuilderState({
333
+ ...reportBuilderState,
334
+ columns: newColumns,
335
+ pivot: removePivot ? null : pivot,
336
+ });
193
337
  }
194
- }, [isChartBuilderOpen]);
195
- // Whenever unique values changes, update the fieldValuesMap, used in FilterModals
196
- const updateFieldValuesMap = (curUniqueValues, tableName) => {
197
- const newFieldValues = {};
198
- if (curUniqueValues[tableName]) {
199
- for (const field of Object.keys(curUniqueValues[tableName])) {
200
- newFieldValues[field] = [];
201
- for (const value of Object.keys(curUniqueValues[tableName][field])) {
202
- newFieldValues[field]?.push(value);
203
- }
338
+ };
339
+ const handleFilterInsertion = (newFilter) => {
340
+ const newFilterStack = [...filterStack];
341
+ if (newFilterStack.length > 0) {
342
+ const tabNode = {
343
+ leaf: false,
344
+ operator: 'and',
345
+ leftNode: null,
346
+ rightNode: null,
347
+ };
348
+ newFilterStack.push(tabNode);
349
+ }
350
+ const newItem = {
351
+ leaf: true,
352
+ operator: null,
353
+ leftNode: null,
354
+ rightNode: null,
355
+ value: newFilter,
356
+ };
357
+ newFilterStack.push(newItem);
358
+ handleFilterStackChange(newFilterStack, true);
359
+ };
360
+ const handleFilterStackChange = (newFilterStack, fetchData, updateStateStack = true) => {
361
+ setFilterStack(newFilterStack);
362
+ if (newFilterStack.length === 0) {
363
+ setFilteredUniqueValues(null);
364
+ }
365
+ if (updateStateStack) {
366
+ setStateStack((prevStack) => [
367
+ ...prevStack,
368
+ { ...reportBuilderState, filterStack: newFilterStack },
369
+ ]);
370
+ }
371
+ if (fetchData) {
372
+ fetchDataFromReportBuilderState({
373
+ ...reportBuilderState,
374
+ filterStack: newFilterStack,
375
+ }, true);
376
+ }
377
+ };
378
+ const handlePivotChange = (newPivot, fetchData, updateStateStack = true) => {
379
+ setPivot(newPivot ? setTypesOnPivot(newPivot, reportColumns) : null);
380
+ setPivotHint('');
381
+ setPivotError('');
382
+ if (!newPivot) {
383
+ setPivotData(null);
384
+ if (!fetchData) {
385
+ const formattedRows = formatRows(reportRows, reportColumns, false);
386
+ setFormattedRows(formattedRows);
204
387
  }
205
388
  }
206
- setFieldValuesMap(newFieldValues);
389
+ let resetSortAndLimit = false;
390
+ if (!pivot && newPivot && (sort.length > 0 || limit)) {
391
+ setSort([]);
392
+ setLimit(null);
393
+ resetSortAndLimit = true;
394
+ }
395
+ setSortOrLimitWasReset(resetSortAndLimit);
396
+ if (updateStateStack) {
397
+ setStateStack((prevStack) => [
398
+ ...prevStack,
399
+ {
400
+ ...reportBuilderState,
401
+ pivot: newPivot,
402
+ sort: resetSortAndLimit ? [] : sort,
403
+ limit: resetSortAndLimit ? null : limit,
404
+ },
405
+ ]);
406
+ }
407
+ if (fetchData) {
408
+ fetchDataFromReportBuilderState({
409
+ ...reportBuilderState,
410
+ pivot: newPivot,
411
+ sort: resetSortAndLimit ? [] : sort,
412
+ limit: resetSortAndLimit ? null : limit,
413
+ });
414
+ }
207
415
  };
208
- useEffect(() => {
209
- // Since the TextInput component takes a required numeric width parameter,
210
- // we dynamically calculate the width of this component here.
211
- function handleResize() {
212
- updateFirstChildWidth(askAIContainerRef, setAskAIInputWidth, { gap: 12 });
213
- updateFirstChildWidth(askAILoadingContainerRef, setAskAILoadingContainerWidth, { gap: 12 });
416
+ const updatePivot = async (changeField, fieldKey) => {
417
+ if (!client || !pivot) {
418
+ return;
214
419
  }
215
- handleResize();
216
- window.addEventListener('resize', handleResize);
217
- return () => {
218
- window.removeEventListener('resize', handleResize);
219
- };
220
- }, []);
221
- const setTypesOnPivot = (newPivot, searchColumns) => ({
222
- ...newPivot,
223
- aggregations: newPivot.aggregations?.map((agg) => ({
224
- ...agg,
225
- valueFieldType: searchColumns.find((c) => c.field === agg.valueField)
226
- ?.jsType,
227
- valueField2Type: searchColumns.find((c) => c.field === agg.valueField2)
228
- ?.jsType,
229
- })),
230
- valueField: newPivot.aggregations?.[0]?.valueField,
231
- valueField2: newPivot.aggregations?.[0]?.valueField2,
232
- aggregationType: newPivot.aggregations?.[0]?.aggregationType,
233
- valueFieldType: searchColumns.find((c) => c.field === newPivot.aggregations?.[0]?.valueField)?.jsType,
234
- valueField2Type: searchColumns.find((c) => c.field === newPivot.aggregations?.[0]?.valueField2)?.jsType,
235
- });
236
- const updatePivot = async (changeField, fieldKey, prevValue) => {
237
- let newPivot = deepCopy(pivot);
238
- setPivotError(undefined);
420
+ let newPivot = { ...pivot };
421
+ setPivotError('');
239
422
  // @ts-ignore
240
423
  newPivot[fieldKey] = changeField;
241
424
  newPivot.rowLimit = undefined;
242
425
  newPivot.sort = undefined;
426
+ newPivot.sortDirection = undefined;
427
+ newPivot.sortField = undefined;
428
+ newPivot.sortFieldType = undefined;
243
429
  if (fieldKey === 'rowField') {
244
430
  if (changeField === '' || changeField === undefined) {
245
431
  setPivotColumnField(undefined);
@@ -251,22 +437,8 @@ export default function ReportBuilder({ initialTableName = '', onSubmitEditRepor
251
437
  : agg.aggregationType,
252
438
  })));
253
439
  }
254
- // check to see if the new rowField value is a date field
255
- const column = columns.find((c) => c.field === changeField);
256
- if (column?.jsType === 'date') {
257
- newPivot.rowFieldType = 'date';
258
- newPivot.sort = true;
259
- newPivot.sortField = changeField;
260
- newPivot.sortFieldType = column.format;
261
- newPivot.sortDirection = 'ASC';
262
- }
263
- else {
264
- newPivot.rowFieldType = 'string';
265
- newPivot.sort = undefined;
266
- }
267
440
  }
268
- newPivot = setTypesOnPivot(newPivot, columns);
269
- setPivot(setTypesOnPivot(newPivot, columns));
441
+ newPivot = setTypesOnPivot(newPivot, reportColumns);
270
442
  if (newPivot.aggregations?.length === 0 ||
271
443
  newPivot.aggregations?.some((agg) => !agg.aggregationType) ||
272
444
  newPivot.aggregations?.some((agg) => !agg.valueField && agg.aggregationType !== 'count')) {
@@ -277,793 +449,116 @@ export default function ReportBuilder({ initialTableName = '', onSubmitEditRepor
277
449
  setPivotError(reason);
278
450
  return;
279
451
  }
280
- resetLimit();
281
- resetSort();
282
- setPreviousPage(0);
283
- setTableLoading(true);
284
- let dateBucket = undefined;
285
- const tempDateRange = dateRanges && dateRanges[newPivot.rowField || ''];
286
- if (tempDateRange) {
287
- dateBucket = getDateBucketFromRange(tempDateRange.dateRange);
288
- }
289
- let distinctValuesForQuery = {};
290
- const prevPivot = pivot;
291
- const prevPivotData = pivotData;
292
- setPivotHint('');
293
- if (newPivot.columnField) {
294
- if (uniqueValues?.[newPivot.columnField]) {
295
- distinctValuesForQuery = uniqueValues;
296
- }
297
- else {
298
- distinctValuesForQuery = await getUniqueValuesByColumns([
299
- {
300
- field: newPivot.columnField,
301
- label: newPivot.columnField,
302
- format: 'string',
303
- },
304
- ], activeQuery, [], client, tenants, schemaData.customFields ?? {}, undefined, dashboardName);
305
- }
306
- }
307
- try {
308
- const pivotedData = await generatePivotTable({
309
- pivot: newPivot,
310
- dateBucket,
311
- report: tempReport,
312
- client,
313
- uniqueValues: distinctValuesForQuery,
314
- dashboardName,
315
- tenants,
316
- additionalProcessing: { page: REPORT_BUILDER_PAGINATION },
317
- });
318
- resetProcessing();
319
- setPivotData(pivotedData || []);
320
- const formattedRows = formatRows(pivotedData.rows, columns, true, newPivot.aggregationType);
321
- setFormattedRows(formattedRows);
322
- }
323
- catch (e) {
324
- switch (fieldKey) {
325
- case 'rowField':
326
- setPivotRowField(prevValue);
327
- break;
328
- case 'columnField':
329
- setPivotColumnField(prevValue);
330
- break;
331
- case 'aggregations':
332
- setPivotAggregations(prevValue);
333
- break;
334
- }
335
- setPivot(prevPivot);
336
- setPivotData(prevPivotData);
337
- if (e instanceof Error)
338
- setPivotError(e.message);
339
- }
340
- finally {
341
- setTableLoading(false);
342
- }
343
- };
344
- const resetLimit = (fetchData) => {
345
- const newAst = {
346
- ...baseAst,
347
- limit: null,
348
- top: null,
349
- orderby: client?.databaseType === 'mssql' ? null : baseAst.orderby,
350
- };
351
- setBaseAst((prevAst) => ({
352
- ...prevAst,
353
- limit: null,
354
- top: null,
355
- orderby: client?.databaseType === 'mssql' ? null : prevAst.orderby,
356
- }));
357
- setPivot((oldPivot) => {
358
- if (!oldPivot)
359
- return null;
360
- return {
361
- ...oldPivot,
362
- rowLimit: undefined,
363
- };
364
- });
365
- if (fetchData) {
366
- fetchReportFromASTHelper({
367
- baseAst: newAst,
368
- curPivot: pivot ? { ...pivot, rowLimit: undefined } : undefined,
369
- });
370
- }
452
+ handlePivotChange(newPivot, true);
371
453
  };
372
- const resetSort = (fetchData) => {
373
- if (pivot) {
374
- setPivot((oldPivot) => {
375
- if (!oldPivot)
376
- return null;
377
- return {
378
- ...oldPivot,
379
- sort: undefined,
380
- sortField: undefined,
381
- sortDirection: undefined,
382
- sortFieldType: undefined,
383
- };
384
- });
454
+ const handleSortChange = (newSort, fetchData, updateStateStack = true) => {
455
+ setSort(newSort);
456
+ if (updateStateStack) {
457
+ setStateStack((prevStack) => [
458
+ ...prevStack,
459
+ { ...reportBuilderState, sort: newSort },
460
+ ]);
385
461
  }
386
- const newAst = { ...baseAst, orderby: null };
387
- setBaseAst((prevAst) => ({ ...prevAst, orderby: null }));
388
462
  if (fetchData) {
389
- fetchReportFromASTHelper({
390
- baseAst: newAst,
391
- curPivot: pivot
392
- ? {
393
- ...pivot,
394
- sort: undefined,
395
- sortField: undefined,
396
- sortDirection: undefined,
397
- sortFieldType: undefined,
398
- }
399
- : undefined,
463
+ fetchDataFromReportBuilderState({
464
+ ...reportBuilderState,
465
+ sort: newSort,
400
466
  });
401
467
  }
402
468
  };
403
- const enforceOrderOnColumns = (columnNames) => {
404
- if (pivot) {
405
- const rowName = pivot.rowField;
406
- const sortFn = (a, b) => a === rowName ? -1 : b === rowName ? 1 : 0;
407
- const columnsInPivot = getColumnsInPivotExpanded();
408
- return columnNames
409
- .sort(sortFn)
410
- .filter((c) => columnsInPivot.includes(c));
411
- }
412
- // make the columnNames match the order of the selectedOrderedColumns
413
- return columnNames.sort((a, b) => selectedOrderedColumns.indexOf(a) - selectedOrderedColumns.indexOf(b));
414
- // return columnNames;
415
- };
416
- const clearAllState = () => {
417
- // We're trying to not block the main thread while resetting all the state.
418
- // This shouldn't be an issue since the dispatches shouldn't block, but
419
- // this seems to work for now. ¯\_(ツ)_/¯
420
- setTimeout(() => {
421
- setAskedAQuestion(false);
422
- setAiPrompt('');
423
- setBaseAst(null);
424
- setFormData(null);
425
- setSelectedColumns([]);
426
- setActiveQuery('');
427
- setActiveEditItem(null);
428
- setOpenPopover(null);
429
- setLoading(false);
430
- setDataDisplayed(false);
431
- setRows([]);
432
- setColumns([]);
433
- setSelectedOrderedColumns([]);
434
- setErrorMessage('');
435
- setPivotError(undefined);
436
- setFormattedRows([]);
437
- setCurrentTable('');
438
- setUniqueValues({});
439
- setPivot(null);
440
- setPivotHint('');
441
- setPivotData(null);
442
- setRecommendedPivots([]);
443
- }, 0);
444
- };
445
- const fetchGlobalUniqueValues = async (columns, currentTable, client) => {
446
- setGlobalUniqueValuesIsLoading(true);
447
- const global = await getUniqueStringValues(columns, currentTable, client, tenants, schemaData.customFields, undefined, true, undefined, dashboardName);
448
- setGlobalUniqueValues(uniqueValuesToStringMap(global));
449
- setGlobalUniqueValuesIsLoading(false);
450
- };
451
- const uniqueValuesDependenciesRef = useRef({ client, columns, currentTable });
452
- const memoizedFetchGlobalUniqueValues = useCallback(fetchGlobalUniqueValues, []);
453
- useEffect(() => {
454
- const hasChanged = !equal({ client, columns, currentTable }, uniqueValuesDependenciesRef.current);
455
- if (hasChanged && client && columns && currentTable) {
456
- memoizedFetchGlobalUniqueValues(columns, currentTable, client);
457
- uniqueValuesDependenciesRef.current = { client, columns, currentTable };
458
- }
459
- }, [client, columns, currentTable, memoizedFetchGlobalUniqueValues]);
460
- const formatRows = (rows, columns, pivot, aggregationType, dateBucket) => {
461
- const copiedRows = deepCopy(rows);
462
- if (pivot) {
463
- const formattedRows = copiedRows.map((row) => {
464
- const formattedRow = row;
465
- Object.keys(row).forEach((key) => {
466
- const column = columns.find((c) => c.field === key);
467
- let format = 'string';
468
- if (!column) {
469
- format =
470
- aggregationType === 'count'
471
- ? 'whole_number'
472
- : 'two_decimal_places';
473
- }
474
- else {
475
- format = DATE_FORMAT_TYPES.includes(column.format)
476
- ? dateBucket
477
- ? getDateFormatFromBucket(dateBucket)
478
- : 'MMM_yyyy'
479
- : 'string';
480
- }
481
- const formattedValue = quillFormat({
482
- value: row[key],
483
- format,
484
- });
485
- formattedRow[key] = formattedValue;
486
- });
487
- return formattedRow;
488
- });
489
- return formattedRows;
490
- }
491
- else {
492
- const formattedRows = copiedRows.map((row) => {
493
- return columns.reduce((formattedRow, column) => {
494
- // Apply the format function to each field in the row
495
- const formattedValue = quillFormat({
496
- value: row[column.field],
497
- format: column.format,
498
- });
499
- formattedRow[column.field] = formattedValue;
500
- return formattedRow;
501
- }, {});
502
- });
503
- return formattedRows;
504
- }
505
- };
506
- const copySQLToClipboard = () => {
507
- let query = activeQuery;
508
- if (pivot) {
509
- query = pivotToSql(pivot, activeQuery, columns);
510
- }
511
- setIsCopying(true);
512
- navigator.clipboard.writeText(query);
513
- setTimeout(() => setIsCopying(false), 800);
514
- };
515
- const clearCheckboxes = () => {
516
- const checkboxes = uniqueValues;
517
- const newValues = {};
518
- for (const table of Object.keys(checkboxes)) {
519
- newValues[table] = {};
520
- for (const column of Object.keys(checkboxes[table])) {
521
- newValues[table][column] = {};
522
- for (const variant of Object.keys(checkboxes[table][column])) {
523
- newValues[table][column][variant] = false;
524
- }
525
- }
469
+ const handleLimitChange = (newLimit, fetchData, updateStateStack = true) => {
470
+ setLimit(newLimit);
471
+ if (updateStateStack) {
472
+ setStateStack((prevStack) => [
473
+ ...prevStack,
474
+ { ...reportBuilderState, limit: newLimit },
475
+ ]);
526
476
  }
527
- setUniqueValues(newValues);
528
- updateFieldValuesMap(newValues, currentTable);
529
- };
530
- const fetchSqlQuery = async (ast, formData, fetchData = true) => {
531
477
  if (fetchData) {
532
- setLoading(true);
533
- }
534
- setErrorMessage('');
535
- try {
536
- const where = formData ? formData : ast?.where || null;
537
- const response = await fetch(`${QUILL_SERVER}/sqlify`, {
538
- method: 'POST',
539
- headers: {
540
- 'Content-Type': 'application/json',
541
- },
542
- body: JSON.stringify({
543
- ast: { ...ast, where },
544
- publicKey: client.publicKey,
545
- useNewNodeSql: true, // new flag
546
- }),
478
+ fetchDataFromReportBuilderState({
479
+ ...reportBuilderState,
480
+ limit: newLimit,
547
481
  });
548
- const data = await response.json();
549
- setActiveQuery(data.query);
550
- if (fetchData) {
551
- fetchReportFromASTHelper({
552
- baseAst: ast,
553
- newFormData: formData,
554
- });
555
- }
556
- return data.query;
557
- }
558
- catch (error) {
559
- setLoading(false);
560
- console.error(error);
561
- }
562
- };
563
- const getDateRanges = async (columns, tableName) => {
564
- const dateColumns = columns.filter((column) => {
565
- return column.fieldType === 'date';
566
- });
567
- if (dateColumns.length === 0) {
568
- return {};
569
- }
570
- const dateColumnNames = dateColumns.map((column) => {
571
- return column.field;
572
- });
573
- const dateRanges = await getQueryDateRangeByColumns(dateColumns, `Select ${dateColumnNames.join(', ')} from ${tableName}`, client, tenants, schemaData.customFields ?? {}, dashboardName);
574
- return dateRanges;
575
- };
576
- // It's just like getColumnsInPivot but we expand the columnField
577
- // if there is one to include all the variants just like it would
578
- // show up in the table. (eg. category -> ...[Fuel, Food, Other])
579
- const getColumnsInPivotExpanded = () => {
580
- if (!pivot)
581
- return [];
582
- const tables = getTableNames(baseAst);
583
- if (tables.length !== 1)
584
- return [];
585
- const result = [];
586
- const table = tables[0];
587
- const { valueField, rowField, columnField } = pivot;
588
- if (columnField &&
589
- uniqueValues[table] &&
590
- uniqueValues[table][columnField]) {
591
- result.push(...Object.keys(uniqueValues[table][columnField]));
592
- }
593
- result.push(valueField, rowField);
594
- return result.filter(Boolean);
595
- };
596
- const loadTable = async (tables) => {
597
- if (!tables?.length || !schemaData.schema?.length)
598
- return;
599
- setLoading(true);
600
- const tableInfo = tables.find((tableInfo) => tableInfo.name === initialTableName);
601
- if (tableInfo) {
602
- setUniqueValuesIsLoading(true);
603
- const newUniqueValues = await getUniqueStringValues(tableInfo.columns, initialTableName, client, tenants, schemaData.customFields, uniqueValues, true, undefined, dashboardName);
604
- if (hashCode(uniqueValues) !== hashCode(newUniqueValues)) {
605
- setUniqueValues(newUniqueValues);
606
- updateFieldValuesMap(newUniqueValues, initialTableName);
607
- }
608
- setUniqueValuesIsLoading(false);
609
- const dateRangesTemp = await getDateRanges(tableInfo.columns, initialTableName);
610
- setDateRanges(dateRangesTemp);
611
- }
612
- const columnsForTable = tables
613
- .find((t) => t.name === initialTableName)
614
- ?.columns.map((c) => c.field)
615
- .sort((a, b) => {
616
- const aIsId = a.endsWith('.id') ||
617
- a.endsWith('_id') ||
618
- a.endsWith('Id') ||
619
- a === 'id';
620
- const bIsId = b.endsWith('.id') ||
621
- b.endsWith('_id') ||
622
- b.endsWith('Id') ||
623
- b === 'id';
624
- if (aIsId && !bIsId)
625
- return 1;
626
- if (bIsId && !aIsId)
627
- return -1;
628
- return 0;
629
- });
630
- await fetchAstFromPromptHelper(`get ${columnsForTable} from ${initialTableName}`);
631
- setInitialLoad(false);
632
- };
633
- const onSchemaChange = async () => {
634
- try {
635
- setOrderedColumnNames((schemaData.schemaWithCustomFields ?? []).flatMap((table) => table.columns
636
- .map((c) => `${table.name}.${c.field}`)
637
- .sort((a, b) => {
638
- const aIsId = a.endsWith('.id') ||
639
- a.endsWith('_id') ||
640
- a.endsWith('Id') ||
641
- a === 'id';
642
- const bIsId = b.endsWith('.id') ||
643
- b.endsWith('_id') ||
644
- b.endsWith('Id') ||
645
- b === 'id';
646
- if (aIsId && !bIsId)
647
- return 1;
648
- if (bIsId && !aIsId)
649
- return -1;
650
- return 0;
651
- })));
652
- if (initialTableName) {
653
- await loadTable(schemaData.schemaWithCustomFields);
654
- }
655
- setInitialLoad(false);
656
- }
657
- catch (error) {
658
- console.error(error);
659
- }
660
- };
661
- useEffect(() => {
662
- const loadChart = async () => {
663
- let report;
664
- try {
665
- if (!reportId) {
666
- throw new Error('Report ID is required');
667
- }
668
- report = dashboard[reportId];
669
- if (!report || report.referencedTables?.length !== 1) {
670
- throw new Error('Report not found');
671
- }
672
- const { ast: newAst, pivot: newPivot, schema: curSchema, } = await fetchASTFromQuillReport(report, client, schemaData.schemaWithCustomFields);
673
- setBaseAst({ ...newAst, where: null });
674
- await fetchReportFromASTHelper({
675
- baseAst: { ...newAst, where: null },
676
- newFormData: newAst.where,
677
- curPivot: newPivot,
678
- curSchema,
679
- previousReport: report,
680
- });
681
- await onSchemaChange();
682
- setReportInfo(report);
683
- const query = await fetchSqlQuery(newAst, null, false);
684
- setActiveQuery(query);
685
- }
686
- catch (err) {
687
- console.error(err);
688
- setErrorMessage('Error when loading chart');
689
- }
690
- };
691
- if (reportId) {
692
- loadChart();
693
- }
694
- }, [dashboard[reportId || '']]);
695
- useEffect(() => {
696
- onSchemaChange();
697
- }, [schemaData.schemaWithCustomFields, initialTableName, reportId]);
698
- // Function to handle the insertion of expressions
699
- const handleInsertion = (value) => {
700
- const newFilterStack = [...filterStack];
701
- if (newFilterStack.length > 0) {
702
- const tabNode = {
703
- leaf: false,
704
- operator: 'and',
705
- leftNode: null,
706
- rightNode: null,
707
- };
708
- newFilterStack.push(tabNode);
709
- }
710
- const newItem = {
711
- leaf: true,
712
- operator: null,
713
- leftNode: null,
714
- rightNode: null,
715
- value: value,
716
- };
717
- newFilterStack.push(newItem);
718
- const tree = filterStackToFilterTree(newFilterStack);
719
- if (tree) {
720
- const newFormData = filterTreeToAst(tree, client.databaseType.toLowerCase());
721
- setFormData(newFormData);
722
- const newAst = { ...baseAst };
723
- newAst.where = newFormData;
724
- setBaseAst(newAst);
725
- fetchSqlQuery(newAst, newFormData);
726
482
  }
727
483
  };
728
- /**
729
- * Searches for the column by name and returns the field type.
730
- *
731
- * Searches the known schema and returns the fieldType of the first column
732
- * it can find with the given name. Will first search through the current
733
- * list of fields in the current query if any, then will default to searching
734
- * through the whole schema.
735
- *
736
- * If more than one column exist with the given name, it will return the first
737
- * one that it finds. This might not be the one that you intended.
738
- *
739
- * TODO: pass an optional table param to limit the search to a given table.
740
- *
741
- * @param columnName the name to search for.
742
- * @returns the fieldType string or undefined if not found.
743
- */
744
- const getColumnTypeByName = (columnName) => {
745
- const column = columns.find((col) => col.field === columnName);
746
- return column?.fieldType;
747
- };
748
- const emptyPivotColumns = () => {
749
- if (pivot && pivot.rowField && pivot.columnField) {
750
- return [
751
- { label: snakeAndCamelCaseToTitleCase(pivot.rowField) },
752
- { label: snakeAndCamelCaseToTitleCase(pivot.columnField) },
753
- ];
754
- }
755
- else if (pivot && pivot.rowField) {
756
- return [
757
- { label: snakeAndCamelCaseToTitleCase(pivot.rowField) },
758
- ...(pivot.aggregations ?? [])
759
- .filter((a) => !!a.valueField)
760
- .map((agg) => ({
761
- label: snakeAndCamelCaseToTitleCase(agg.valueField),
762
- })),
763
- ];
484
+ const handleMultiStateChange = (state, fetchData, updateStateStack = true) => {
485
+ if (state.tables !== undefined) {
486
+ handleTablesChange(state.tables, false);
764
487
  }
765
- else {
766
- return (pivot?.aggregations ?? [])
767
- .filter((a) => !!a.valueField)
768
- .map((agg) => ({
769
- label: snakeAndCamelCaseToTitleCase(agg.valueField),
770
- }));
488
+ if (state.columns !== undefined) {
489
+ handleColumnsChange(state.columns, false, false);
771
490
  }
772
- };
773
- const [previousPage, setPreviousPage] = useState(0);
774
- const [currentProcessing, setCurrentProcessing] = useState({
775
- page: REPORT_BUILDER_PAGINATION,
776
- });
777
- const [numberOfRows, setNumberOfRows] = useState(0);
778
- const [rowCountIsLoading, setRowCountIsLoading] = useState(false);
779
- const [tableLoading, setTableLoading] = useState(false);
780
- const resetProcessing = () => {
781
- setCurrentProcessing({ page: REPORT_BUILDER_PAGINATION });
782
- };
783
- const onPageChange = (page, initiator = 'ReportBuilder') => {
784
- const pagination = initiator === 'ReportBuilder'
785
- ? REPORT_BUILDER_PAGINATION
786
- : DEFAULT_PAGINATION;
787
- if (currentProcessing.page &&
788
- shouldFetchMore(pagination, page, previousPage, pivotData ? pivotData.rows.length : rows.length)) {
789
- const newPagination = { ...currentProcessing.page, page };
790
- const updatedProcessing = { ...currentProcessing, page: newPagination };
791
- setCurrentProcessing(updatedProcessing);
792
- handleRunQuery(updatedProcessing);
491
+ if (state.filterStack !== undefined) {
492
+ handleFilterStackChange(state.filterStack, false, false);
793
493
  }
794
- if (page > previousPage) {
795
- setPreviousPage(page);
494
+ if (state.pivot !== undefined) {
495
+ handlePivotChange(state.pivot, false, false);
796
496
  }
797
- };
798
- const onSortChange = (sort, isDelete) => {
799
- if (pivot) {
800
- let newPivot = null;
801
- if (isDelete) {
802
- setPivot((oldPivot) => {
803
- if (!oldPivot)
804
- return null;
805
- newPivot = {
806
- ...oldPivot,
807
- sort: undefined,
808
- sortField: undefined,
809
- sortDirection: undefined,
810
- sortFieldType: undefined,
811
- };
812
- return newPivot;
813
- });
814
- }
815
- else {
816
- setPivot((oldPivot) => {
817
- if (!oldPivot)
818
- return null;
819
- newPivot = {
820
- ...oldPivot,
821
- sort: true,
822
- sortField: sort.field,
823
- sortDirection: sort.direction,
824
- sortFieldType: getColumnTypeByName(sort.field),
825
- };
826
- return newPivot;
827
- });
828
- }
829
- fetchReportFromASTHelper({
830
- baseAst: baseAst,
831
- curPivot: newPivot,
832
- });
497
+ if (state.sort !== undefined) {
498
+ handleSortChange(state.sort, false, false);
833
499
  }
834
- else {
835
- const newAst = { ...baseAst };
836
- if (!newAst.orderby) {
837
- newAst.orderby = [];
838
- }
839
- const existingSortIndex = newAst.orderby.findIndex((item) => getFieldFromExpression(item.expr) === sort.field);
840
- if (isDelete) {
841
- if (existingSortIndex !== -1) {
842
- newAst.orderby.splice(existingSortIndex, 1);
843
- }
844
- }
845
- else if (existingSortIndex !== -1) {
846
- newAst.orderby[existingSortIndex] = {
847
- expr: { type: 'column_ref', column: sort.field },
848
- type: sort.direction.toUpperCase(),
849
- };
850
- }
851
- else {
852
- newAst.orderby.push({
853
- expr: { type: 'column_ref', column: sort.field },
854
- type: sort.direction.toUpperCase(),
855
- });
856
- }
857
- setBaseAst(deepCopy(newAst));
858
- fetchReportFromASTHelper({
859
- baseAst: newAst,
860
- });
861
- setPreviousPage(0);
500
+ if (state.limit !== undefined) {
501
+ handleLimitChange(state.limit, false, false);
862
502
  }
863
- };
864
- const onLimitChange = (limit) => {
865
- if (limit) {
866
- if (pivot) {
867
- setPivot((oldPivot) => {
868
- if (!oldPivot)
869
- return null;
870
- const newPivot = { ...oldPivot, rowLimit: limit };
871
- fetchReportFromASTHelper({ baseAst, curPivot: newPivot });
872
- return newPivot;
873
- });
874
- }
875
- else {
876
- const newAst = { ...baseAst };
877
- if (client.databaseType.toLowerCase() === 'mssql') {
878
- newAst.top = {
879
- value: limit,
880
- };
881
- }
882
- else {
883
- newAst.limit = {
884
- seperator: '',
885
- value: [
886
- {
887
- type: 'number',
888
- value: limit,
889
- },
890
- ],
891
- };
892
- }
893
- setBaseAst(deepCopy(newAst));
894
- fetchSqlQuery(deepCopy(newAst), undefined, true);
895
- }
503
+ if (updateStateStack) {
504
+ setStateStack((prevStack) => [
505
+ ...prevStack,
506
+ { ...reportBuilderState, ...state },
507
+ ]);
896
508
  }
897
- else {
898
- resetLimit(true);
509
+ if (fetchData) {
510
+ fetchDataFromReportBuilderState({ ...reportBuilderState, ...state }, !!state.filterStack, !!state.tables);
899
511
  }
900
- setOpenPopover(null);
901
512
  };
902
- const fetchRowCount = async (processing, includeFilters) => {
903
- if (!client || !activeQuery) {
513
+ const handleUndo = () => {
514
+ if (stateStack.length <= 1) {
904
515
  return;
905
516
  }
906
- setRowCountIsLoading(true);
907
- const tableInfo = await fetchResultsByQuery({
908
- query: activeQuery,
909
- client,
910
- tenants,
911
- processing,
912
- customFields: schemaData.customFields,
913
- filters: includeFilters ? specificDashboardFilters : undefined,
914
- dateField: includeFilters
915
- ? (tempReport.dateField ?? reportInfo?.dateField)
916
- : undefined,
917
- rowsOnly: false,
918
- rowCountOnly: true,
919
- filterMap: undefined,
920
- dashboardName,
921
- });
922
- if (tableInfo.rowCount) {
923
- setNumberOfRows(tableInfo.rowCount);
924
- // @ts-ignore
925
- setTempReport((tempReport) => ({
926
- ...tempReport,
927
- rowCount: tableInfo.rowCount,
928
- }));
929
- }
930
- setRowCountIsLoading(false);
517
+ const previousState = stateStack[stateStack.length - 2];
518
+ setStateStack((prevStack) => prevStack.slice(0, -1));
519
+ handleMultiStateChange(previousState, true, false);
931
520
  };
932
- const fetchRowCountFromAST = async (ast, where) => {
933
- setRowCountIsLoading(true);
934
- const tableData = await fetchTableByAST({ ...ast, where }, client, tenants, { page: REPORT_BUILDER_PAGINATION }, schemaData.customFields, false, true, dashboardName);
935
- if (tableData.rowCount) {
936
- setNumberOfRows(tableData.rowCount);
937
- // @ts-ignore
938
- setTempReport((tempReport) => ({
939
- ...tempReport,
940
- rowCount: tableData.rowCount,
941
- }));
942
- }
943
- setRowCountIsLoading(false);
944
- };
945
- const handleRunQuery = async (processing, resetRows = false, includeFilters = false) => {
946
- try {
947
- const isPivotPagination = !!(pivot && pivotData);
948
- setErrorMessage('');
949
- setTableLoading(true);
950
- const tableInfo = await fetchResultsByQuery({
951
- query: isPivotPagination ? pivotData.pivotQuery : activeQuery,
952
- comparisonQuery: pivot && pivotData ? pivotData.comparisonPivotQuery : undefined,
953
- client,
954
- tenants,
955
- processing,
956
- customFields: schemaData.customFields,
957
- rowsOnly: true,
958
- dashboardName,
959
- pivot: pivot,
960
- getPivotRowCount: false,
961
- });
962
- if (tableInfo.error) {
963
- throw new Error(tableInfo.error);
964
- }
965
- else if (tableInfo.rows.length === 0) {
966
- throw new Error('No data found');
967
- }
968
- if (!isPivotPagination) {
969
- // fetching row count for non-pivot query
970
- fetchRowCount(processing, includeFilters);
971
- let tempRows = [...rows, ...tableInfo.rows];
972
- if (resetRows) {
973
- tempRows = tableInfo.rows;
974
- setPreviousPage(0);
975
- }
976
- setRows(tempRows);
977
- setFormattedRows(formatRowsFromReport({ rows: tempRows, columns: tableInfo.columns }));
978
- setTempReport((tempReport) => ({
979
- ...tempReport,
980
- rows: tempRows,
981
- rowCount: tableInfo.rowCount ?? tempReport.rowCount,
982
- }));
983
- setColumns(tableInfo.columns);
984
- }
985
- else {
986
- let tempRows = [...pivotData.rows, ...tableInfo.rows];
987
- if (resetRows) {
988
- tempRows = tableInfo.rows;
989
- setPreviousPage(0);
990
- }
991
- setPivotData((oldPivotData) => {
992
- if (oldPivotData) {
993
- return {
994
- ...oldPivotData,
995
- rows: tempRows,
996
- columns: tableInfo.columns,
997
- };
998
- }
999
- return null;
1000
- });
1001
- setFormattedRows(formatRowsFromReport({ rows: tempRows, columns: tableInfo.columns }));
1002
- }
1003
- setTableLoading(false);
1004
- }
1005
- catch (e) {
1006
- setTableLoading(false);
1007
- setErrorMessage('Failed to run SQL query: ' + e.message);
1008
- setRows([]);
1009
- setColumns([]);
521
+ const fetchDataFromReportBuilderState = (state, filtersChanged, tablesChanged) => {
522
+ if (!client) {
1010
523
  return;
1011
524
  }
1012
- };
1013
- /**
1014
- * Return whether all columns have been selected (used to hide select all
1015
- * and show clear button).
1016
- */
1017
- const isSelectedAllColumns = () => {
1018
- if (selectedColumns.length < 1)
1019
- return false;
1020
- const allColumns = orderedColumnNames.filter((row) => {
1021
- const [table] = row.split('.');
1022
- const selectedTable = selectedColumns[0].split('.')[0];
1023
- return selectedTable === table;
525
+ const ast = reportBuilderStateToAst(state, client.databaseType?.toLowerCase() || 'postgresql');
526
+ fetchReportFromASTHelper({
527
+ ast,
528
+ pivot: state.pivot,
529
+ requiresNewFilteredUniqueValues: filtersChanged,
530
+ requiresNewUnfilteredUniqueValues: tablesChanged,
1024
531
  });
1025
- return selectedColumns.length === allColumns.length;
1026
532
  };
1027
- const nameToColumn = (name) => ({
1028
- type: 'expr',
1029
- expr: {
1030
- type: 'column_ref',
1031
- table: null,
1032
- column: name,
1033
- },
1034
- as: null,
1035
- });
1036
- const updateUniqueValue = (uniqueStrings, table) => {
1037
- const uniqueStringsObj = uniqueValues ?? {};
1038
- uniqueStringsObj[table] = uniqueStrings ?? {};
1039
- setUniqueValues(uniqueStringsObj);
1040
- updateFieldValuesMap(uniqueStringsObj, table);
1041
- return uniqueStringsObj;
1042
- };
1043
- const fetchReportFromASTHelper = async ({ baseAst, newFormData, curPivot, curSchema, previousReport, keepPivotHint = false, }) => {
1044
- const curFormData = newFormData !== undefined ? newFormData : formData;
533
+ const fetchReportFromASTHelper = async ({ ast, pivot, previousReport, requiresNewFilteredUniqueValues = false, requiresNewUnfilteredUniqueValues = false, }) => {
1045
534
  let reportBuilderInfo = undefined;
535
+ setErrorMessage('');
536
+ const schema = filteredSchema;
1046
537
  try {
1047
- setLoading(true);
538
+ if (!client || reportBuilderLoading) {
539
+ return;
540
+ }
541
+ setReportBuilderLoading(true);
1048
542
  reportBuilderInfo = await fetchReportBuilderDataFromAST({
1049
- baseAst,
1050
- formData: curFormData,
1051
- schema: curSchema ?? schemaData.schemaWithCustomFields,
543
+ baseAst: ast,
544
+ schema,
1052
545
  client,
1053
546
  tenants,
1054
- pivot: curPivot ?? pivot,
1055
- previousFormData: formData,
1056
- currentTable,
547
+ pivot: pivot ?? undefined,
1057
548
  previousRelevant: {
1058
- uniqueStrings: uniqueValues,
549
+ uniqueStringsByTable: unfilteredUniqueValues,
1059
550
  dateRanges: dateRanges ?? {},
551
+ uniqueStringsByColumn: requiresNewFilteredUniqueValues
552
+ ? {}
553
+ : columnUniqueValues,
1060
554
  },
1061
- report: previousReport ?? reportInfo ?? undefined,
555
+ requiresNewFilteredUniqueValues,
556
+ report: previousReport,
1062
557
  customFields: schemaData.customFields,
1063
558
  skipUniqueValues: true,
1064
559
  skipRowCount: true,
1065
560
  processing: { page: REPORT_BUILDER_PAGINATION },
1066
- dashboardName,
561
+ dashboardName: destinationDashboard,
1067
562
  });
1068
563
  if (reportBuilderInfo.error) {
1069
564
  throw new Error(reportBuilderInfo.error);
@@ -1072,58 +567,41 @@ export default function ReportBuilder({ initialTableName = '', onSubmitEditRepor
1072
567
  catch (err) {
1073
568
  if (err instanceof Error) {
1074
569
  setErrorMessage(err.message);
1075
- setLoading(false);
570
+ setReportBuilderLoading(false);
1076
571
  return { error: true, message: err.message, rows: [] };
1077
572
  }
1078
- setLoading(false);
573
+ setReportBuilderLoading(false);
1079
574
  setErrorMessage('Failed to fetch');
1080
575
  return { error: true, message: 'Failed to fetch', rows: [] };
1081
576
  }
1082
577
  if (!reportBuilderInfo) {
1083
- setLoading(false);
578
+ setReportBuilderLoading(false);
1084
579
  setErrorMessage('Failed to fetch');
1085
580
  return;
1086
581
  }
1087
582
  const cleanedReport = await cleanDashboardItem({
1088
583
  item: reportBuilderInfo.report,
1089
- dashboardFilters: specificDashboardFilters,
584
+ dashboardFilters: [],
1090
585
  client,
1091
586
  customFields: schemaData.customFields,
1092
587
  skipPivotFetch: true,
1093
588
  });
589
+ // set tempReport
1094
590
  setTempReport({
1095
591
  ...cleanedReport,
1096
592
  pagination: REPORT_BUILDER_PAGINATION,
1097
593
  });
1098
- fetchRowCountFromAST(baseAst, curFormData);
1099
- const reportTable = reportBuilderInfo.table;
1100
- const reportBuilderInfoColumns = reportBuilderInfo.columns.map((column) => {
1101
- return `${reportTable}.${column.field}`;
1102
- });
1103
- const prevFormData = formData;
1104
- const prevTable = currentTable;
1105
- setRows(reportBuilderInfo.rows);
1106
- setPreviousPage(0);
1107
- if (!(client.databaseType.toLowerCase() === 'bigquery') ||
594
+ setActiveQuery(reportBuilderInfo.query);
595
+ // table data
596
+ fetchRowCountFromAST(ast, ast.where);
597
+ setReportRows(reportBuilderInfo.rows);
598
+ setFormattedRows(reportBuilderInfo.formattedRows);
599
+ resetProcessing();
600
+ if (!(client.databaseType?.toLowerCase() === 'bigquery') ||
1108
601
  (reportBuilderInfo.rows && reportBuilderInfo.rows.length > 0)) {
1109
- setColumns(reportBuilderInfo.columns);
1110
- setSelectedColumns(reportBuilderInfoColumns);
1111
- }
1112
- if (prevTable !== reportBuilderInfo.table) {
1113
- setSelectedOrderedColumns([]); // reset selected ordered columns
1114
- }
1115
- setPivot(reportBuilderInfo.pivot
1116
- ? setTypesOnPivot(reportBuilderInfo.pivot, reportBuilderInfo.columns)
1117
- : null);
1118
- if (!keepPivotHint) {
1119
- setPivotHint('');
602
+ setReportColumns(reportBuilderInfo.columns);
1120
603
  }
1121
604
  setPivotData(reportBuilderInfo.pivotData);
1122
- setDateRanges(reportBuilderInfo.dateRanges);
1123
- setFormattedRows(reportBuilderInfo.formattedRows);
1124
- setDataDisplayed(true);
1125
- setCurrentTable(reportBuilderInfo.table);
1126
- setFormData(curFormData);
1127
605
  if (reportBuilderInfo.pivot) {
1128
606
  setPivotRowField(reportBuilderInfo.pivot.rowField);
1129
607
  setPivotColumnField(reportBuilderInfo.pivot.columnField);
@@ -1132,107 +610,109 @@ export default function ReportBuilder({ initialTableName = '', onSubmitEditRepor
1132
610
  valueField: reportBuilderInfo.pivot.valueField,
1133
611
  valueField2: reportBuilderInfo.pivot.valueField2,
1134
612
  aggregationType: reportBuilderInfo.pivot.aggregationType,
1135
- },
1136
- ]);
1137
- }
1138
- else {
1139
- setLoading(false);
1140
- }
1141
- setReportInfo(reportBuilderInfo.report);
1142
- const schema = curSchema ?? schemaData.schemaWithCustomFields;
1143
- const tableInfo = schema.find((tableInfo) => tableInfo.name === reportBuilderInfo?.table);
1144
- let query = reportBuilderInfo.query;
1145
- if (!query && tableInfo) {
1146
- const ast = baseAst ??
1147
- createBasicSelectASTFromColumns(tableInfo.columns, reportBuilderInfo.table);
1148
- const queryResult = await fetchSqlQuery({
1149
- ...ast,
1150
- where: curFormData,
1151
- }, curFormData, false);
1152
- if (queryResult.error) {
1153
- console.error(queryResult.error);
1154
- }
1155
- else {
1156
- query = queryResult;
1157
- setActiveQuery(queryResult);
1158
- }
1159
- }
1160
- else if (query) {
1161
- setActiveQuery(query);
613
+ },
614
+ ]);
1162
615
  }
616
+ setPivot(reportBuilderInfo.pivot);
617
+ setPivotHint(reportBuilderInfo.pivotHint);
618
+ setDateRanges(reportBuilderInfo.dateRanges);
619
+ const tableNames = reportBuilderInfo.tables;
620
+ const columnInfo = tableNames.flatMap((table) => {
621
+ const tableInfo = schema.find((tableInfo) => tableInfo.name === table);
622
+ if (!tableInfo) {
623
+ return [];
624
+ }
625
+ return tableInfo.columns.map((col) => ({ ...col, table }));
626
+ });
1163
627
  // fetch unique values after everything else since it is the most expensive
1164
- if (prevTable !== reportBuilderInfo.table ||
1165
- !equal(prevFormData, curFormData)) {
1166
- try {
1167
- setUniqueValuesIsLoading(true);
628
+ try {
629
+ let uniqueStrings = filteredUniqueValues ?? unfilteredUniqueValues;
630
+ let uniqueValuesByColumn = columnUniqueValues;
631
+ if (requiresNewUnfilteredUniqueValues) {
632
+ fetchGlobalUniqueValues(columnInfo, tableNames);
633
+ }
634
+ if (requiresNewFilteredUniqueValues) {
1168
635
  if (reportBuilderInfo.pivot) {
1169
- setUnresolvedReportMessage('Processing pivot selection options...');
1170
- }
1171
- if (!tableInfo) {
1172
- throw new Error('Table info not found');
1173
- }
1174
- const uniqueStrings = await getUniqueStringValues(tableInfo.columns, reportBuilderInfo.table, client, tenants, schemaData.customFields, undefined, true, query, dashboardName);
1175
- const newUnique = updateUniqueValue(uniqueStrings, reportBuilderInfo.table);
1176
- let pivotChanged = false;
1177
- let newPivot;
1178
- if (reportBuilderInfo.pivot &&
1179
- !isValidPivotForReport(reportBuilderInfo.pivot, newUnique, reportBuilderInfo.table, reportBuilderInfo.columns)) {
1180
- const { pivot, hint } = makePivotValid(reportBuilderInfo.pivot, newUnique, reportBuilderInfo.table, reportBuilderInfo.columns);
1181
- if (pivot) {
1182
- setPivot(setTypesOnPivot(pivot, reportBuilderInfo.columns));
1183
- newPivot = pivot;
1184
- pivotChanged = true;
1185
- setPivotHint(hint);
1186
- }
1187
- else {
1188
- setUnresolvedReportMessage('Not a valid pivot');
1189
- }
636
+ // if there's a pivot, these values would have had to been fetched
637
+ uniqueStrings = reportBuilderInfo.uniqueStringsByTable;
638
+ uniqueValuesByColumn = reportBuilderInfo.uniqueStringsByColumn;
1190
639
  }
1191
640
  else {
1192
- setUnresolvedReportMessage('');
1193
- }
1194
- setUniqueValuesIsLoading(false);
1195
- if (pivotChanged) {
1196
- fetchReportFromASTHelper({
1197
- baseAst,
1198
- newFormData: curFormData,
1199
- curPivot: newPivot,
1200
- curSchema,
1201
- previousReport,
1202
- keepPivotHint: true,
641
+ setFilteredUniqueValuesIsLoading(true);
642
+ const starQuery = await fetchSqlQuery(createSelectStarFromAst(ast), client);
643
+ uniqueStrings = await getUniqueStringValuesByTable({
644
+ columns: columnInfo,
645
+ tables: tableNames,
646
+ client,
647
+ tenants,
648
+ customFields: schemaData.customFields ?? undefined,
649
+ withExceededColumns: true,
650
+ dashboardName: destinationDashboard,
651
+ queryTemplate: starQuery.query,
652
+ });
653
+ uniqueValuesByColumn = {};
654
+ reportBuilderInfo.reportBuilderColumns.forEach((col) => {
655
+ uniqueValuesByColumn[col.alias || col.field] =
656
+ uniqueStrings[col.table || '']?.[col.field] ?? [];
1203
657
  });
1204
- }
1205
- else {
1206
- setLoading(false);
1207
658
  }
1208
659
  }
1209
- catch (err) {
1210
- if (err instanceof Error) {
1211
- setErrorMessage(err.message);
1212
- setLoading(false);
1213
- return { error: true, message: err.message, rows: [] };
1214
- }
1215
- setLoading(false);
1216
- setErrorMessage('Failed to fetch');
1217
- return { error: true, message: 'Failed to fetch', rows: [] };
660
+ setFilteredUniqueValues(uniqueStrings);
661
+ setFilteredUniqueValuesIsLoading(false);
662
+ setColumnUniqueValues(uniqueValuesByColumn);
663
+ setReportBuilderLoading(false);
664
+ }
665
+ catch (err) {
666
+ if (err instanceof Error) {
667
+ setErrorMessage(err.message);
668
+ setReportBuilderLoading(false);
669
+ return { error: true, message: err.message, rows: [] };
1218
670
  }
671
+ setReportBuilderLoading(false);
672
+ setErrorMessage('Failed to fetch');
673
+ return { error: true, message: 'Failed to fetch', rows: [] };
1219
674
  }
1220
- else {
1221
- setLoading(false);
675
+ };
676
+ const fetchRowCountFromAST = async (ast, where) => {
677
+ setRowCountIsLoading(true);
678
+ if (!client) {
679
+ return;
680
+ }
681
+ const tableData = await fetchTableByAST({ ...ast, where }, client, tenants, { page: REPORT_BUILDER_PAGINATION }, schemaData.customFields, false, true, destinationDashboard);
682
+ if (tableData.rowCount) {
683
+ setNumberOfRows(tableData.rowCount);
684
+ // @ts-ignore
685
+ setTempReport((tempReport) => ({
686
+ ...tempReport,
687
+ rowCount: tableData.rowCount,
688
+ }));
1222
689
  }
690
+ setRowCountIsLoading(false);
1223
691
  };
1224
692
  const fetchAstFromPromptHelper = async (overridePrompt) => {
1225
693
  let astInfo = {};
1226
694
  const prompt = overridePrompt || aiPrompt;
695
+ if (!client) {
696
+ return;
697
+ }
1227
698
  if (!prompt) {
1228
699
  setErrorMessage('Please supply a prompt.');
1229
700
  return;
1230
701
  }
1231
702
  try {
1232
- setLoading(true);
703
+ setReportBuilderLoading(true);
1233
704
  setAskAILoading(true);
1234
705
  setErrorMessage('');
1235
- astInfo = await fetchAndProcessASTFromPrompt(prompt, schemaData.schemaWithCustomFields, client, pivot ?? undefined, activeQuery, currentTable, dashboardName, tenants);
706
+ astInfo = await fetchAndProcessASTFromPrompt({
707
+ aiPrompt: prompt,
708
+ schema: filteredSchema,
709
+ client,
710
+ prevPivot: pivot ?? undefined,
711
+ currentQuery: activeQuery,
712
+ prevTable: tables?.[0]?.name,
713
+ dashboardName: destinationDashboard,
714
+ tenants,
715
+ });
1236
716
  if (astInfo.error) {
1237
717
  throw new Error(astInfo.error);
1238
718
  }
@@ -1241,246 +721,263 @@ export default function ReportBuilder({ initialTableName = '', onSubmitEditRepor
1241
721
  if (err instanceof Error) {
1242
722
  setErrorMessage(err.message);
1243
723
  }
1244
- setLoading(false);
724
+ setReportBuilderLoading(false);
1245
725
  setAskAILoading(false);
1246
726
  return;
1247
727
  }
1248
728
  // check if pivot works with ReportBuilder constraints
1249
- if (uniqueValues[currentTable ?? ''] &&
729
+ if (Object.keys(columnUniqueValues).length > 0 &&
1250
730
  astInfo.pivot &&
1251
- !isValidPivotForReport(astInfo.pivot)) {
731
+ !isValidPivotForReport(astInfo.pivot, columnUniqueValues, reportColumns)) {
1252
732
  astInfo.pivot = null;
1253
733
  astInfo.ast.groupby = null;
1254
734
  }
1255
- const _tables = getTableNames(astInfo.ast);
1256
- const _table = _tables[0] ? _tables[0] : '';
1257
- const _columns = schemaData.schemaWithCustomFields.find((table) => {
1258
- return table.name === _table;
1259
- })?.columns;
1260
- // parse the whereAst first
1261
- const filterTree = astToFilterTree(astInfo.whereAST, client.databaseType?.toLowerCase(), _columns);
1262
- const cleanAst = filterTree
1263
- ? filterTreeToAst(filterTree, client.databaseType?.toLowerCase())
1264
- : null;
1265
- setFormData(cleanAst);
1266
- astInfo.ast.where = cleanAst;
1267
- setBaseAst(astInfo.ast);
1268
- fetchSqlQuery(astInfo.ast, cleanAst, false);
1269
735
  setAskAILoading(false);
1270
- await fetchReportFromASTHelper({
1271
- baseAst: astInfo.ast,
1272
- newFormData: cleanAst,
1273
- curPivot: astInfo.pivot,
736
+ const newState = astToReportBuilderState(astInfo.ast, client.databaseType || 'postgresql', filteredSchema);
737
+ handleMultiStateChange({ ...newState, pivot: astInfo.pivot }, true);
738
+ };
739
+ const fetchGlobalUniqueValues = async (columns, tables) => {
740
+ if (!client) {
741
+ return;
742
+ }
743
+ setUnfilteredUniqueValuesIsLoading(true);
744
+ const uniqueStrings = await getUniqueStringValuesByTable({
745
+ columns,
746
+ tables,
747
+ client,
748
+ tenants,
749
+ customFields: schemaData.customFields ?? undefined,
750
+ withExceededColumns: true,
751
+ dashboardName: destinationDashboard,
1274
752
  });
753
+ setUnfilteredUniqueValues(uniqueStrings);
754
+ setUnfilteredUniqueValuesIsLoading(false);
1275
755
  };
1276
- const pivotFormData = (pivot) => {
1277
- const yAxisField = pivot.columnField ?? disambiguatedValueField(pivot) ?? '';
1278
- const yAxisLabel = tempReport?.yAxisFields && tempReport?.yAxisFields?.length > 0
1279
- ? (tempReport.yAxisFields[0]?.label ?? '')
1280
- : (disambiguatedValueField(pivot) ?? '');
1281
- // date labels for pivots should be treated like strings since they are
1282
- const yAxisIsDate = pivot.columnField
1283
- ? isDateField(pivot.columnFieldType ?? '')
1284
- : false;
1285
- const chartType = tempReport?.chartType ?? (pivot.rowField ? 'column' : 'metric');
1286
- const result = {
1287
- pivot,
1288
- chartType: chartType,
1289
- xAxisField: pivot.rowField
1290
- ? pivot.rowField
1291
- : disambiguatedValueField(pivot),
1292
- xAxisFormat: isDateType(pivot.rowFieldType ?? '')
1293
- ? 'string'
1294
- : isNumberType(pivot.rowFieldType ?? '')
1295
- ? 'whole_number'
1296
- : 'string',
1297
- xAxisLabel: tempReport?.xAxisLabel || pivot.rowField,
1298
- yAxisFields: [
1299
- {
1300
- field: yAxisField,
1301
- label: yAxisLabel,
1302
- format: yAxisIsDate
1303
- ? 'string'
1304
- : tempReport?.yAxisFields && tempReport?.yAxisFields?.length > 0
1305
- ? (tempReport?.yAxisFields[0]?.format ?? 'whole_number')
1306
- : (tempReport?.columns.find((col) => col.field === disambiguatedValueField(pivot))?.format ?? 'whole_number'),
1307
- },
1308
- ],
1309
- };
1310
- return result;
756
+ const resetProcessing = () => {
757
+ setCurrentProcessing({ page: REPORT_BUILDER_PAGINATION });
758
+ setPreviousPage(0);
1311
759
  };
1312
- const makePivotValid = (pivot, uniqueValuesForPivot, reportTable, reportColumns) => {
1313
- if (!pivot) {
1314
- return { pivot: null, hint: '' };
1315
- }
1316
- // try flipping row and column field
1317
- let newPivot = {
1318
- ...pivot,
1319
- rowField: pivot.columnField,
1320
- columnField: pivot.rowField,
1321
- };
1322
- if (isValidPivotForReport(newPivot, uniqueValuesForPivot, reportTable, reportColumns)) {
1323
- return {
1324
- pivot: newPivot,
1325
- hint: 'Flipping pivot row and column fields to maintain validity',
1326
- };
760
+ const handlePagination = async (processing) => {
761
+ try {
762
+ if (!client) {
763
+ return;
764
+ }
765
+ const isPivotPagination = !!(pivot && pivotData);
766
+ setErrorMessage('');
767
+ setTableLoading(true);
768
+ const tableInfo = await fetchResultsByQuery({
769
+ query: isPivotPagination ? pivotData.pivotQuery : activeQuery,
770
+ comparisonQuery: pivot && pivotData ? pivotData.comparisonPivotQuery : undefined,
771
+ client,
772
+ tenants,
773
+ processing,
774
+ customFields: schemaData.customFields,
775
+ rowsOnly: true,
776
+ dashboardName: destinationDashboard,
777
+ pivot: pivot,
778
+ getPivotRowCount: false,
779
+ });
780
+ if (tableInfo.error) {
781
+ throw new Error(tableInfo.error);
782
+ }
783
+ else if (tableInfo.rows.length === 0) {
784
+ throw new Error('No data found');
785
+ }
786
+ if (!isPivotPagination) {
787
+ const tempRows = [...reportRows, ...tableInfo.rows];
788
+ setReportRows(tempRows);
789
+ setFormattedRows(formatRowsFromReport({ rows: tempRows, columns: tableInfo.columns }));
790
+ setTempReport((tempReport) => ({
791
+ ...tempReport,
792
+ rows: tempRows,
793
+ rowCount: tableInfo.rowCount ?? tempReport.rowCount,
794
+ }));
795
+ }
796
+ else {
797
+ const tempRows = [...pivotData.rows, ...tableInfo.rows];
798
+ setPivotData((oldPivotData) => {
799
+ if (oldPivotData) {
800
+ return {
801
+ ...oldPivotData,
802
+ rows: tempRows,
803
+ columns: tableInfo.columns,
804
+ };
805
+ }
806
+ return null;
807
+ });
808
+ setFormattedRows(formatRowsFromReport({ rows: tempRows, columns: tableInfo.columns }));
809
+ }
810
+ setTableLoading(false);
1327
811
  }
1328
- // try removing column field
1329
- newPivot = {
1330
- ...pivot,
1331
- rowField: pivot.rowField,
1332
- columnField: undefined,
1333
- };
1334
- if (isValidPivotForReport(newPivot, uniqueValuesForPivot, reportTable, reportColumns)) {
1335
- return {
1336
- pivot: newPivot,
1337
- hint: 'Removing column field to maintain validity',
1338
- };
812
+ catch (e) {
813
+ setTableLoading(false);
814
+ setErrorMessage('Failed to run SQL query: ' + e.message);
815
+ setReportRows([]);
816
+ setReportColumns([]);
817
+ return;
1339
818
  }
1340
- return { pivot: null, hint: '' };
1341
819
  };
1342
- const isValidPivotForReport = (pivot, uniqueValuesForPivot, reportTable, reportColumns) => {
1343
- if (!isValidPivot(pivot).valid) {
1344
- return false;
1345
- }
1346
- const uniqueValuesToCheck = uniqueValuesForPivot ?? uniqueValues;
1347
- // check that pivot rows and columns
1348
- const pivotTable = reportTable ?? currentTable;
1349
- const pivotColumns = reportColumns ?? columns;
1350
- const possibleOptions = getPossiblePivotFieldOptions(pivotColumns, uniqueValuesToCheck[pivotTable] ?? {});
1351
- if (pivot.rowField &&
1352
- pivot.columnField &&
1353
- pivot.rowField === pivot.columnField) {
1354
- return false;
820
+ const onPageChange = (page, initiator = 'ReportBuilder') => {
821
+ const pagination = initiator === 'ReportBuilder'
822
+ ? REPORT_BUILDER_PAGINATION
823
+ : DEFAULT_PAGINATION;
824
+ if (currentProcessing.page &&
825
+ shouldFetchMore(pagination, page, previousPage, pivotData ? pivotData.rows.length : reportRows.length)) {
826
+ const newPagination = { ...currentProcessing.page, page };
827
+ const updatedProcessing = { ...currentProcessing, page: newPagination };
828
+ setCurrentProcessing(updatedProcessing);
829
+ handlePagination(updatedProcessing);
1355
830
  }
1356
- if (pivot.rowField && !possibleOptions.rowFields.includes(pivot.rowField)) {
1357
- return false;
831
+ if (page > previousPage) {
832
+ setPreviousPage(page);
1358
833
  }
1359
- if (pivot.columnField &&
1360
- !possibleOptions.columnFields.includes(pivot.columnField)) {
1361
- return false;
834
+ };
835
+ const onSortChange = (newSort, isDelete) => {
836
+ if (!newSort.field) {
837
+ return;
1362
838
  }
1363
- for (const agg of pivot.aggregations ?? []) {
1364
- if (agg.valueField &&
1365
- !possibleOptions.valueFields.includes(agg.valueField)) {
1366
- return false;
839
+ if (pivot) {
840
+ let newPivot = null;
841
+ if (isDelete) {
842
+ newPivot = {
843
+ ...pivot,
844
+ sort: undefined,
845
+ sortField: undefined,
846
+ sortDirection: undefined,
847
+ sortFieldType: undefined,
848
+ };
849
+ }
850
+ else {
851
+ newPivot = {
852
+ ...pivot,
853
+ sort: true,
854
+ sortField: newSort.field,
855
+ sortDirection: newSort.direction,
856
+ sortFieldType: reportColumns.find((col) => col.field === newSort.field)?.fieldType,
857
+ };
1367
858
  }
859
+ handlePivotChange(newPivot, true);
1368
860
  }
1369
- if (!pivot.aggregations || pivot.aggregations.length === 0) {
1370
- return false;
861
+ else {
862
+ const updatedSort = [...sort];
863
+ const existingSortIndex = updatedSort.findIndex((item) => item.field === newSort.field);
864
+ if (isDelete) {
865
+ if (existingSortIndex !== -1) {
866
+ updatedSort.splice(existingSortIndex, 1);
867
+ }
868
+ }
869
+ else if (existingSortIndex !== -1) {
870
+ updatedSort[existingSortIndex] = {
871
+ field: newSort.field,
872
+ direction: newSort.direction,
873
+ };
874
+ }
875
+ else {
876
+ updatedSort.push({
877
+ field: newSort.field,
878
+ direction: newSort.direction,
879
+ });
880
+ }
881
+ handleSortChange(updatedSort, true);
1371
882
  }
1372
- return true;
1373
883
  };
1374
- const handleDeleteColumn = (name) => {
1375
- if (!baseAst || !baseAst.columns.length || selectedColumns.length === 1) {
1376
- clearAllState();
1377
- return;
884
+ const onLimitChange = (limit) => {
885
+ if (limit) {
886
+ if (pivot) {
887
+ const newPivot = { ...pivot, rowLimit: limit };
888
+ handlePivotChange(newPivot, true);
889
+ }
890
+ else {
891
+ handleLimitChange({ value: limit }, true);
892
+ }
1378
893
  }
1379
- setSelectedColumns((selectedColumns) => selectedColumns.filter((column) => !column.endsWith(name)));
1380
- const columns = baseAst.columns.filter((col) => {
1381
- if (col.expr.type === 'column_ref') {
1382
- return (col.expr.column !== name &&
1383
- (!col.expr.column.expr || col.expr.column.expr.value !== name));
894
+ else {
895
+ if (pivot) {
896
+ const newPivot = { ...pivot, rowLimit: undefined };
897
+ handlePivotChange(newPivot, true);
1384
898
  }
1385
- else if (col.as) {
1386
- return col.as !== name;
899
+ else {
900
+ handleLimitChange(null, true);
1387
901
  }
1388
- return col.expr.value !== name;
1389
- });
1390
- if (columns.length === 0) {
1391
- clearAllState();
1392
- return;
1393
902
  }
1394
- const newAst = deepCopy({ ...baseAst, columns });
1395
- setBaseAst(newAst);
1396
- fetchSqlQuery(newAst);
1397
903
  };
1398
- const DraggableItem = ({ id, label, onDelete }) => {
1399
- const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: id });
1400
- const style = {
1401
- transform: DND_CSS.Transform.toString(transform),
1402
- transition,
904
+ useEffect(() => {
905
+ // Since the TextInput component takes a required numeric width parameter,
906
+ // we dynamically calculate the width of this component here.
907
+ function handleResize() {
908
+ updateFirstChildWidth(askAIContainerRef, setAskAIInputWidth, { gap: 12 });
909
+ }
910
+ handleResize();
911
+ window.addEventListener('resize', handleResize);
912
+ return () => {
913
+ window.removeEventListener('resize', handleResize);
1403
914
  };
1404
- return (_jsx("div", { style: { ...style }, ref: setNodeRef, children: _jsx(DraggableColumnComponent, { label: snakeAndCamelCaseToTitleCase(label), onDelete: onDelete, DragHandle: (props) => (_jsx("div", { style: {
1405
- cursor: 'grab',
1406
- }, ...attributes, ...listeners, children: _jsx(props.dragIcon, {}) })), deleteDisabled: loading || tableLoading }) }));
1407
- };
1408
- function DraggableColumns() {
1409
- const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, {
1410
- coordinateGetter: sortableKeyboardCoordinates,
1411
- }));
1412
- // When a drag event ends, switch the item order.
1413
- function handleDragEnd(event) {
1414
- const { active, over } = event;
1415
- if (!active || !over)
915
+ }, []);
916
+ useEffect(() => {
917
+ if (!client) {
918
+ return;
919
+ }
920
+ if (client.featureFlags?.['recommendedPivotsDisabled'] !== undefined) {
921
+ setPivotRecommendationsEnabledState(!client.featureFlags?.['recommendedPivotsDisabled']);
922
+ }
923
+ if (!initialTableName && !reportId && client.publicKey) {
924
+ clearAllState();
925
+ }
926
+ }, [client]);
927
+ // Initialize ReportBuilder with a report
928
+ useEffect(() => {
929
+ const loadChart = async () => {
930
+ let report;
931
+ if (!client) {
1416
932
  return;
1417
- if (active.id !== over.id) {
1418
- const oldIndex = orderedColumnNames.findIndex((c) => c.endsWith(`${currentTable}.${active.id}`));
1419
- const newIndex = orderedColumnNames.findIndex((c) => c.endsWith(`${currentTable}.${over.id}`));
1420
- const newOrder = arrayMove(orderedColumnNames, oldIndex, newIndex);
1421
- setOrderedColumnNames(newOrder);
1422
- const orderedSelectedColumns = [];
1423
- for (const value of newOrder) {
1424
- const [, ...rest] = value.split('.'); // table.field
1425
- const column = rest.join('.');
1426
- if (selectedColumns.includes(value)) {
1427
- orderedSelectedColumns.push(column);
1428
- }
1429
- }
1430
- setSelectedOrderedColumns(orderedSelectedColumns);
1431
- // If there is already an AST saved in state, only update the columns
1432
- // otherwise fill in the defaultAST shape and also update columns.
1433
- const fallbackAST = {
1434
- ...defaultAST,
1435
- from: [{ ...defaultTable }],
1436
- columns: orderedSelectedColumns.map((name) => nameToColumn(name)),
1437
- };
1438
- const newBaseAst = {
1439
- ...baseAst,
1440
- columns: baseAst?.columns.length
1441
- ? orderedSelectedColumns.map((name) => nameToColumn(name))
1442
- : baseAst?.columns,
1443
- };
1444
- const newAst = baseAst ? newBaseAst : fallbackAST;
1445
- setBaseAst(newAst);
1446
- fetchSqlQuery(newAst, undefined, false);
1447
933
  }
1448
- }
1449
- const columnNamesInAst = baseAst?.columns
1450
- .map((col) => {
1451
- if (col.expr.type === 'column_ref' && col.expr.column) {
1452
- if (typeof col.expr.column === 'string') {
1453
- return col.expr.column;
934
+ try {
935
+ if (!reportId) {
936
+ throw new Error('Report ID is required');
1454
937
  }
1455
- else {
1456
- return col.expr.column.expr.value;
938
+ report = dashboard[reportId];
939
+ if (!report) {
940
+ throw new Error('Report not found');
1457
941
  }
942
+ const { ast: newAst, pivot: newPivot } = await fetchASTFromQuillReport(report, client, filteredSchema);
943
+ const initialState = astToReportBuilderState(newAst, client.databaseType || 'postgresql', filteredSchema);
944
+ setTempReport(report);
945
+ handleMultiStateChange({
946
+ ...initialState,
947
+ pivot: newPivot ?? null,
948
+ }, true);
1458
949
  }
1459
- else if (col.as) {
1460
- if (typeof col.as === 'string') {
1461
- return col.as;
1462
- }
1463
- else {
1464
- return col.as.expr?.value;
1465
- }
950
+ catch (err) {
951
+ console.error(err);
952
+ setErrorMessage('Error when loading chart');
1466
953
  }
1467
- else if (col.expr && col.expr.type === 'aggr_func') {
1468
- if (col.expr.args) {
1469
- return `${col.expr.name.toLowerCase()}(${col.expr.args.expr.value})`;
1470
- }
1471
- return col.expr.name;
954
+ };
955
+ if (reportId && client) {
956
+ loadChart();
957
+ }
958
+ }, [dashboard[reportId || ''], client]);
959
+ useEffect(() => {
960
+ if (initialTableName) {
961
+ const tableColumns = filteredSchema.find((table) => {
962
+ return table.name === initialTableName;
963
+ })?.columns ?? [];
964
+ if (tableColumns.length > 0) {
965
+ handleMultiStateChange({
966
+ ...EMPTY_REPORT_BUILDER_STATE,
967
+ tables: [{ name: initialTableName }],
968
+ columns: tableColumns.map((col) => ({
969
+ field: col.field,
970
+ table: initialTableName,
971
+ })),
972
+ }, true);
1472
973
  }
1473
- return col.expr.value;
1474
- })
1475
- .filter(
1476
- // remove duplicate entries
1477
- (value, index, self) => value && self.indexOf(value) === index) ?? [];
1478
- return (_jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: columnNamesInAst, strategy: verticalListSortingStrategy, children: _jsxs("div", { style: {
1479
- display: 'flex',
1480
- flexDirection: 'column',
1481
- gap: 8,
1482
- }, children: [columnNamesInAst.map((name) => (_jsx(DraggableItem, { id: name, label: name, onDelete: () => !loading && handleDeleteColumn(name) }, name))), columnNamesInAst?.length > 0 && _jsx("div", { style: { height: 6 } })] }) }) }));
1483
- }
974
+ }
975
+ }, [filteredSchema, initialTableName]);
976
+ useEffect(() => {
977
+ if (isChartBuilderOpen === false) {
978
+ onCloseChartBuilder && onCloseChartBuilder();
979
+ }
980
+ }, [isChartBuilderOpen]);
1484
981
  return (_jsxs("div", { style: { backgroundColor: theme?.backgroundColor, ...containerStyle }, className: className, children: [(!isChartBuilderHorizontalView ||
1485
982
  (isChartBuilderHorizontalView && !isChartBuilderOpen)) && (_jsxs("div", { ref: parentRef, style: {
1486
983
  display: 'flex',
@@ -1489,72 +986,37 @@ export default function ReportBuilder({ initialTableName = '', onSubmitEditRepor
1489
986
  overflowY: 'auto',
1490
987
  boxSizing: 'border-box',
1491
988
  ...containerStyle,
1492
- }, className: className, children: [_jsxs(SidebarComponent, { children: [_jsxs("div", { style: { width: '100%' }, children: [_jsx(SidebarHeadingComponent, { label: "Columns" }), _jsx(DraggableColumns, {}), _jsx(SecondaryButtonComponent, { onClick: () => {
1493
- if (!orderedColumnNames) {
1494
- return;
1495
- }
989
+ }, className: className, children: [_jsxs(SidebarComponent, { children: [_jsxs("div", { style: { width: '100%' }, children: [_jsx(SidebarHeadingComponent, { label: "Columns" }), _jsx(DraggableColumns, { columns: columns, DraggableColumnComponent: DraggableColumnComponent, onColumnOrderChange: handleColumnsChange, loading: loading }), _jsx(SecondaryButtonComponent, { onClick: () => {
1496
990
  if (!openPopover) {
1497
991
  setOpenPopover('AddColumnModal');
1498
992
  }
1499
- }, label: "Select columns", disabled: tableLoading || loading }), _jsx(ModalComponent, { isOpen: openPopover === 'AddColumnModal', setIsOpen: (isOpen) => {
993
+ }, label: "Select columns", disabled: loading }), _jsx(ModalComponent, { isOpen: openPopover === 'AddColumnModal', setIsOpen: (isOpen) => {
1500
994
  if (!isOpen) {
1501
995
  // delay onClose callback so onClick no-ops
1502
996
  setTimeout(() => {
1503
- setActiveEditItem(null);
1504
997
  setOpenPopover(null);
1505
998
  }, 100);
1506
999
  }
1507
- }, title: "Select columns", children: _jsx(AddColumnModal, { onSave: () => {
1508
- setActiveEditItem(null);
1000
+ }, title: "Select columns", children: _jsx(NewAddColumnModal, { onSave: (tables, columns) => {
1001
+ handleMultiStateChange({
1002
+ ...EMPTY_REPORT_BUILDER_STATE,
1003
+ tables,
1004
+ columns,
1005
+ }, true);
1509
1006
  setOpenPopover(null);
1510
- }, orderedColumnNames: orderedColumnNames, setOrderedColumnNames: setOrderedColumnNames, selectedColumns: selectedColumns, setSelectedColumns: setSelectedColumns, isSelectedAllColumns: isSelectedAllColumns, clearAllState: clearAllState, nameToColumn: nameToColumn, baseAst: baseAst, setBaseAst: (ast) => {
1511
- if (baseAst &&
1512
- ast?.from?.[0]?.table !== baseAst?.from?.[0]?.table) {
1513
- // table changed, past presets no longer valid
1514
- setFormData(null);
1515
- setRecommendedPivots([]);
1516
- setCreatedPivots([]);
1517
- ast.where = null;
1518
- ast.orderby = null;
1519
- ast.limit = null;
1520
- ast.top = null;
1521
- setBaseAst(ast);
1522
- fetchSqlQuery(ast, null);
1523
- }
1524
- else {
1525
- setBaseAst(ast);
1526
- fetchSqlQuery(ast);
1527
- }
1528
- }, pivot: pivot, initialTableName: initialTableName, defaultAST: defaultAST, defaultTable: defaultTable, schemaLoading: schemaData.isSchemaLoading, setPivot: setPivot, TextInputComponent: TextInputComponent, SelectColumn: SelectColumnComponent, SecondaryButton: SecondaryButtonComponent, Button: ButtonComponent, ColumnSearchEmptyState: ColumnSearchEmptyState, LoadingComponent: LoadingComponent }) })] }), _jsxs("div", { style: { width: '100%' }, children: [_jsx(SidebarHeadingComponent, { label: "Filters" }), formData && (_jsx("div", { style: {
1007
+ },
1008
+ // selectedTables={tables} // Bring back after select table join UI
1009
+ selectedColumns: columns, schema: filteredSchema, schemaLoading: schemaData.isSchemaLoading, TextInputComponent: TextInputComponent, SelectColumn: SelectColumnComponent, SecondaryButton: SecondaryButtonComponent, Button: ButtonComponent, ColumnSearchEmptyState: ColumnSearchEmptyState, LoadingComponent: LoadingComponent }) })] }), _jsxs("div", { style: { width: '100%' }, children: [_jsx(SidebarHeadingComponent, { label: "Filters" }), filterStack.length > 0 && (_jsx("div", { style: {
1529
1010
  display: 'flex',
1530
1011
  flexDirection: 'column',
1531
1012
  gap: 8,
1532
1013
  marginBottom: 12,
1533
- }, children: _jsx(FilterStack, { client: client, filterStack: filterStack, filterTree: filterTree, defaultAST: defaultAST, baseAst: baseAst, setBaseAst: setBaseAst, setFormData: setFormData, fetchSqlQuery: fetchSqlQuery, schemaData: schemaData, defaultColumn: defaultColumn, defaultTable: defaultTable, currentTable: currentTable, initialTableName: initialTableName, globalUniqueValues: globalUniqueValues, globalUniqueValuesIsLoading: globalUniqueValuesIsLoading, columns: columns, removingFilter: removingFilter, setRemovingFilter: setRemovingFilter, TabsComponent: TabsComponent, FilterPopoverComponent: FilterPopoverComponent, FilterModal: FilterModal, ButtonComponent: ButtonComponent, SecondaryButtonComponent: SecondaryButtonComponent, SelectComponent: SelectComponent, TextInputComponent: TextInputComponent, MultiSelectComponent: MultiSelectComponent, actionsEnabled: !tableLoading && !loading, dashboardName: dashboardName }) })), _jsxs("div", { style: {
1014
+ }, children: _jsx(FilterStack, { client: client, filterStack: filterStack, handleFilterStackChange: handleFilterStackChange, schemaData: schemaData, uniqueValues: unfilteredUniqueValues, uniqueValuesIsLoading: unfilteredUniqueValuesIsLoading, tables: tables, TabsComponent: TabsComponent, FilterPopoverComponent: FilterPopoverComponent, FilterModal: FilterModal, ButtonComponent: ButtonComponent, SecondaryButtonComponent: SecondaryButtonComponent, SelectComponent: SelectComponent, TextInputComponent: TextInputComponent, MultiSelectComponent: MultiSelectComponent, actionsEnabled: !loading, dashboardName: destinationDashboard }) })), _jsxs("div", { style: {
1534
1015
  display: 'flex',
1535
1016
  flexDirection: 'column',
1536
1017
  alignItems: 'flex-start',
1537
- }, children: [_jsx(SecondaryButtonComponent, { disabled: !baseAst || !dataDisplayed || loading || tableLoading, onClick: () => {
1538
- if (!selectedColumns ||
1539
- selectedColumns.length === 0 ||
1540
- loading) {
1541
- return;
1542
- }
1018
+ }, children: [_jsx(SecondaryButtonComponent, { disabled: columns.length === 0 || loading, onClick: () => {
1543
1019
  if (!openPopover) {
1544
- const value = orderedColumnNames[0];
1545
- const [, ...rest] = value.split('.'); // table.field
1546
- const column = rest.join('.');
1547
- const columnType = getColumnTypeByName(column);
1548
- if (isNumericColumnType(columnType)) {
1549
- const newSubtree = deepCopy(defaultNumericComparison);
1550
- newSubtree.left.column = column;
1551
- setActiveEditItem(newSubtree);
1552
- }
1553
- else {
1554
- const newSubtree = deepCopy(defaultEntry);
1555
- newSubtree.left.args.value[0].column = column;
1556
- setActiveEditItem(newSubtree);
1557
- }
1558
1020
  setOpenPopover('AddFilterPopover');
1559
1021
  }
1560
1022
  }, label: 'Add filter' }), _jsx("div", { style: {
@@ -1562,134 +1024,56 @@ export default function ReportBuilder({ initialTableName = '', onSubmitEditRepor
1562
1024
  ...(openPopover === 'AddFilterPopover' && { top: 12 }),
1563
1025
  }, children: _jsx(PopoverComponent, { isOpen: openPopover === 'AddFilterPopover', setIsOpen: (isOpen) => {
1564
1026
  if (!isOpen) {
1565
- // delay onClose callback so onClick no-ops
1566
1027
  setOpenPopover(null);
1567
- setTimeout(() => {
1568
- clearCheckboxes();
1569
- setActiveEditItem(null);
1570
- }, 300);
1571
1028
  }
1572
- }, popoverTitle: "Add filter", popoverChildren: _jsx(FilterModal, { schema: schemaData.schemaWithCustomFields.find((s) => s.name === currentTable ||
1573
- s.displayName === currentTable) ?? schemaData.schemaWithCustomFields[0], fieldValuesMap: fieldValuesMap, fieldValuesMapIsLoading: uniqueValuesIsLoading, onSubmitFilter: (filter) => {
1029
+ }, popoverTitle: "Add filter", popoverChildren: _jsx(FilterModal, { schema: filteredSchema, tables: tables.map((table) => table.name), fieldValuesMap: filteredUniqueValues ?? unfilteredUniqueValues, fieldValuesMapIsLoading: filteredUniqueValuesIsLoading ||
1030
+ unfilteredUniqueValuesIsLoading, onSubmitFilter: (filter) => {
1574
1031
  setOpenPopover(null);
1575
- handleInsertion(filter);
1576
- }, onDeleteFilter: () => { }, ButtonComponent: ButtonComponent, SelectComponent: SelectComponent, TextInputComponent: TextInputComponent, MultiSelectComponent: MultiSelectComponent }) }) })] })] }), _jsxs("div", { style: { width: '100%' }, children: [_jsx(SidebarHeadingComponent, { label: "Pivot" }), _jsx(PivotModal, { pivotRowField: pivotRowField, setPivotRowField: setPivotRowField, pivotColumnField: pivotColumnField, setPivotColumnField: setPivotColumnField, pivotAggregations: pivotAggregations, setPivotAggregations: setPivotAggregations, createdPivots: createdPivots, setCreatedPivots: setCreatedPivots, recommendedPivots: recommendedPivots, setRecommendedPivots: setRecommendedPivots, popUpTitle: pivotPopUpTitle, setPopUpTitle: setPivotPopUpTitle, selectedTable: initialTableName, SubheaderComponent: SubHeaderComponent, DeleteButtonComponent: DeleteButtonComponent, SelectComponent: SelectComponent, ButtonComponent: ButtonComponent, CardComponent: CardComponent, SecondaryButtonComponent: SecondaryButtonComponent, PopoverComponent: PopoverComponent, TextComponent: TextComponent, ErrorMessageComponent: ErrorMessageComponent, PivotRowContainer: PivotRowContainer, PivotColumnContainer: PivotColumnContainer, LoadingComponent: LoadingComponent, isOpen: showPivotPopover, setIsOpen: setShowPivotPopover, showUpdatePivot: isEditingPivot, setShowUpdatePivot: setIsEditingPivot, parentRef: parentRef, data: rows, columns: columns, triggerButtonText: 'Add pivot', selectedPivotIndex: selectedPivotIndex, setSelectedPivotIndex: setSelectedPivotIndex, removePivot: () => {
1577
- setPivot(null);
1578
- setPivotHint('');
1579
- setPivotData(null);
1580
- resetLimit();
1581
- resetSort(true);
1582
- setPreviousPage(0);
1583
- const formattedRows = formatRows(rows, columns, false);
1584
- setFormattedRows(formattedRows);
1585
- }, selectPivot: async (selectedPivot, uniqueValues, dateRange, pivotTable) => {
1032
+ handleFilterInsertion(filter);
1033
+ }, hideTableName: tables.length === 1, onDeleteFilter: () => { }, ButtonComponent: ButtonComponent, SelectComponent: SelectComponent, TextInputComponent: TextInputComponent, MultiSelectComponent: MultiSelectComponent }) }) })] })] }), _jsxs("div", { style: { width: '100%' }, children: [_jsx(SidebarHeadingComponent, { label: "Pivot" }), _jsx(PivotModal, { pivotRowField: pivotRowField, setPivotRowField: setPivotRowField, pivotColumnField: pivotColumnField, setPivotColumnField: setPivotColumnField, pivotAggregations: pivotAggregations, setPivotAggregations: setPivotAggregations, createdPivots: createdPivots, setCreatedPivots: setCreatedPivots, recommendedPivots: recommendedPivots, setRecommendedPivots: setRecommendedPivots, popUpTitle: pivotPopUpTitle, setPopUpTitle: setPivotPopUpTitle, selectedTable: initialTableName, SubheaderComponent: SubHeaderComponent, DeleteButtonComponent: DeleteButtonComponent, SelectComponent: SelectComponent, ButtonComponent: ButtonComponent, CardComponent: CardComponent, SecondaryButtonComponent: SecondaryButtonComponent, PopoverComponent: PopoverComponent, TextComponent: TextComponent, ErrorMessageComponent: ErrorMessageComponent, PivotRowContainer: PivotRowContainer, PivotColumnContainer: PivotColumnContainer, LoadingComponent: LoadingComponent, isOpen: showPivotPopover, setIsOpen: setShowPivotPopover, showUpdatePivot: isEditingPivot, setShowUpdatePivot: setIsEditingPivot, parentRef: parentRef, data: reportRows, columns: reportColumnsToStateColumns, triggerButtonText: 'Add pivot', selectedPivotIndex: selectedPivotIndex, setSelectedPivotIndex: setSelectedPivotIndex, removePivot: () => {
1034
+ handlePivotChange(null, sortOrLimitWasReset);
1035
+ }, selectPivot: async (selectedPivot) => {
1586
1036
  if (!selectedPivot)
1587
1037
  return;
1588
- // const newAst = { ...baseAst };
1589
- // newAst.orderby = null;
1590
- if (selectedPivot.rowFieldType === 'date') {
1591
- selectedPivot['sort'] = true;
1592
- selectedPivot['sortDirection'] = 'ASC';
1593
- }
1594
- // setBaseAst(newAst); // trigger refetch
1595
- let dateBucket = undefined;
1596
- if (dateRange) {
1597
- dateBucket = getDateBucketFromRange(dateRange);
1598
- }
1599
- const processedPivot = setTypesOnPivot(selectedPivot, columns);
1600
- setPivot(processedPivot);
1601
- resetLimit();
1602
- resetSort();
1603
- setPreviousPage(0);
1604
- setPivotHint('');
1605
- try {
1606
- if (!pivotTable) {
1607
- setTableLoading(true);
1608
- pivotTable = await generatePivotTable({
1609
- pivot: processedPivot,
1610
- dateBucket,
1611
- report: tempReport,
1612
- client,
1613
- uniqueValues,
1614
- dashboardName,
1615
- tenants,
1616
- additionalProcessing: {
1617
- page: REPORT_BUILDER_PAGINATION,
1618
- },
1619
- });
1620
- }
1621
- resetProcessing();
1622
- setPivotData(pivotTable || []);
1623
- setTempReport({
1624
- ...tempReport,
1625
- ...pivotFormData(processedPivot),
1626
- });
1627
- const formattedRows = formatRows(pivotTable.rows, columns, true, selectedPivot.aggregationType, dateBucket);
1628
- setFormattedRows(formattedRows);
1629
- setErrorMessage('');
1630
- setPivotError(undefined);
1631
- }
1632
- catch (e) {
1633
- if (e instanceof Error)
1634
- setPivotError(e.message);
1635
- }
1636
- finally {
1637
- setTableLoading(false);
1638
- }
1639
- }, selectPivotOnEdit: true, showTrigger: !pivot, theme: theme, LabelComponent: LabelComponent, HeaderComponent: HeaderComponent, dateRange: undefined, pivotCountRequest: 4, query: activeQuery, initialUniqueValues: uniqueValues[currentTable], uniqueValuesIsLoading: uniqueValuesIsLoading, disabled: !baseAst || !dataDisplayed || tableLoading || loading, pivotRecommendationsEnabled: pivotRecommendationsEnabledState, report: tempReport, dashboardName: dashboardName }), pivot && (_jsx(PivotForm, { columns: columns, uniqueValues: uniqueValues[currentTable], uniqueValuesIsLoading: uniqueValuesIsLoading, setPivotRowField: (value) => {
1640
- const prev = pivotRowField;
1038
+ handlePivotChange(selectedPivot, true);
1039
+ }, selectPivotOnEdit: true, showTrigger: !pivot, theme: theme, LabelComponent: LabelComponent, HeaderComponent: HeaderComponent, dateRange: undefined, pivotCountRequest: 4, query: activeQuery, initialUniqueValues: columnUniqueValues, uniqueValuesIsLoading: filteredUniqueValuesIsLoading ||
1040
+ unfilteredUniqueValuesIsLoading, disabled: !columns.length || tableLoading || loading, pivotRecommendationsEnabled: pivotRecommendationsEnabledState, report: tempReport, dashboardName: destinationDashboard }), pivot && (_jsx(PivotForm, { columns: reportColumnsToStateColumns, uniqueValues: columnUniqueValues, uniqueValuesIsLoading: filteredUniqueValuesIsLoading ||
1041
+ unfilteredUniqueValuesIsLoading, setPivotRowField: (value) => {
1641
1042
  setPivotRowField(value);
1642
- updatePivot(value, 'rowField', prev);
1043
+ updatePivot(value, 'rowField');
1643
1044
  }, setPivotColumnField: (value) => {
1644
- const prev = pivotColumnField;
1645
1045
  setPivotColumnField(value);
1646
- updatePivot(value, 'columnField', prev);
1046
+ updatePivot(value, 'columnField');
1647
1047
  }, setPivotAggregations: (value) => {
1648
- const prev = pivotAggregations;
1649
1048
  setPivotAggregations(value);
1650
- updatePivot(value, 'aggregations', prev);
1049
+ updatePivot(value, 'aggregations');
1651
1050
  }, onDelete: () => {
1652
- setPivot(null);
1653
- setPivotError(undefined);
1654
- setPivotHint('');
1655
- setPivotData(null);
1656
- resetLimit();
1657
- resetSort();
1658
- setPreviousPage(0);
1659
- const formattedRows = formatRows(rows, columns, false);
1660
- setFormattedRows(formattedRows);
1051
+ handlePivotChange(null, sortOrLimitWasReset);
1661
1052
  }, isLoading: tableLoading || loading, pivotRowField: pivotRowField, pivotColumnField: pivotColumnField, pivotAggregations: pivotAggregations, SecondaryButtonComponent: SecondaryButtonComponent, SelectComponent: SelectComponent, PivotColumnContainer: PivotColumnContainer, DeleteButtonComponent: DeleteButtonComponent, pivotHint: pivotHint }))] }), _jsxs("div", { style: { width: '100%' }, children: [_jsx(SidebarHeadingComponent, { label: "Sort" }), pivot && pivot.sort && pivot.sortField && (_jsx("div", { style: {
1662
1053
  display: 'flex',
1663
1054
  flexDirection: 'column',
1664
1055
  gap: 8,
1665
1056
  marginBottom: 12,
1666
- }, children: _jsx(SortSentence, { sortField: pivot.sortField, sortDirection: pivot.sortDirection || 'ASC', columns: pivotData?.columns ?? [], setIsPending: () => { }, setEditPopoverKey: () => { }, setActiveEditItem: setActiveEditItem, setOpenPopover: setOpenPopover, SortPopover: SortPopoverComponent, EditPopover: AddSortPopover, handleDelete: async () => {
1057
+ }, children: _jsx(SortSentence, { sortField: pivot.sortField, sortDirection: pivot.sortDirection || 'ASC', columns: pivotData?.columns ?? [], setOpenPopover: setOpenPopover, SortPopover: SortPopoverComponent, EditPopover: AddSortPopover, handleDelete: async () => {
1667
1058
  onSortChange({
1668
1059
  field: pivot.sortField ?? '',
1669
1060
  direction: pivot.sortDirection ?? 'ASC',
1670
1061
  }, true);
1671
1062
  }, onSave: async (column, direction) => {
1672
1063
  onSortChange({ field: column, direction });
1673
- }, Select: SelectComponent, Button: ButtonComponent, SecondaryButton: SecondaryButtonComponent, disabled: tableLoading || loading }, `sort-sentence-pivot`) })), baseAst && baseAst.orderby && baseAst.orderby.length > 0 && (_jsx("div", { style: {
1064
+ }, Select: SelectComponent, Button: ButtonComponent, SecondaryButton: SecondaryButtonComponent, disabled: tableLoading || loading }, `sort-sentence-pivot`) })), sort && sort.length > 0 && (_jsx("div", { style: {
1674
1065
  display: 'flex',
1675
1066
  flexDirection: 'column',
1676
1067
  gap: 8,
1677
1068
  marginBottom: 12,
1678
- }, children: baseAst.orderby.map((sortData, id) => (_jsx(SortSentence, { sortField: sortData.expr?.column || sortData.expr?.value, sortDirection: sortData.type, columns: columns, setIsPending: () => { }, setEditPopoverKey: () => { }, setActiveEditItem: setActiveEditItem, setOpenPopover: setOpenPopover, SortPopover: SortPopoverComponent, EditPopover: AddSortPopover, handleDelete: () => {
1679
- onSortChange({
1680
- field: sortData.expr?.column || sortData.expr?.value,
1681
- direction: sortData.type,
1682
- }, true);
1069
+ }, children: sort.map((sortData, id) => (_jsx(SortSentence, { sortField: sortData.field, sortDirection: sortData.direction, columns: reportColumnsToStateColumns, setOpenPopover: setOpenPopover, SortPopover: SortPopoverComponent, EditPopover: AddSortPopover, handleDelete: () => {
1070
+ onSortChange(sortData, true);
1683
1071
  }, onSave: (column, direction) => {
1684
1072
  onSortChange({ field: column, direction });
1685
- }, Select: SelectComponent, Button: ButtonComponent, SecondaryButton: SecondaryButtonComponent, disabled: tableLoading || loading }, `sort-sentence-${id}`))) })), _jsx(SecondaryButtonComponent, { disabled: !baseAst ||
1686
- !dataDisplayed ||
1073
+ }, Select: SelectComponent, Button: ButtonComponent, SecondaryButton: SecondaryButtonComponent, disabled: tableLoading || loading }, `sort-sentence-${id}`))) })), _jsx(SecondaryButtonComponent, { disabled: columns.length === 0 ||
1687
1074
  loading ||
1688
1075
  tableLoading ||
1689
- mssqlSortWarning, onClick: () => {
1690
- if (!selectedColumns || selectedColumns.length === 0) {
1691
- return;
1692
- }
1076
+ !!mssqlSortWarning, onClick: () => {
1693
1077
  if (!openPopover) {
1694
1078
  setOpenPopover('AddSortPopover');
1695
1079
  }
@@ -1698,103 +1082,25 @@ export default function ReportBuilder({ initialTableName = '', onSubmitEditRepor
1698
1082
  ...(openPopover === 'AddSortPopover' && { top: 12 }),
1699
1083
  }, children: _jsx(PopoverComponent, { isOpen: openPopover === 'AddSortPopover', setIsOpen: (isOpen) => {
1700
1084
  if (!isOpen) {
1701
- setActiveEditItem(null);
1702
1085
  setOpenPopover(null);
1703
1086
  }
1704
- }, popoverTitle: "Sort by", popoverChildren: _jsx(AddSortPopover, { columns: pivotData ? pivotData.columns : columns, Select: SelectComponent, Button: ButtonComponent, SecondaryButton: SecondaryButtonComponent, onSave: async (column, direction) => {
1705
- if (column === '')
1706
- return;
1707
- if (pivot) {
1708
- const sortFieldType = pivot.aggregations?.some((agg) => column === agg.valueField || column === 'count')
1709
- ? 'number'
1710
- : pivot.rowFieldType;
1711
- const tempPivot = setTypesOnPivot({
1712
- ...pivot,
1713
- sort: true,
1714
- sortDirection: direction,
1715
- sortField: column,
1716
- sortFieldType: sortFieldType,
1717
- }, columns);
1718
- setPivot(tempPivot);
1719
- setPivotHint('');
1720
- let dateBucket = undefined;
1721
- const tempDateRange = dateRanges &&
1722
- pivot.rowField &&
1723
- dateRanges[pivot.rowField];
1724
- if (tempDateRange) {
1725
- dateBucket = getDateBucketFromRange(tempDateRange.dateRange);
1726
- }
1727
- try {
1728
- const pivotedData = await generatePivotTable({
1729
- pivot: tempPivot,
1730
- dateBucket,
1731
- report: tempReport,
1732
- client,
1733
- uniqueValues: uniqueValues[currentTable],
1734
- dashboardName,
1735
- tenants,
1736
- additionalProcessing: {
1737
- page: REPORT_BUILDER_PAGINATION,
1738
- },
1739
- });
1740
- resetProcessing();
1741
- setErrorMessage('');
1742
- setPivotData(pivotedData || []);
1743
- const formattedRows = formatRows(pivotedData.rows, columns, true, pivot.aggregationType, dateBucket);
1744
- setFormattedRows(formattedRows);
1745
- }
1746
- catch (e) {
1747
- if (e instanceof Error)
1748
- setPivotError(e.message);
1749
- }
1750
- setOpenPopover(null);
1751
- setBaseAst(deepCopy(baseAst));
1752
- return;
1753
- }
1754
- else {
1755
- const newAst = { ...baseAst };
1756
- if (!newAst.orderby)
1757
- newAst.orderby = [];
1758
- const existingSortIndex = newAst.orderby.findIndex((item) => getFieldFromExpression(item.expr) === column);
1759
- if (existingSortIndex !== -1) {
1760
- newAst.orderby[existingSortIndex] = {
1761
- expr: { type: 'column_ref', column },
1762
- type: direction,
1763
- };
1764
- }
1765
- else {
1766
- newAst.orderby.push({
1767
- expr: { type: 'column_ref', column },
1768
- type: direction,
1769
- });
1770
- }
1771
- // look through the columns
1772
- setOpenPopover(null);
1773
- setBaseAst(deepCopy(newAst));
1774
- fetchSqlQuery(deepCopy(newAst));
1775
- }
1776
- } }) }) })] }), _jsxs("div", { style: { width: '100%' }, children: [_jsx(SidebarHeadingComponent, { label: "Limit" }), (baseAst &&
1777
- ((baseAst.limit && baseAst.limit.value?.length > 0) ||
1778
- (baseAst.top && baseAst.top.value))) ||
1779
- pivot?.rowLimit ? (_jsx("div", { style: {
1087
+ }, popoverTitle: "Sort by", popoverChildren: _jsx(AddSortPopover, { columns: pivotData
1088
+ ? pivotData.columns
1089
+ : reportColumnsToStateColumns, Select: SelectComponent, Button: ButtonComponent, SecondaryButton: SecondaryButtonComponent, onSave: async (column, direction) => {
1090
+ onSortChange({ field: column, direction });
1091
+ setOpenPopover(null);
1092
+ } }) }) })] }), _jsxs("div", { style: { width: '100%' }, children: [_jsx(SidebarHeadingComponent, { label: "Limit" }), limit || pivot?.rowLimit ? (_jsx("div", { style: {
1780
1093
  display: 'flex',
1781
1094
  flexDirection: 'column',
1782
1095
  gap: 8,
1783
1096
  marginBottom: 12,
1784
- }, children: _jsx(LimitSentence, { limit: baseAst.limit?.value?.[0]?.value ||
1785
- baseAst.top?.value ||
1786
- pivot?.rowLimit ||
1787
- 0, setOpenPopover: setOpenPopover, LimitPopover: LimitPopoverComponent, EditPopover: AddLimitPopover, handleDelete: () => {
1097
+ }, children: _jsx(LimitSentence, { limit: limit?.value || pivot?.rowLimit || 0, setOpenPopover: setOpenPopover, LimitPopover: LimitPopoverComponent, EditPopover: AddLimitPopover, handleDelete: () => {
1788
1098
  onLimitChange(null);
1099
+ setOpenPopover(null);
1789
1100
  }, onSave: (limit) => {
1790
1101
  onLimitChange(limit);
1791
- }, TextInput: TextInputComponent, Button: ButtonComponent, SecondaryButton: SecondaryButtonComponent, disabled: tableLoading || loading }) })) : (_jsxs(_Fragment, { children: [_jsx(SecondaryButtonComponent, { disabled: !baseAst || !dataDisplayed || loading || tableLoading, onClick: () => {
1792
- if (!selectedColumns || selectedColumns.length === 0) {
1793
- return;
1794
- }
1795
- if (!baseAst) {
1796
- return;
1797
- }
1102
+ setOpenPopover(null);
1103
+ }, TextInput: TextInputComponent, Button: ButtonComponent, SecondaryButton: SecondaryButtonComponent, disabled: tableLoading || loading }) })) : (_jsxs(_Fragment, { children: [_jsx(SecondaryButtonComponent, { disabled: columns.length === 0 || loading || tableLoading, onClick: () => {
1798
1104
  if (!openPopover) {
1799
1105
  setOpenPopover('AddLimitPopover');
1800
1106
  }
@@ -1803,37 +1109,36 @@ export default function ReportBuilder({ initialTableName = '', onSubmitEditRepor
1803
1109
  ...(openPopover === 'AddLimitPopover' && { top: 12 }),
1804
1110
  }, children: _jsx(PopoverComponent, { isOpen: openPopover === 'AddLimitPopover', setIsOpen: (isOpen) => {
1805
1111
  if (!isOpen) {
1806
- setActiveEditItem(null);
1807
1112
  setOpenPopover(null);
1808
1113
  }
1809
1114
  }, popoverTitle: "Add limit", popoverChildren: _jsx(AddLimitPopover, { TextInputComponent: TextInputComponent, Button: ButtonComponent, SecondaryButton: SecondaryButtonComponent, onSave: (limit) => {
1810
1115
  onLimitChange(limit);
1116
+ setOpenPopover(null);
1811
1117
  } }) }) })] }))] }), _jsx("div", { style: { width: '100%', minHeight: '30vh' } })] }), _jsxs(ContainerComponent, { children: [isAIEnabled && (_jsx("form", { ref: askAIContainerRef, onSubmit: (event) => {
1812
1118
  event.preventDefault();
1813
1119
  }, style: {
1814
1120
  display: 'flex',
1815
1121
  flexDirection: 'row',
1816
1122
  gap: 12,
1817
- visibility: askAIInputWidth === -1 && askAILoadingContainerWidth === -1
1818
- ? 'hidden'
1819
- : 'visible',
1820
- }, children: _jsxs(_Fragment, { children: [_jsx(TextInputComponent, { id: "ask_ai_input_bar", value: aiPrompt, width: askAIInputWidth !== -1
1821
- ? askAIInputWidth
1822
- : askAILoadingContainerWidth, onChange: (e) => setAiPrompt(e.target.value), placeholder: askedAQuestion
1823
- ? 'Ask a follow-up question...'
1824
- : 'Ask a question...' }), _jsx(ButtonComponent, { onClick: () => {
1825
- fetchAstFromPromptHelper();
1826
- }, isLoading: askAILoading && !baseAst, label: 'Ask AI' }), ((baseAst && dataDisplayed) || initialLoad) && !reportId && (_jsx(SecondaryButtonComponent, { label: 'New report', onClick: clearAllState }))] }) })), baseAst && (_jsx(TableComponent, { isLoading: tableLoading || (loading && errorMessage.length === 0), rows: formattedRows, rowCount: pivot ? pivotData?.rowCount : numberOfRows, rowCountIsLoading: rowCountIsLoading, rowsPerPage: 20, columns: pivot
1827
- ? pivotData?.columns || emptyPivotColumns()
1828
- : enforceOrderOnColumns(Object.keys(rows[0] ?? {})).map((c) => {
1123
+ visibility: askAIInputWidth === -1 ? 'hidden' : 'visible',
1124
+ }, children: _jsxs(_Fragment, { children: [_jsx(TextInputComponent, { id: "ask_ai_input_bar", value: aiPrompt, width: askAIInputWidth, onChange: (e) => setAiPrompt(e.target.value), placeholder: 'Ask a question...' }), _jsx(QuillToolTip, { enabled: tables.length > 1, text: "Ask AI does not support joined tables right now", textStyle: {
1125
+ maxWidth: '250px',
1126
+ whiteSpace: 'normal',
1127
+ }, displayBelow: true, children: _jsx(ButtonComponent, { disabled: tables.length > 1, onClick: () => {
1128
+ if (tables.length <= 1) {
1129
+ fetchAstFromPromptHelper();
1130
+ }
1131
+ }, isLoading: askAILoading && columns.length === 0, label: 'Ask AI' }) }), !reportId && (_jsx(SecondaryButtonComponent, { label: 'New report', onClick: clearAllState, disabled: columns.length === 0 || loading }))] }) })), columns.length > 0 && (_jsx(TableComponent, { isLoading: tableLoading || (loading && errorMessage.length === 0), rows: formattedRows, rowCount: pivot ? pivotData?.rowCount : numberOfRows, rowCountIsLoading: rowCountIsLoading, rowsPerPage: 20, columns: pivot
1132
+ ? pivotData?.columns || []
1133
+ : reportColumnsToStateColumns.map((col) => {
1829
1134
  return {
1830
- label: snakeAndCamelCaseToTitleCase(c),
1831
- field: c,
1135
+ field: col.field,
1136
+ label: snakeAndCamelCaseToTitleCase(col.field),
1832
1137
  };
1833
1138
  }), onPageChange: onPageChange, onSortChange: (sort) => {
1834
1139
  onSortChange(sort);
1835
1140
  }, disableSort: !!mssqlSortWarning, containerStyle: {
1836
- maxHeight: Math.max(window.innerHeight - 290, 75 + Math.min(Math.max(10, rows.length), 20) * 37),
1141
+ maxHeight: Math.max(window.innerHeight - 290, 75 + Math.min(Math.max(10, reportRows.length), 20) * 37),
1837
1142
  } })), _jsxs("div", { style: {
1838
1143
  display: 'flex',
1839
1144
  flexDirection: 'row',
@@ -1848,130 +1153,122 @@ export default function ReportBuilder({ initialTableName = '', onSubmitEditRepor
1848
1153
  width: '100%',
1849
1154
  gap: 12,
1850
1155
  alignItems: 'center',
1851
- }, children: [_jsx(ErrorMessageComponent, { errorMessage: errorMessage || pivotError || '' }), _jsx(SecondaryButtonComponent, { onClick: () => {
1156
+ }, children: [_jsx(ErrorMessageComponent, { errorMessage: errorMessage || pivotError }), _jsx(SecondaryButtonComponent, { onClick: () => {
1852
1157
  fetchAstFromPromptHelper();
1853
- }, label: 'Retry' }), _jsx(SecondaryButtonComponent, { onClick: clearAllState, label: 'Reset' })] })) : (_jsx("div", { style: { width: '100%' } })), baseAst && dataDisplayed && (_jsxs(_Fragment, { children: [onDiscardChanges && (_jsx(SecondaryButtonComponent, { onClick: onDiscardChanges, label: "Discard changes" })), !hideCopySQL && (_jsx(SecondaryButtonComponent, { label: isCopying ? 'Copied' : 'Copy SQL', onClick: () => copySQLToClipboard() })), !isAdminEnabled ? null : (_jsx(SecondaryButtonComponent, { onClick: async () => {
1854
- const tempReportColumns = selectedOrderedColumns.length > 0
1855
- ? selectedOrderedColumns
1856
- .map((columnName) => {
1857
- return columns.find((col) => col.field === columnName);
1858
- })
1859
- .filter((col) => col !== undefined)
1860
- .map((c) => reportInfo?.columns.find((col) => col.field === c.field))
1861
- .filter((col) => col !== undefined)
1862
- : (reportInfo?.columns.filter((col) => {
1863
- return columns.find((c) => {
1864
- return col.field === c.field;
1865
- });
1866
- }) ?? []);
1158
+ }, label: 'Retry' }), _jsx(SecondaryButtonComponent, { onClick: clearAllState, label: 'Reset' })] })) : (_jsx("div", { style: { width: '100%' } })), columns.length > 0 && activeQuery && (_jsxs(_Fragment, { children: [undoButtonEnabled && stateStack.length > 1 && (_jsx(SecondaryButtonComponent, { onClick: handleUndo, label: "Undo", disabled: stateStack.length <= 1 || loading })), onDiscardChanges && (_jsx(SecondaryButtonComponent, { onClick: onDiscardChanges, label: "Discard changes" })), !hideCopySQL && (_jsx(SecondaryButtonComponent, { label: isCopying ? 'Copied' : 'Copy SQL', onClick: () => copySQLToClipboard() })), !isAdminEnabled ? null : (_jsx(SecondaryButtonComponent, { onClick: async () => {
1159
+ const tempReportColumns = columns
1160
+ .map((column) => {
1161
+ return reportColumnsToStateColumns.find((col) => col.field === (column.alias || column.field));
1162
+ })
1163
+ .filter((col) => col !== undefined);
1164
+ let customFieldColumns = [];
1165
+ if (client && isSelectStar && schemaData.customFields) {
1166
+ customFieldColumns = tables.flatMap((table) => {
1167
+ return (schemaData.customFields?.[table.name || ''] || []).map((field) => field.field);
1168
+ });
1169
+ }
1867
1170
  setTempReport({
1868
- ...(reportInfo
1869
- ? {
1870
- ...reportInfo,
1871
- ...tempReport,
1872
- id: TEMP_REPORT_ID,
1873
- dashboardName: 'quill-saved-queries',
1874
- chartType: 'table',
1875
- pivot: pivot,
1876
- yAxisFields: reportInfo?.pivot && !pivot
1877
- ? []
1878
- : reportInfo?.yAxisFields,
1879
- columns: isSelectedAllColumns()
1880
- ? // if SELECT *, filter out custom fields from tabular view
1881
- // so Automatic Custom Fields can be applied
1882
- tempReportColumns.filter((col) => {
1883
- return !schemaData.customFields?.[currentTable]?.some((field) => {
1884
- return field.field === col.field;
1885
- });
1886
- })
1887
- : tempReportColumns,
1888
- queryString: isSelectedAllColumns()
1889
- ? convertQueryToSelectStar(activeQuery)
1890
- : activeQuery,
1891
- includeCustomFields: isSelectedAllColumns(),
1892
- rows: rows,
1893
- pivotRows: pivotData?.rows,
1894
- pivotColumns: pivotData?.columns,
1895
- pivotRowCount: pivotData?.rowCount,
1896
- pivotQuery: pivotData?.pivotQuery,
1897
- comparisonPivotQuery: pivotData?.comparisonPivotQuery,
1898
- flags: reportInfo?.flags ?? tempReport?.flags,
1899
- }
1900
- : {
1901
- ...tempReport,
1902
- chartType: 'table',
1903
- id: TEMP_REPORT_ID,
1904
- flags: tempReport?.flags,
1905
- }),
1171
+ ...tempReport,
1172
+ ...(pivot
1173
+ ? pivotFormData(pivot, reportColumnsToStateColumns, tempReport, tempReport.chartType, pivotData ?? undefined)
1174
+ : {}),
1175
+ id: TEMP_REPORT_ID,
1176
+ dashboardName: destinationDashboard,
1177
+ pivot: pivot,
1178
+ yAxisFields: tempReport?.pivot && !pivot
1179
+ ? []
1180
+ : tempReport?.yAxisFields,
1181
+ columns: isSelectStar
1182
+ ? // if SELECT *, filter out custom fields from tabular view
1183
+ // so Automatic Custom Fields can be applied
1184
+ tempReportColumns.filter((col) => {
1185
+ return !customFieldColumns.includes(col.field);
1186
+ })
1187
+ : tempReportColumns,
1188
+ columnInternal: isSelectStar
1189
+ ? // if SELECT *, filter out custom fields from tabular view
1190
+ // so Automatic Custom Fields can be applied
1191
+ tempReportColumns.filter((col) => {
1192
+ return !customFieldColumns.includes(col.field);
1193
+ })
1194
+ : tempReportColumns,
1195
+ queryString: isSelectStar
1196
+ ? convertQueryToSelectStar(activeQuery)
1197
+ : activeQuery,
1198
+ includeCustomFields: isSelectStar,
1199
+ rows: reportRows,
1200
+ pivotRows: pivotData?.rows,
1201
+ pivotColumns: pivotData?.columns,
1202
+ pivotRowCount: pivotData?.rowCount,
1203
+ pivotQuery: pivotData?.pivotQuery,
1204
+ comparisonPivotQuery: pivotData?.comparisonPivotQuery,
1205
+ flags: tempReport?.flags,
1906
1206
  });
1907
1207
  setIsSaveQueryModalOpen(true);
1908
1208
  }, disabled: !!errorMessage ||
1909
1209
  !!pivotError ||
1910
1210
  tableLoading ||
1911
1211
  loading ||
1912
- unresolvedReportMessage, label: 'Save query', tooltipText: unresolvedReportMessage })), _jsx(ButtonComponent, { onClick: async () => {
1212
+ !!unresolvedReportMessage, label: 'Save query', tooltipText: unresolvedReportMessage })), _jsx(ButtonComponent, { onClick: async () => {
1913
1213
  onSaveChanges && onSaveChanges();
1914
- const tempReportColumns = selectedOrderedColumns.length > 0
1915
- ? selectedOrderedColumns
1916
- .map((columnName) => {
1917
- return columns.find((col) => col.field === columnName);
1918
- })
1919
- .filter((col) => col !== undefined)
1920
- .map((c) => reportInfo?.columns.find((col) => col.field === c.field))
1921
- .filter((col) => col !== undefined)
1922
- : (reportInfo?.columns.filter((col) => {
1923
- return columns.find((c) => {
1924
- return col.field === c.field;
1925
- });
1926
- }) ?? []);
1214
+ const tempReportColumns = columns
1215
+ .map((column) => {
1216
+ return reportColumnsToStateColumns.find((col) => col.field === (column.alias || column.field));
1217
+ })
1218
+ .filter((col) => col !== undefined);
1219
+ let customFieldColumns = [];
1220
+ if (client && isSelectStar && schemaData.customFields) {
1221
+ customFieldColumns = tables.flatMap((table) => {
1222
+ return (schemaData.customFields?.[table.name || ''] || []).map((field) => field.field);
1223
+ });
1224
+ }
1927
1225
  setTempReport({
1928
- ...(reportInfo
1929
- ? {
1930
- ...reportInfo,
1931
- ...tempReport,
1932
- id: TEMP_REPORT_ID,
1933
- dashboardName: destinationDashboard,
1934
- pivot: pivot,
1935
- yAxisFields: reportInfo?.pivot && !pivot
1936
- ? []
1937
- : reportInfo?.yAxisFields,
1938
- columns: isSelectedAllColumns()
1939
- ? // if SELECT *, filter out custom fields from tabular view
1940
- // so Automatic Custom Fields can be applied
1941
- tempReportColumns.filter((col) => {
1942
- return !schemaData.customFields?.[currentTable]?.some((field) => {
1943
- return field.field === col.field;
1944
- });
1945
- })
1946
- : tempReportColumns,
1947
- queryString: isSelectedAllColumns()
1948
- ? convertQueryToSelectStar(activeQuery)
1949
- : activeQuery,
1950
- includeCustomFields: isSelectedAllColumns(),
1951
- rows: rows,
1952
- pivotRows: pivotData?.rows,
1953
- pivotColumns: pivotData?.columns,
1954
- pivotRowCount: pivotData?.rowCount,
1955
- pivotQuery: pivotData?.pivotQuery,
1956
- comparisonPivotQuery: pivotData?.comparisonPivotQuery,
1957
- flags: reportInfo?.flags ?? tempReport?.flags,
1958
- }
1959
- : {
1960
- ...tempReport,
1961
- id: TEMP_REPORT_ID,
1962
- flags: tempReport?.flags,
1963
- }),
1226
+ ...tempReport,
1227
+ ...(pivot
1228
+ ? pivotFormData(pivot, reportColumnsToStateColumns, tempReport, tempReport.chartType, pivotData ?? undefined)
1229
+ : {}),
1230
+ id: TEMP_REPORT_ID,
1231
+ dashboardName: destinationDashboard,
1232
+ pivot: pivot,
1233
+ yAxisFields: tempReport?.pivot && !pivot
1234
+ ? []
1235
+ : tempReport?.yAxisFields,
1236
+ columns: isSelectStar
1237
+ ? // if SELECT *, filter out custom fields from tabular view
1238
+ // so Automatic Custom Fields can be applied
1239
+ tempReportColumns.filter((col) => {
1240
+ return !customFieldColumns.includes(col.field);
1241
+ })
1242
+ : tempReportColumns,
1243
+ columnInternal: isSelectStar
1244
+ ? // if SELECT *, filter out custom fields from tabular view
1245
+ // so Automatic Custom Fields can be applied
1246
+ tempReportColumns.filter((col) => {
1247
+ return !customFieldColumns.includes(col.field);
1248
+ })
1249
+ : tempReportColumns,
1250
+ queryString: isSelectStar
1251
+ ? convertQueryToSelectStar(activeQuery)
1252
+ : activeQuery,
1253
+ includeCustomFields: isSelectStar,
1254
+ rows: reportRows,
1255
+ pivotRows: pivotData?.rows,
1256
+ pivotColumns: pivotData?.columns,
1257
+ pivotRowCount: pivotData?.rowCount,
1258
+ pivotQuery: pivotData?.pivotQuery,
1259
+ comparisonPivotQuery: pivotData?.comparisonPivotQuery,
1260
+ flags: tempReport?.flags,
1964
1261
  });
1965
1262
  setIsChartBuilderOpen(true);
1966
1263
  }, disabled: !!errorMessage ||
1967
1264
  !!pivotError ||
1968
1265
  tableLoading ||
1969
1266
  loading ||
1970
- unresolvedReportMessage, label: reportId ? 'Save changes' : 'Add to dashboard', tooltipText: unresolvedReportMessage })] }))] })] }), _jsx("style", { children: `body{margin:0;}` })] })), (!isChartBuilderHorizontalView || isChartBuilderOpen) && (_jsx(ChartBuilderWithModal, { tempReport: tempReport, reportId: reportId, isAdmin: isAdminEnabled, title: chartBuilderTitle
1267
+ !!unresolvedReportMessage, label: reportId ? 'Save changes' : 'Add to dashboard', tooltipText: unresolvedReportMessage })] }))] })] }), _jsx("style", { children: `body{margin:0;}` })] })), (!isChartBuilderHorizontalView || isChartBuilderOpen) && (_jsx(ChartBuilderWithModal, { tempReport: tempReport, reportId: reportId, isAdmin: isAdminEnabled, title: chartBuilderTitle
1971
1268
  ? chartBuilderTitle
1972
1269
  : reportId
1973
1270
  ? 'Save changes'
1974
- : 'Add to dashboard', isHorizontalView: true, isOpen: isChartBuilderOpen, setIsOpen: setIsChartBuilderOpen, onAddToDashboardComplete: reportId ? onSubmitEditReport : onSubmitCreateReport, organizationName: organizationName, initialUniqueValues: uniqueValues[currentTable], initialUniqueValuesIsLoading: uniqueValuesIsLoading, pivotRecommendationsEnabled: pivotRecommendationsEnabledState, SelectComponent: SelectComponent, TextInputComponent: TextInputComponent, ButtonComponent: ButtonComponent, SecondaryButtonComponent: SecondaryButtonComponent, HeaderComponent: HeaderComponent, SubHeaderComponent: SubHeaderComponent, LabelComponent: LabelComponent, TextComponent: TextComponent, CardComponent: CardComponent, ModalComponent: ChartBuilderModalComponent, PopoverComponent: PopoverComponent, TableComponent: TableComponent, DeleteButtonComponent: DeleteButtonComponent, LoadingComponent: LoadingComponent, ChartBuilderInputRowContainer: ChartBuilderInputRowContainer, ChartBuilderInputColumnContainer: ChartBuilderInputColumnContainer, CheckboxComponent: CheckboxComponent, FormContainer: ChartBuilderFormContainer, hideDateRangeFilter: true, hideDeleteButton: true, buttonLabel: !!reportId ? 'Save changes' : 'Add to dashboard', onClickChartElement: onClickChartElement, filtersEnabled: filtersEnabled, onFiltersEnabledChanged: (enabled) => {
1271
+ : 'Add to dashboard', isHorizontalView: true, isOpen: isChartBuilderOpen, setIsOpen: setIsChartBuilderOpen, onAddToDashboardComplete: reportId ? onSubmitEditReport : onSubmitCreateReport, destinationDashboard: destinationDashboard, organizationName: organizationName, initialUniqueValues: columnUniqueValues, initialUniqueValuesIsLoading: filteredUniqueValuesIsLoading || unfilteredUniqueValuesIsLoading, pivotRecommendationsEnabled: pivotRecommendationsEnabledState, SelectComponent: SelectComponent, TextInputComponent: TextInputComponent, ButtonComponent: ButtonComponent, SecondaryButtonComponent: SecondaryButtonComponent, HeaderComponent: HeaderComponent, SubHeaderComponent: SubHeaderComponent, LabelComponent: LabelComponent, TextComponent: TextComponent, CardComponent: CardComponent, ModalComponent: ChartBuilderModalComponent, PopoverComponent: PopoverComponent, TableComponent: TableComponent, DeleteButtonComponent: DeleteButtonComponent, LoadingComponent: LoadingComponent, ChartBuilderInputRowContainer: ChartBuilderInputRowContainer, ChartBuilderInputColumnContainer: ChartBuilderInputColumnContainer, CheckboxComponent: CheckboxComponent, FormContainer: ChartBuilderFormContainer, hideDateRangeFilter: true, hideDeleteButton: true, buttonLabel: !!reportId ? 'Save changes' : 'Add to dashboard', onClickChartElement: onClickChartElement, filtersEnabled: filtersEnabled, onFiltersEnabledChanged: (enabled) => {
1975
1272
  setFiltersEnabled(enabled);
1976
1273
  }, isEditingMode: true })), isSaveQueryModalOpen && (_jsx(ChartBuilderWithModal, { isHorizontalView: false, hideTableView: true, hideChartView: true, hidePivotForm: true, hideChartType: true, isOpen: isSaveQueryModalOpen, setIsOpen: setIsSaveQueryModalOpen, onAddToDashboardComplete: onSubmitSaveQuery, destinationDashboard: 'quill-saved-queries', isAdmin: false, title: 'Save query', buttonLabel: 'Save query', tempReport: tempReport, reportId: reportId, organizationName: organizationName, CardComponent: CardComponent, TableComponent: TableComponent, ModalComponent: ModalComponent, ButtonComponent: ButtonComponent, TextInputComponent: TextInputComponent, SelectComponent: SelectComponent, SecondaryButtonComponent: SecondaryButtonComponent, HeaderComponent: HeaderComponent, SubHeaderComponent: SubHeaderComponent, LabelComponent: LabelComponent, TextComponent: TextComponent, PopoverComponent: PopoverComponent, DeleteButtonComponent: DeleteButtonComponent, LoadingComponent: LoadingComponent, ChartBuilderInputRowContainer: ChartBuilderInputRowContainer, ChartBuilderInputColumnContainer: ChartBuilderInputColumnContainer, ErrorMessageComponent: ErrorMessageComponent, PivotRowContainer: PivotRowContainer, PivotColumnContainer: PivotColumnContainer, FormContainer: ChartBuilderFormContainer, CheckboxComponent: CheckboxComponent, hideDateRangeFilter: true, hideDeleteButton: true, hideDiscardChanges: true, hideSQLQuery: false, onClickChartElement: onClickChartElement,
1977
1274
  // hide filters table, make it a table chart etc