@quillsql/react 2.13.38 → 2.13.39

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 (89) hide show
  1. package/dist/cjs/Chart.d.ts.map +1 -1
  2. package/dist/cjs/Chart.js +0 -1
  3. package/dist/cjs/ChartBuilder.d.ts.map +1 -1
  4. package/dist/cjs/ChartBuilder.js +68 -57
  5. package/dist/cjs/ReportBuilder.d.ts.map +1 -1
  6. package/dist/cjs/ReportBuilder.js +69 -51
  7. package/dist/cjs/components/Chart/ChartTooltip.d.ts +1 -1
  8. package/dist/cjs/components/Chart/ChartTooltip.d.ts.map +1 -1
  9. package/dist/cjs/components/Chart/ChartTooltip.js +3 -3
  10. package/dist/cjs/components/Chart/LineChart.d.ts.map +1 -1
  11. package/dist/cjs/components/Chart/LineChart.js +4 -4
  12. package/dist/cjs/components/ReportBuilder/convert.d.ts +1 -1
  13. package/dist/cjs/components/ReportBuilder/convert.d.ts.map +1 -1
  14. package/dist/cjs/components/ReportBuilder/convert.js +73 -21
  15. package/dist/cjs/components/UiComponents.d.ts +2 -1
  16. package/dist/cjs/components/UiComponents.d.ts.map +1 -1
  17. package/dist/cjs/components/UiComponents.js +11 -4
  18. package/dist/cjs/hooks/useExport.d.ts.map +1 -1
  19. package/dist/cjs/hooks/useExport.js +4 -5
  20. package/dist/cjs/internals/ReportBuilder/PivotForm.d.ts +19 -11
  21. package/dist/cjs/internals/ReportBuilder/PivotForm.d.ts.map +1 -1
  22. package/dist/cjs/internals/ReportBuilder/PivotForm.js +62 -48
  23. package/dist/cjs/internals/ReportBuilder/PivotList.js +5 -4
  24. package/dist/cjs/internals/ReportBuilder/PivotModal.d.ts +28 -31
  25. package/dist/cjs/internals/ReportBuilder/PivotModal.d.ts.map +1 -1
  26. package/dist/cjs/internals/ReportBuilder/PivotModal.js +315 -633
  27. package/dist/cjs/models/Pivot.d.ts +27 -7
  28. package/dist/cjs/models/Pivot.d.ts.map +1 -1
  29. package/dist/cjs/utils/dashboard.d.ts.map +1 -1
  30. package/dist/cjs/utils/dashboard.js +36 -11
  31. package/dist/cjs/utils/merge.d.ts.map +1 -1
  32. package/dist/cjs/utils/merge.js +2 -1
  33. package/dist/cjs/utils/pivotConstructor.d.ts +1 -0
  34. package/dist/cjs/utils/pivotConstructor.d.ts.map +1 -1
  35. package/dist/cjs/utils/pivotConstructor.js +37 -7
  36. package/dist/cjs/utils/pivotProcessing.d.ts.map +1 -1
  37. package/dist/cjs/utils/pivotProcessing.js +10 -14
  38. package/dist/cjs/utils/queryConstructor.d.ts.map +1 -1
  39. package/dist/cjs/utils/queryConstructor.js +421 -134
  40. package/dist/cjs/utils/report.d.ts.map +1 -1
  41. package/dist/cjs/utils/report.js +2 -2
  42. package/dist/cjs/utils/textProcessing.d.ts +1 -1
  43. package/dist/cjs/utils/textProcessing.d.ts.map +1 -1
  44. package/dist/cjs/utils/textProcessing.js +3 -0
  45. package/dist/esm/Chart.d.ts.map +1 -1
  46. package/dist/esm/Chart.js +0 -1
  47. package/dist/esm/ChartBuilder.d.ts.map +1 -1
  48. package/dist/esm/ChartBuilder.js +68 -57
  49. package/dist/esm/ReportBuilder.d.ts.map +1 -1
  50. package/dist/esm/ReportBuilder.js +69 -51
  51. package/dist/esm/components/Chart/ChartTooltip.d.ts +1 -1
  52. package/dist/esm/components/Chart/ChartTooltip.d.ts.map +1 -1
  53. package/dist/esm/components/Chart/ChartTooltip.js +3 -3
  54. package/dist/esm/components/Chart/LineChart.d.ts.map +1 -1
  55. package/dist/esm/components/Chart/LineChart.js +4 -4
  56. package/dist/esm/components/ReportBuilder/convert.d.ts +1 -1
  57. package/dist/esm/components/ReportBuilder/convert.d.ts.map +1 -1
  58. package/dist/esm/components/ReportBuilder/convert.js +74 -22
  59. package/dist/esm/components/UiComponents.d.ts +2 -1
  60. package/dist/esm/components/UiComponents.d.ts.map +1 -1
  61. package/dist/esm/components/UiComponents.js +11 -4
  62. package/dist/esm/hooks/useExport.d.ts.map +1 -1
  63. package/dist/esm/hooks/useExport.js +4 -5
  64. package/dist/esm/internals/ReportBuilder/PivotForm.d.ts +19 -11
  65. package/dist/esm/internals/ReportBuilder/PivotForm.d.ts.map +1 -1
  66. package/dist/esm/internals/ReportBuilder/PivotForm.js +63 -49
  67. package/dist/esm/internals/ReportBuilder/PivotList.js +5 -4
  68. package/dist/esm/internals/ReportBuilder/PivotModal.d.ts +28 -31
  69. package/dist/esm/internals/ReportBuilder/PivotModal.d.ts.map +1 -1
  70. package/dist/esm/internals/ReportBuilder/PivotModal.js +327 -635
  71. package/dist/esm/models/Pivot.d.ts +27 -7
  72. package/dist/esm/models/Pivot.d.ts.map +1 -1
  73. package/dist/esm/utils/dashboard.d.ts.map +1 -1
  74. package/dist/esm/utils/dashboard.js +36 -11
  75. package/dist/esm/utils/merge.d.ts.map +1 -1
  76. package/dist/esm/utils/merge.js +2 -1
  77. package/dist/esm/utils/pivotConstructor.d.ts +1 -0
  78. package/dist/esm/utils/pivotConstructor.d.ts.map +1 -1
  79. package/dist/esm/utils/pivotConstructor.js +37 -8
  80. package/dist/esm/utils/pivotProcessing.d.ts.map +1 -1
  81. package/dist/esm/utils/pivotProcessing.js +10 -14
  82. package/dist/esm/utils/queryConstructor.d.ts.map +1 -1
  83. package/dist/esm/utils/queryConstructor.js +421 -134
  84. package/dist/esm/utils/report.d.ts.map +1 -1
  85. package/dist/esm/utils/report.js +2 -2
  86. package/dist/esm/utils/textProcessing.d.ts +1 -1
  87. package/dist/esm/utils/textProcessing.d.ts.map +1 -1
  88. package/dist/esm/utils/textProcessing.js +3 -0
  89. package/package.json +1 -1
@@ -2,25 +2,32 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useContext, useMemo, useState, useEffect, useRef, } from 'react';
3
3
  import { ClientContext, SchemaDataContext, TenantContext } from '../../Context';
4
4
  import { PivotList, PivotCard } from './PivotList';
5
- import { differenceInDays, eachDayOfInterval, eachMonthOfInterval, eachWeekOfInterval, eachYearOfInterval, endOfDay, isValid, isWithinInterval, parseISO, subMilliseconds, } from 'date-fns';
6
- import { compareValues, getValidDate, valueFormatter, } from '../../utils/valueFormatter';
7
- import { numberFormatOptions } from '../../ChartBuilder';
5
+ import { differenceInDays, eachDayOfInterval, eachMonthOfInterval, eachWeekOfInterval, eachYearOfInterval,
6
+ // endOfDay,
7
+ isValid,
8
+ // isWithinInterval,
9
+ parseISO,
10
+ // subMilliseconds,
11
+ } from 'date-fns';
12
+ import {
13
+ // compareValues,
14
+ // getValidDate,
15
+ valueFormatter, } from '../../utils/valueFormatter';
8
16
  import { snakeAndCamelCaseToTitleCase } from '../../utils/textProcessing';
9
- import { QuillErrorMessageComponent, QuillLoadingComponent, QuillPivotColumnContainer, QuillPivotRowContainer, } from '../../components/UiComponents';
17
+ import { MemoizedDeleteButton, MemoizedSubHeader, QuillErrorMessageComponent, QuillLoadingComponent, QuillPivotColumnContainer, QuillPivotRowContainer, } from '../../components/UiComponents';
10
18
  import { QuillCard } from '../../components/QuillCard';
11
19
  import { cleanPivot, getPossiblePivotFieldOptions, isValidPivot, } from '../../utils/pivotProcessing';
12
20
  import { hashCode } from '../../utils/crypto';
13
21
  import { getCountsByColumns, getQueryDateRangeByColumns, getUniqueValuesByColumns, } from '../../utils/tableProcessing';
14
22
  import { generatePivotWithSQL } from '../../utils/pivotConstructor';
15
23
  import { getDateBucketFromRange } from '../../utils/dates';
16
- // import Big from 'big.js';
17
24
  import equal from 'fast-deep-equal';
18
- import { uniqueValuesToStringMap } from '../../utils/filterProcessing';
19
25
  import { isStringType } from '../../utils/columnProcessing';
20
26
  import { useQuillCloud } from '../../utils/dataFetcher';
21
- export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField, setPivotColumnField, pivotValueField, setPivotValueField, pivotValueField2, setPivotValueField2, pivotAggregation, setPivotAggregation, popUpTitle, setPopUpTitle, selectedTable, SelectComponent, ButtonComponent, SecondaryButtonComponent, PopoverComponent, ErrorMessageComponent = QuillErrorMessageComponent, PivotRowContainer = QuillPivotRowContainer, PivotColumnContainer = QuillPivotColumnContainer, LoadingComponent = QuillLoadingComponent, CardComponent = QuillCard, HeaderComponent, LabelComponent, TextComponent, selectedPivotIndex, setSelectedPivotIndex, removePivot, selectPivot, showUpdatePivot, setShowUpdatePivot, data, columns, theme, isOpen, setIsOpen, dateRange, createdPivots, setCreatedPivots, recommendedPivots, setRecommendedPivots, triggerButtonText = 'Pivot', showPivotEditButton = false, showEditOnPivotClick = true, showTrigger = true, pivotCountRequest = 6, query, initialUniqueValues, uniqueValuesIsLoading, initialSelectedPivotTable, disabled = false, pivotRecommendationsEnabled = true, report, dashboardName, dateFilter, }) => {
27
+ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField, setPivotColumnField, pivotAggregations, setPivotAggregations, popUpTitle, setPopUpTitle, selectedTable, SelectComponent, ButtonComponent, SecondaryButtonComponent, PopoverComponent, ErrorMessageComponent = QuillErrorMessageComponent, PivotRowContainer = QuillPivotRowContainer, PivotColumnContainer = QuillPivotColumnContainer, LoadingComponent = QuillLoadingComponent, CardComponent = QuillCard, HeaderComponent, SubheaderComponent = MemoizedSubHeader, DeleteButtonComponent = MemoizedDeleteButton, LabelComponent, TextComponent, selectedPivotIndex, setSelectedPivotIndex, removePivot, selectPivot, showUpdatePivot, setShowUpdatePivot, data, columns, theme, isOpen, setIsOpen, dateRange, createdPivots, setCreatedPivots, recommendedPivots, setRecommendedPivots, triggerButtonText = 'Pivot', showPivotEditButton = false, showEditOnPivotClick = true, showTrigger = true, pivotCountRequest = 6, query, initialUniqueValues, uniqueValuesIsLoading, initialSelectedPivotTable, disabled = false, pivotRecommendationsEnabled = true, report, dashboardName, dateFilter, }) => {
22
28
  const [isLoading, setIsLoading] = useState(false);
23
29
  const [previewLoading, setPreviewLoading] = useState(false);
30
+ // const [showPercentageAggPopover, setShowPercentageAggPopover] = useState<number | undefined>(undefined);
24
31
  const [selectedPivotType, setSelectedPivotType] = useState('recommended');
25
32
  const [errors, setErrors] = useState([]);
26
33
  const [client] = useContext(ClientContext);
@@ -38,6 +45,7 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
38
45
  const [allowedValueFields, setAllowedValueFields] = useState([]);
39
46
  const [uniqueValues, setUniqueValues] = useState(initialUniqueValues);
40
47
  const buttonRef = useRef(null);
48
+ // const percentageAggButtonRef = useRef<HTMLDivElement>(null);
41
49
  const [dateRanges, setDateRanges] = useState({});
42
50
  const [pivotError, setPivotError] = useState('');
43
51
  const getDistinctValues = async (fetchDistinct) => {
@@ -122,15 +130,25 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
122
130
  }, [showUpdatePivot, isOpen]);
123
131
  useEffect(() => {
124
132
  const fetchPivotData = async () => {
125
- if (pivotRowField && data && columns) {
133
+ if (pivotRowField && data && columns && pivotAggregations?.every((p) => p?.valueField && p?.aggregationType)) {
126
134
  const pivot = {
127
135
  rowField: pivotRowField || '',
128
- rowFieldType: columnsToShow[pivotRowField || ''],
136
+ rowFieldType: columnTypes[pivotRowField ?? ''],
129
137
  columnField: pivotColumnField,
130
- columnFieldType: columnsToShow[pivotColumnField || ''],
131
- valueField: pivotValueField || '',
132
- aggregationType: pivotAggregation || '',
133
- valueField2: pivotValueField2 || '',
138
+ columnFieldType: columnTypes[pivotColumnField ?? ''],
139
+ aggregations: pivotAggregations?.map((p) => ({
140
+ valueField: p.valueField,
141
+ valueFieldType: columnTypes[p.valueField ?? ''],
142
+ valueField2: p.valueField2,
143
+ valueField2Type: columnTypes[p.valueField2 ?? ''],
144
+ aggregationType: p.aggregationType,
145
+ })),
146
+ // For backwards compatibility
147
+ valueField: pivotAggregations?.[0]?.valueField,
148
+ valueFieldType: columnTypes[pivotAggregations?.[0]?.valueField ?? ''],
149
+ valueField2: pivotAggregations?.[0]?.valueField2,
150
+ valueField2Type: columnTypes[pivotAggregations?.[0]?.valueField2 ?? ''],
151
+ aggregationType: pivotAggregations?.[0]?.aggregationType,
134
152
  };
135
153
  try {
136
154
  const { rows, columns } = await generatePivotTable({
@@ -166,13 +184,23 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
166
184
  }
167
185
  if (pivotRowField && data && columns) {
168
186
  const pivot = {
169
- rowField: pivotRowField || '',
170
- rowFieldType: columnsToShow[pivotRowField || ''],
187
+ rowField: pivotRowField ?? '',
188
+ rowFieldType: columnTypes[pivotRowField ?? ''],
171
189
  columnField: pivotColumnField,
172
- columnFieldType: columnsToShow[pivotColumnField || ''],
173
- valueField: pivotValueField || '',
174
- aggregationType: pivotAggregation || '',
175
- valueField2: pivotValueField2 || '',
190
+ columnFieldType: columnTypes[pivotColumnField ?? ''],
191
+ aggregations: pivotAggregations?.map((p) => ({
192
+ valueField: p.valueField,
193
+ valueFieldType: columnTypes[p.valueField ?? ''],
194
+ valueField2: p.valueField2,
195
+ valueField2Type: columnTypes[p.valueField2 ?? ''],
196
+ aggregationType: p.aggregationType,
197
+ })),
198
+ // For backwards compatibility
199
+ valueField: pivotAggregations?.[0]?.valueField,
200
+ valueFieldType: columnTypes[pivotAggregations?.[0]?.valueField ?? ''],
201
+ valueField2: pivotAggregations?.[0]?.valueField2,
202
+ valueField2Type: columnTypes[pivotAggregations?.[0]?.valueField2 ?? ''],
203
+ aggregationType: pivotAggregations?.[0]?.aggregationType,
176
204
  };
177
205
  if (initialSelectedPivotTable) {
178
206
  setSamplePivotTable({
@@ -239,6 +267,12 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
239
267
  return map;
240
268
  }, {});
241
269
  }, [columns]);
270
+ const columnTypes = useMemo(() => {
271
+ return (columns || []).reduce((map, col) => {
272
+ map[col.field] = col.jsType;
273
+ return map;
274
+ }, {});
275
+ }, [columns]);
242
276
  const [selectedPivotTable, setSelectedPivotTable] = useState(null);
243
277
  useEffect(() => {
244
278
  const fetchPivotTables = async () => {
@@ -299,22 +333,78 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
299
333
  selectPivot(pivot);
300
334
  setSelectedPivotType('recommended');
301
335
  }
336
+ setPivotError('');
302
337
  setIsOpen(false);
303
338
  };
304
339
  const onSelectCreatedPivot = (pivot) => {
305
340
  selectPivot(pivot, pivot.columnField ? uniqueValues : undefined);
306
341
  setSelectedPivotType('created');
342
+ setPivotError('');
307
343
  setIsOpen(false);
308
344
  setPopUpTitle('Add pivot');
309
345
  };
346
+ const onCommitPivot = useCallback(() => {
347
+ const errors = [];
348
+ if ((pivotAggregations?.length ?? 0) === 0) {
349
+ errors.push('You must have at least one aggregation');
350
+ }
351
+ if (pivotAggregations.some((p) => !p.valueField && p.aggregationType !== 'count')) {
352
+ errors.push("Value field cannot be empty when aggregation is not 'count'");
353
+ }
354
+ if (pivotAggregations.some((p) => !p.aggregationType)) {
355
+ errors.push('Aggregation cannot be empty');
356
+ }
357
+ if (pivotRowField && !columnsToShow[pivotRowField]) {
358
+ errors.push('Error in row field: undefined type');
359
+ }
360
+ if (pivotColumnField &&
361
+ !columnsToShow[pivotColumnField]) {
362
+ errors.push('Error in column field: undefined type');
363
+ }
364
+ if (errors.length === 0 && pivotAggregations?.every((p) => p.valueField && p.aggregationType)) {
365
+ const pivot = {
366
+ rowField: pivotRowField,
367
+ rowFieldType: columnTypes[pivotRowField ?? ''],
368
+ columnField: pivotColumnField,
369
+ columnFieldType: columnsToShow[pivotColumnField ?? ''],
370
+ aggregations: pivotAggregations?.map((p) => ({
371
+ valueField: p.valueField,
372
+ valueFieldType: columnTypes[p.valueField ?? ''],
373
+ valueField2: p.valueField2,
374
+ valueField2Type: columnTypes[p.valueField2 ?? ''],
375
+ aggregationType: p.aggregationType,
376
+ })),
377
+ // For backwards compatibility
378
+ valueField: pivotAggregations?.[0]?.valueField,
379
+ valueFieldType: columnTypes[pivotAggregations?.[0]?.valueField ?? ''],
380
+ valueField2: pivotAggregations?.[0]?.valueField2,
381
+ valueField2Type: columnTypes[pivotAggregations?.[0]?.valueField2 ?? ''],
382
+ aggregationType: pivotAggregations?.[0]?.aggregationType,
383
+ };
384
+ if (isValidPivot(pivot).valid) {
385
+ pivot.title = generatePivotTitle(pivot);
386
+ setPivotError('');
387
+ setIsOpen(false);
388
+ setCreatedPivots([pivot]);
389
+ onSelectCreatedPivot(pivot);
390
+ setPopUpTitle('Add pivot');
391
+ }
392
+ else {
393
+ errors.push(isValidPivot(pivot).reason);
394
+ }
395
+ }
396
+ setErrors(errors);
397
+ }, [
398
+ pivotRowField,
399
+ pivotColumnField,
400
+ pivotAggregations,
401
+ ]);
310
402
  const onEditPivot = async (pivot, index, pivotType) => {
311
403
  setIsLoading(false);
312
404
  setErrors([]);
313
405
  setPivotRowField(pivot.rowField);
314
406
  setPivotColumnField(pivot.columnField);
315
- setPivotValueField(pivot.valueField);
316
- setPivotValueField2(pivot.valueField2);
317
- setPivotAggregation(pivot.aggregationType);
407
+ setPivotAggregations(pivot.aggregations ?? []);
318
408
  setShowUpdatePivot(true);
319
409
  if (pivotType === 'recommended' &&
320
410
  index !== null &&
@@ -418,10 +508,8 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
418
508
  }
419
509
  return {
420
510
  ...pivot,
421
- rowFieldType: columnsToShow[pivot.rowField || ''],
422
- columnFieldType: pivot.columnField
423
- ? columnsToShow[pivot.columnField]
424
- : undefined,
511
+ rowFieldType: columnTypes[pivot.rowField ?? ''],
512
+ columnFieldType: columnTypes[pivot.columnField ?? ''],
425
513
  title: generatePivotTitle(pivot),
426
514
  };
427
515
  });
@@ -459,7 +547,17 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
459
547
  }
460
548
  }));
461
549
  const validPivotTables = pts.filter((pt) => pt !== undefined);
462
- setRecommendedPivotTables(validPivotTables);
550
+ setRecommendedPivotTables(validPivotTables.map((pt) => ({
551
+ ...pt,
552
+ pivot: {
553
+ ...pt.pivot,
554
+ aggregations: [{
555
+ valueField: pt.pivot.valueField,
556
+ aggregationType: pt.pivot.aggregationType,
557
+ valueField2: pt.pivot.valueField2,
558
+ }]
559
+ }
560
+ })));
463
561
  if (validPivotTables.length === 0) {
464
562
  sethasNoRecommendedPivots(true);
465
563
  }
@@ -484,29 +582,73 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
484
582
  columns,
485
583
  ]);
486
584
  const pivotFieldChange = async (field, value) => {
585
+ console.log();
586
+ if (typeof value !== 'string' && (!value?.length || !value?.every((p) => p?.aggregationType))) {
587
+ return;
588
+ }
589
+ if (typeof value !== 'string' && value.some((p) => !p?.valueField && (p?.aggregationType !== 'count' || !pivotRowField))) {
590
+ return;
591
+ }
487
592
  setErrors([]);
488
593
  const pivot = {
489
594
  rowField: pivotRowField,
490
- rowFieldType: columnsToShow[pivotRowField],
595
+ rowFieldType: columnTypes[pivotRowField ?? ''],
491
596
  columnField: pivotColumnField,
492
- valueField: pivotValueField,
493
- valueField2: pivotValueField2,
494
- aggregationType: pivotAggregation,
597
+ aggregations: pivotAggregations?.map((p) => {
598
+ return {
599
+ valueField: p.valueField,
600
+ valueFieldType: columnTypes[p.valueField ?? ''],
601
+ valueField2: p.valueField2,
602
+ valueField2Type: columnTypes[p.valueField2 ?? ''],
603
+ aggregationType: p.aggregationType
604
+ };
605
+ }),
606
+ // For backwards compatibility
607
+ valueField: pivotAggregations?.[0]?.valueField,
608
+ valueFieldType: columnTypes[pivotAggregations?.[0]?.valueField ?? ''],
609
+ valueField2: pivotAggregations?.[0]?.valueField2,
610
+ valueField2Type: columnTypes[pivotAggregations?.[0]?.valueField2 ?? ''],
611
+ aggregationType: pivotAggregations?.[0]?.aggregationType,
495
612
  };
496
- // @ts-ignore
497
- pivot[field] = value;
613
+ if (field === 'aggregations') {
614
+ pivot.aggregations = value.map((p) => ({
615
+ valueField: p.valueField,
616
+ valueFieldType: columnTypes[p.valueField ?? ''],
617
+ valueField2: p.valueField2,
618
+ valueField2Type: columnTypes[p.valueField2 ?? ''],
619
+ aggregationType: p.aggregationType
620
+ }));
621
+ }
622
+ else {
623
+ pivot[field] = value;
624
+ }
498
625
  pivot.title = generatePivotTitle(pivot);
499
626
  if (field === 'rowField') {
500
- pivot.rowFieldType = columnsToShow[value];
627
+ if (value === '') {
628
+ setPivotColumnField(undefined);
629
+ // Also clear any percent aggregations
630
+ setPivotAggregations(pivotAggregations.map((agg) => {
631
+ return {
632
+ ...agg,
633
+ aggregationType: agg.aggregationType === 'percentage' ? undefined : agg.aggregationType,
634
+ };
635
+ }));
636
+ }
637
+ pivot.rowFieldType = columnTypes[value];
501
638
  }
502
639
  else if (field === 'columnField') {
503
- pivot.columnFieldType = columnsToShow[value];
504
- if (!value && pivotValueField2 === pivotValueField) {
505
- // only allow same field percentage with columnField
506
- setPivotValueField2(undefined);
507
- pivot.valueField2 = undefined;
508
- }
640
+ pivot.columnFieldType = columnTypes[value];
509
641
  }
642
+ pivot.aggregations?.forEach((agg, index) => {
643
+ if (!value && agg.valueField === agg.valueField2) {
644
+ agg.valueField2 = undefined;
645
+ setPivotAggregations([
646
+ ...pivotAggregations.slice(0, index),
647
+ { ...pivotAggregations[index], valueField2: undefined },
648
+ ...pivotAggregations.slice(index + 1),
649
+ ]);
650
+ }
651
+ });
510
652
  if (!isValidPivot(pivot).valid) {
511
653
  setSamplePivotTable(null);
512
654
  return;
@@ -535,6 +677,7 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
535
677
  }
536
678
  catch (e) {
537
679
  if (e instanceof Error) {
680
+ console.log('error', e);
538
681
  setPivotError(e.message);
539
682
  setSamplePivotTable(null);
540
683
  return;
@@ -591,6 +734,7 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
591
734
  right: -2,
592
735
  } })) }), showTrigger && (_jsx("div", { ref: buttonRef, children: _jsx(SecondaryButtonComponent, { disabled: disabled, onClick: () => {
593
736
  if (columns.length === 0) {
737
+ setPivotError('');
594
738
  setIsOpen(false);
595
739
  }
596
740
  // table is not loaded yet, so pivot button is not clickable
@@ -629,135 +773,102 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
629
773
  width: pivotCardWidth,
630
774
  minHeight: 160,
631
775
  }, children: _jsx(PivotCard, { pivotTable: samplePivotTable, theme: theme, index: 0, selectedPivotIndex: -1, onEditPivot: () => { }, ButtonComponent: ButtonComponent, showEdit: false, clickable: false, minHeight: 180, LabelComponent: LabelComponent, TextComponent: TextComponent, HeaderComponent: HeaderComponent, CardComponent: CardComponent, onSelectPivot: () => { }, onClose: () => {
632
- setPivotAggregation(null);
633
- setPivotRowField(null);
634
- setPivotValueField(null);
635
- setPivotValueField2(null);
636
- setPivotColumnField(null);
637
- setSamplePivotTable(null);
638
- } }) })), _jsxs(PivotColumnContainer, { children: [_jsxs(PivotRowContainer, { children: [_jsx("div", { ref: rowFieldRef, children: _jsx(SelectComponent, { id: "pivot-row-field", label: "Row field", value: pivotRowField, onChange: (e) => {
639
- pivotFieldChange('rowField', e.target.value);
640
- setPivotRowField(e.target.value === ''
641
- ? undefined
642
- : e.target.value);
643
- }, options: allowedRowFields
644
- .filter((field) => field !== pivotColumnField)
645
- .map((field) => {
646
- return {
647
- label: snakeAndCamelCaseToTitleCase(field),
648
- value: field,
649
- };
650
- }), isLoading: uniqueValuesIsLoading, width: 200 }) }), _jsx("div", { ref: colFieldRef, children: _jsx(SelectComponent, { id: "pivot-row-field", label: "Column field", value: pivotColumnField, onChange: (e) => {
651
- pivotFieldChange('columnField', e.target.value);
652
- setPivotColumnField(e.target.value === ''
653
- ? undefined
654
- : e.target.value);
655
- }, options: allowedColumnFields
656
- .filter((field) => field !== pivotRowField)
657
- .map((field) => {
658
- return {
659
- label: snakeAndCamelCaseToTitleCase(field),
660
- value: field,
661
- };
662
- }), isLoading: uniqueValuesIsLoading, width: 200 }) })] }), _jsxs(PivotRowContainer, { children: [_jsx(SelectComponent, { id: "pivot-row-field", label: "Aggregation type", value: pivotAggregation, onChange: (e) => {
663
- if (e.target.value !== 'count' &&
664
- pivotValueField &&
665
- !numberFormatOptions.includes(columns.find((col) => col.field === pivotValueField).format)) {
666
- setPivotValueField(null);
667
- }
668
- pivotFieldChange('aggregationType', e.target.value);
669
- setPivotAggregation(e.target.value === ''
670
- ? undefined
671
- : e.target.value);
672
- }, options: [
673
- ...[
674
- 'sum',
675
- 'average',
676
- 'count',
677
- 'max',
678
- 'min',
679
- 'percentage',
680
- ].map((option) => {
681
- return { label: option, value: option };
682
- }),
683
- ], width: 200 }), _jsx(SelectComponent, { id: "pivot-row-field", label: "Value field", value: pivotValueField, onChange: (e) => {
684
- pivotFieldChange('valueField', e.target.value);
685
- setPivotValueField(e.target.value === ''
686
- ? undefined
687
- : e.target.value);
688
- }, options: allowedValueFields
689
- .map((field) => {
690
- return {
691
- label: snakeAndCamelCaseToTitleCase(field),
692
- value: field,
693
- };
694
- })
695
- .filter((option) => {
696
- return (pivotAggregation !== 'percentage' ||
697
- pivotColumnField ||
698
- option.value !== pivotValueField2);
699
- }), isLoading: uniqueValuesIsLoading, width: 200 }), pivotAggregation === 'percentage' ? (_jsx(SelectComponent, { id: "pivot-row-field", label: "Total field", value: pivotValueField2, onChange: (e) => {
700
- pivotFieldChange('valueField2', e.target.value);
701
- setPivotValueField2(e.target.value === ''
702
- ? undefined
703
- : e.target.value);
704
- }, options: allowedValueFields
705
- .map((field) => {
706
- return {
707
- label: snakeAndCamelCaseToTitleCase(field),
708
- value: field,
709
- };
710
- })
711
- .filter((option) => {
712
- return (pivotAggregation !== 'percentage' ||
713
- pivotColumnField ||
714
- option.value !== pivotValueField);
715
- }), isLoading: uniqueValuesIsLoading, width: 200 })) : null] })] }), _jsxs("div", { children: [_jsx(ButtonComponent, { id: "custom-button", onClick: () => {
716
- const errors = [];
717
- if (!pivotValueField &&
718
- pivotAggregation !== 'count') {
719
- errors.push("Value field cannot be empty when aggregation is not 'count'");
720
- }
721
- if (!pivotAggregation) {
722
- errors.push('Aggregation cannot be empty');
723
- }
724
- if (pivotRowField && !columnsToShow[pivotRowField]) {
725
- errors.push('Error in row field: undefined type');
726
- }
727
- if (pivotColumnField &&
728
- !columnsToShow[pivotColumnField]) {
729
- errors.push('Error in column field: undefined type');
730
- }
731
- if (errors.length === 0) {
732
- const pivot = {
733
- rowField: pivotRowField || '',
734
- rowFieldType: columnsToShow[pivotRowField || ''],
735
- columnField: pivotColumnField,
736
- columnFieldType: columnsToShow[pivotColumnField || ''],
737
- valueField: pivotValueField || '',
738
- valueField2: pivotValueField2 || '',
739
- aggregationType: pivotAggregation || '',
740
- };
741
- if (isValidPivot(pivot).valid) {
742
- pivot.title = generatePivotTitle(pivot);
743
- setIsOpen(false);
744
- setCreatedPivots([pivot]);
745
- onSelectCreatedPivot(pivot);
746
- setPopUpTitle('Add pivot');
747
- }
748
- else {
749
- errors.push(isValidPivot(pivot).reason);
750
- }
751
- }
752
- setErrors(errors);
753
- }, label: popUpTitle }), errors.length > 0 && (_jsx("div", { style: {
754
- display: 'flex',
755
- flexDirection: 'column',
756
- gap: 8,
757
- paddingTop: 8,
758
- width: pivotCardWidth,
759
- maxWidth: pivotCardWidth,
760
- }, children: errors.map((error, index) => (_jsx(ErrorMessageComponent, { errorMessage: error }, `error_message_${index}`))) }))] })] })) : (_jsx("div", { style: {
776
+ setPivotAggregations([{ valueField: undefined, valueField2: undefined, aggregationType: undefined }]);
777
+ setPivotRowField(undefined);
778
+ setPivotColumnField(undefined);
779
+ setSamplePivotTable(undefined);
780
+ } }) })), _jsxs(PivotColumnContainer, { children: [_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [_jsx(HeaderComponent, { label: "Groupings" }), _jsxs(PivotRowContainer, { children: [_jsx("div", { ref: rowFieldRef, children: _jsx(SelectComponent, { id: "pivot-row-field", label: "Group rows by", value: pivotRowField, onChange: (e) => {
781
+ pivotFieldChange('rowField', e.target.value);
782
+ setPivotRowField(e.target.value === ''
783
+ ? undefined
784
+ : e.target.value);
785
+ }, options: allowedRowFields
786
+ .filter((field) => field !== pivotColumnField)
787
+ .map((field) => {
788
+ return {
789
+ label: snakeAndCamelCaseToTitleCase(field),
790
+ value: field,
791
+ };
792
+ }), isLoading: uniqueValuesIsLoading, width: 200 }) }), _jsx("div", { ref: colFieldRef, children: _jsx(SelectComponent, { id: "pivot-column-field", label: "Group columns by", value: pivotColumnField, onChange: (e) => {
793
+ pivotFieldChange('columnField', e.target.value);
794
+ setPivotColumnField(e.target.value === ''
795
+ ? undefined
796
+ : e.target.value);
797
+ }, options: allowedColumnFields
798
+ .filter((field) => field !== pivotRowField)
799
+ .map((field) => {
800
+ return {
801
+ label: snakeAndCamelCaseToTitleCase(field),
802
+ value: field,
803
+ };
804
+ }), isLoading: uniqueValuesIsLoading, width: 200, disabled: pivotRowField === undefined }) })] })] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [_jsx(HeaderComponent, { label: "Aggregations" }), _jsxs(PivotRowContainer, { children: [_jsx("div", { style: { width: 200 }, children: _jsx(SubheaderComponent, { label: "Aggregation Type" }) }), _jsx("div", { style: { width: 200 }, children: _jsx(SubheaderComponent, { label: "Value Field" }) })] }), pivotAggregations?.map((agg, index) => _jsxs(PivotRowContainer, { children: [_jsx(SelectComponent, { id: "pivot-aggregation-type", value: agg.aggregationType, onChange: (e) => {
805
+ // if (
806
+ // e.target.value !== 'count' &&
807
+ // agg.valueField &&
808
+ // !numberFormatOptions.includes(
809
+ // columns.find(
810
+ // (col: Column) =>
811
+ // col.field === agg.valueField,
812
+ // )!.format,
813
+ // )
814
+ // ) {
815
+ // setPivotValueField(undefined);
816
+ // }
817
+ const newAgg = [
818
+ ...pivotAggregations.slice(0, index),
819
+ { ...agg, aggregationType: e.target.value === '' ? undefined : e.target.value },
820
+ ...pivotAggregations.slice(index + 1),
821
+ ];
822
+ pivotFieldChange('aggregations', newAgg);
823
+ setPivotAggregations(newAgg);
824
+ }, options: [
825
+ ...[
826
+ 'sum',
827
+ 'average',
828
+ 'count',
829
+ 'max',
830
+ 'min',
831
+ ...(pivotRowField ? ['percentage'] : []),
832
+ ].map((option) => {
833
+ return { label: option, value: option };
834
+ }),
835
+ ], width: 200 }), _jsx(SelectComponent, { id: "pivot-value-field", value: agg.valueField, onChange: (e) => {
836
+ const newAgg = [
837
+ ...pivotAggregations.slice(0, index),
838
+ { ...agg, valueField: e.target.value === '' ? undefined : e.target.value },
839
+ ...pivotAggregations.slice(index + 1),
840
+ ];
841
+ pivotFieldChange('aggregations', newAgg);
842
+ setPivotAggregations(newAgg);
843
+ }, options: allowedValueFields
844
+ .map((field) => {
845
+ return {
846
+ label: snakeAndCamelCaseToTitleCase(field),
847
+ value: field,
848
+ };
849
+ }), isLoading: uniqueValuesIsLoading, width: 200 }), _jsx("div", { style: { marginLeft: -16, marginRight: -16, visibility: index > 0 ? 'visible' : 'hidden' }, children: _jsx(DeleteButtonComponent, { onClick: () => {
850
+ setPivotAggregations([
851
+ ...pivotAggregations.slice(0, index),
852
+ ...pivotAggregations.slice(index + 1),
853
+ ]);
854
+ } }) })] }, index))] })] }), _jsx("div", { style: { display: 'flex', flexDirection: 'row', marginRight: 'auto' }, children: _jsx(SecondaryButtonComponent, { label: 'Add Aggregation', onClick: () => {
855
+ setPivotAggregations([
856
+ ...pivotAggregations,
857
+ { aggregationType: undefined, valueField: undefined, valueField2: undefined },
858
+ ]);
859
+ } }) }), errors.length > 0 && (_jsx("div", { style: {
860
+ display: 'flex',
861
+ flexDirection: 'column',
862
+ gap: 8,
863
+ paddingTop: 8,
864
+ width: pivotCardWidth,
865
+ maxWidth: pivotCardWidth,
866
+ }, children: errors.map((error, index) => (_jsx(ErrorMessageComponent, { errorMessage: error }, `error_message_${index}`))) })), _jsxs("div", { style: { display: 'flex', flexDirection: 'row', marginLeft: 'auto', gap: 8 }, children: [_jsx(SecondaryButtonComponent, { onClick: () => {
867
+ setPivotError('');
868
+ setIsOpen(false);
869
+ setPopUpTitle('Add pivot');
870
+ }, label: "Cancel" }), _jsx(ButtonComponent, { id: "custom-button", onClick: onCommitPivot, label: 'Save', disabled: !(pivotAggregations?.length > 0) ||
871
+ pivotAggregations.some((agg) => !agg.aggregationType || !agg.valueField) })] })] })) : (_jsx("div", { style: {
761
872
  display: 'flex',
762
873
  flexDirection: 'column',
763
874
  fontFamily: theme?.fontFamily,
@@ -778,7 +889,7 @@ export const PivotModal = ({ pivotRowField, setPivotRowField, pivotColumnField,
778
889
  display: 'flex',
779
890
  flexDirection: 'row',
780
891
  gap: 8,
781
- }, children: [_jsx(SecondaryButtonComponent, { label: "Regenerate", onClick: refreshPivots, icon: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", style: { width: 16, height: 16 }, children: _jsx("path", { fillRule: "evenodd", d: "M9 4.5a.75.75 0 0 1 .721.544l.813 2.846a3.75 3.75 0 0 0 2.576 2.576l2.846.813a.75.75 0 0 1 0 1.442l-2.846.813a3.75 3.75 0 0 0-2.576 2.576l-.813 2.846a.75.75 0 0 1-1.442 0l-.813-2.846a3.75 3.75 0 0 0-2.576-2.576l-2.846-.813a.75.75 0 0 1 0-1.442l2.846-.813A3.75 3.75 0 0 0 7.466 7.89l.813-2.846A.75.75 0 0 1 9 4.5ZM18 1.5a.75.75 0 0 1 .728.568l.258 1.036c.236.94.97 1.674 1.91 1.91l1.036.258a.75.75 0 0 1 0 1.456l-1.036.258c-.94.236-1.674.97-1.91 1.91l-.258 1.036a.75.75 0 0 1-1.456 0l-.258-1.036a2.625 2.625 0 0 0-1.91-1.91l-1.036-.258a.75.75 0 0 1 0-1.456l1.036-.258a2.625 2.625 0 0 0 1.91-1.91l.258-1.036A.75.75 0 0 1 18 1.5ZM16.5 15a.75.75 0 0 1 .712.513l.394 1.183c.15.447.5.799.948.948l1.183.395a.75.75 0 0 1 0 1.422l-1.183.395c-.447.15-.799.5-.948.948l-.395 1.183a.75.75 0 0 1-1.422 0l-.395-1.183a1.5 1.5 0 0 0-.948-.948l-1.183-.395a.75.75 0 0 1 0-1.422l1.183-.395c.447-.15.799-.5.948-.948l.395-1.183A.75.75 0 0 1 16.5 15Z", clipRule: "evenodd" }) }) }), _jsx(SecondaryButtonComponent, { label: "Create pivot +", onClick: () => onEditPivot({}, null) })] }), isLoading || uniqueValuesIsLoading ? (_jsx(LoadingComponent, {})) : pivotError ? (_jsx("div", { style: {
892
+ }, children: [_jsx(SecondaryButtonComponent, { label: "Regenerate", onClick: refreshPivots, icon: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", style: { width: 16, height: 16 }, children: _jsx("path", { fillRule: "evenodd", d: "M9 4.5a.75.75 0 0 1 .721.544l.813 2.846a3.75 3.75 0 0 0 2.576 2.576l2.846.813a.75.75 0 0 1 0 1.442l-2.846.813a3.75 3.75 0 0 0-2.576 2.576l-.813 2.846a.75.75 0 0 1-1.442 0l-.813-2.846a3.75 3.75 0 0 0-2.576-2.576l-2.846-.813a.75.75 0 0 1 0-1.442l2.846-.813A3.75 3.75 0 0 0 7.466 7.89l.813-2.846A.75.75 0 0 1 9 4.5ZM18 1.5a.75.75 0 0 1 .728.568l.258 1.036c.236.94.97 1.674 1.91 1.91l1.036.258a.75.75 0 0 1 0 1.456l-1.036.258c-.94.236-1.674.97-1.91 1.91l-.258 1.036a.75.75 0 0 1-1.456 0l-.258-1.036a2.625 2.625 0 0 0-1.91-1.91l-1.036-.258a.75.75 0 0 1 0-1.456l1.036-.258a2.625 2.625 0 0 0 1.91-1.91l.258-1.036A.75.75 0 0 1 18 1.5ZM16.5 15a.75.75 0 0 1 .712.513l.394 1.183c.15.447.5.799.948.948l1.183.395a.75.75 0 0 1 0 1.422l-1.183.395c-.447.15-.799.5-.948.948l-.395 1.183a.75.75 0 0 1-1.422 0l-.395-1.183a1.5 1.5 0 0 0-.948-.948l-1.183-.395a.75.75 0 0 1 0-1.422l1.183-.395c.447-.15.799-.5.948-.948l.395-1.183A.75.75 0 0 1 16.5 15Z", clipRule: "evenodd" }) }) }), _jsx(SecondaryButtonComponent, { label: "Create pivot +", onClick: () => onEditPivot({ aggregations: [{}] }, null) })] }), isLoading || uniqueValuesIsLoading ? (_jsx(LoadingComponent, {})) : pivotError ? (_jsx("div", { style: {
782
893
  width: '100%',
783
894
  height: '140px',
784
895
  display: 'flex',
@@ -805,7 +916,7 @@ export function generatePivotTableYAxis(pivot, cols, yAxisField) {
805
916
  if (pivot?.aggregationType === 'count') {
806
917
  return [
807
918
  {
808
- field: pivot.valueField || 'count',
919
+ field: pivot.valueField ?? 'count',
809
920
  label: yAxisField.label,
810
921
  format: yAxisField.format,
811
922
  },
@@ -814,22 +925,22 @@ export function generatePivotTableYAxis(pivot, cols, yAxisField) {
814
925
  // otherwise use the default (ie. the field label)
815
926
  return [
816
927
  {
817
- field: pivot.valueField,
928
+ field: pivot.valueField ?? 'count',
818
929
  label: yAxisField.label,
819
930
  format: yAxisField.format,
820
931
  },
821
932
  ];
822
933
  }
823
934
  export function generatePivotTitle(pivot) {
824
- if (pivot.rowField && !pivot.valueField) {
825
- return snakeAndCamelCaseToTitleCase(`${pivot.aggregationType} of ${pivot.rowField}
935
+ if (pivot.rowField && !pivot.valueField && pivot.aggregations?.[0]) {
936
+ return snakeAndCamelCaseToTitleCase(`${pivot.aggregations[0].aggregationType} of ${pivot.rowField}
826
937
  `);
827
938
  }
828
- else if (pivot.valueField && !pivot.rowField) {
829
- return snakeAndCamelCaseToTitleCase(`${pivot.aggregationType} of ${pivot.valueField}
939
+ else if (!pivot.rowField && pivot.aggregations?.[0]?.valueField) {
940
+ return snakeAndCamelCaseToTitleCase(`${pivot.aggregations[0].aggregationType} of ${pivot.aggregations[0].valueField}
830
941
  `);
831
942
  }
832
- return snakeAndCamelCaseToTitleCase(`${pivot.aggregationType} of ${pivot.valueField} by ${pivot.rowField}${pivot.columnField ? ` and ${pivot.columnField}` : ''}`);
943
+ return snakeAndCamelCaseToTitleCase(`${pivot.aggregations?.[0]?.aggregationType ?? 'Aggregation'} of ${pivot.aggregations?.[0]?.valueField ?? 'value'} by ${pivot.rowField}${pivot.columnField ? ` and ${pivot.columnField}` : ''}`);
833
944
  }
834
945
  function castValueToDate(value) {
835
946
  if (!value) {
@@ -957,13 +1068,18 @@ export function getDateBuckets(dateRange, column, data, dateBucket) {
957
1068
  const dayDifference = differenceInDays(dateRange.end, dateRange.start);
958
1069
  return dayDifferenceToInterval(dayDifference, dateRange);
959
1070
  }
960
- function getCompDateBuckets(dateRange, compDateRange, column, data) {
961
- if (!dateRange) {
962
- return eachMonthOfInterval(getDateRange(undefined, column, data));
963
- }
964
- const dayDifference = differenceInDays(dateRange.end, dateRange.start);
965
- return dayDifferenceToInterval(dayDifference, compDateRange);
966
- }
1071
+ // function getCompDateBuckets(
1072
+ // dateRange: { start: Date; end: Date } | undefined,
1073
+ // compDateRange: any,
1074
+ // column: string,
1075
+ // data: any,
1076
+ // ) {
1077
+ // if (!dateRange) {
1078
+ // return eachMonthOfInterval(getDateRange(undefined, column, data));
1079
+ // }
1080
+ // const dayDifference = differenceInDays(dateRange.end, dateRange.start);
1081
+ // return dayDifferenceToInterval(dayDifference, compDateRange);
1082
+ // }
967
1083
  function dayDifferenceToInterval(dayDifference, dateRange) {
968
1084
  if (dayDifference < 14) {
969
1085
  return eachDayOfInterval(dateRange);
@@ -1039,30 +1155,33 @@ export function isDateField(fieldType) {
1039
1155
  fieldType === 'MMM_dd_hh:mm_ap_pm' ||
1040
1156
  fieldType === 'hh_ap_pm');
1041
1157
  }
1042
- const isNullUndefinedOrInvalidDate = (date) => date === null || date === undefined || isNaN(date.getTime());
1043
- // If the value is an object with key value (ie. a BigQuery date) we will
1044
- // return the inner value, otherwise return passed param as-is.
1045
- const handleBigQueryValue = (value) => {
1046
- if (Boolean(value) &&
1047
- typeof value === 'object' &&
1048
- Object.keys(value).length === 1 &&
1049
- value['value']) {
1050
- return value['value'];
1051
- }
1052
- return value;
1053
- };
1054
- // Process out all the BigQuery dates.
1055
- const fixBigQueryData = (data = []) => {
1056
- const newData = [];
1057
- for (const row of data) {
1058
- const newRow = {};
1059
- for (const key in row) {
1060
- newRow[key] = handleBigQueryValue(row[key]);
1061
- }
1062
- newData.push(newRow);
1063
- }
1064
- return newData;
1065
- };
1158
+ // const isNullUndefinedOrInvalidDate = (date: Date) =>
1159
+ // date === null || date === undefined || isNaN(date.getTime());
1160
+ // // If the value is an object with key value (ie. a BigQuery date) we will
1161
+ // // return the inner value, otherwise return passed param as-is.
1162
+ // const handleBigQueryValue = (value: any) => {
1163
+ // if (
1164
+ // Boolean(value) &&
1165
+ // typeof value === 'object' &&
1166
+ // Object.keys(value).length === 1 &&
1167
+ // value['value']
1168
+ // ) {
1169
+ // return value['value'];
1170
+ // }
1171
+ // return value;
1172
+ // };
1173
+ // // Process out all the BigQuery dates.
1174
+ // const fixBigQueryData = (data: any[] = []) => {
1175
+ // const newData = [];
1176
+ // for (const row of data) {
1177
+ // const newRow: any = {};
1178
+ // for (const key in row) {
1179
+ // newRow[key] = handleBigQueryValue(row[key]);
1180
+ // }
1181
+ // newData.push(newRow);
1182
+ // }
1183
+ // return newData;
1184
+ // };
1066
1185
  export async function generatePivotTable({ pivot, dateBucket, dateFilter, report, client, uniqueValues, dashboardName, tenants, additionalProcessing, }) {
1067
1186
  try {
1068
1187
  if (report && client) {
@@ -1089,430 +1208,3 @@ export async function generatePivotTable({ pivot, dateBucket, dateFilter, report
1089
1208
  }
1090
1209
  throw Error('Failed to generate pivot table: invalid report');
1091
1210
  }
1092
- export function generatePivotTableInMemory(pivot, data, dateRange, isComparison, rowLimit, compRange = undefined, dateBucket, uniqueValues) {
1093
- // If there is no rowField, aggregate on the valueField
1094
- if (!pivot.rowField) {
1095
- return valueFieldAggregation(data, pivot.valueField, pivot.aggregationType, isComparison);
1096
- }
1097
- // Handle edge-case for BigQuery objects.
1098
- data = fixBigQueryData(data);
1099
- if (!dateRange) {
1100
- if (isDateField(pivot.rowFieldType || '')) {
1101
- dateRange = getDateRange(dateRange, pivot.rowField, data);
1102
- }
1103
- }
1104
- if (!compRange ||
1105
- isNullUndefinedOrInvalidDate(compRange.start) ||
1106
- isNullUndefinedOrInvalidDate(compRange.end)) {
1107
- compRange = undefined;
1108
- }
1109
- const pivotRows = [];
1110
- const uniqueValuesMap = uniqueValuesToStringMap(uniqueValues || {});
1111
- const uniqueRows = (isDateField(pivot.rowFieldType || '')
1112
- ? getDateBuckets(dateRange, pivot.rowField, data, dateBucket)
1113
- : uniqueValuesMap[pivot.rowField] &&
1114
- uniqueValuesMap[pivot.rowField]?.[0] !== 'EXCEEDS_LIMIT'
1115
- ? uniqueValuesMap[pivot.rowField]
1116
- : [...new Set(data.map((item) => item[pivot.rowField || '']))])
1117
- .filter((col) => col !== null && col !== '')
1118
- .map((col) => isDateField(pivot.rowFieldType || '') ? castValueToDate(col) : col);
1119
- const rowDateRange = getDateRange(dateRange, pivot.rowField, data);
1120
- const compRowDateRange = getDateRange(compRange ?? dateRange, pivot.rowField, data);
1121
- // If columnField is not provided, we will not be using uniqueColumns
1122
- // @ts-ignore
1123
- const uniqueColumns = (pivot.columnField
1124
- ? isDateField(pivot.columnFieldType || '')
1125
- ? getDateBuckets(dateRange, pivot.columnField, data, dateBucket)
1126
- : uniqueValues && pivot.columnField
1127
- ? uniqueValuesMap[pivot.columnField]
1128
- : [...new Set(data.map((item) => item[pivot.columnField || '']))]
1129
- : [pivot.valueField]).filter((col) => col !== null && col !== '' && col !== undefined);
1130
- // Map from new dates to their corresponding prior dates
1131
- const COL_DATE_MAP = {};
1132
- const ROW_DATE_MAP = {};
1133
- // add in the comparison columns for all columns in the pivot
1134
- let compUniqueRows = [];
1135
- if (isComparison) {
1136
- if (pivot.columnField) {
1137
- const col = pivot.columnField;
1138
- const row = pivot.rowField;
1139
- const isDateCol = isDateField(pivot.columnFieldType || '');
1140
- const isDateRow = isDateField(pivot.rowFieldType || '');
1141
- data.forEach((item) => {
1142
- if (isDateCol) {
1143
- const key = getDateString(item[col], dateRange);
1144
- const value = getDateString(item[`comparison_${col}`], dateRange, dateBucket);
1145
- COL_DATE_MAP[key] = value;
1146
- }
1147
- if (isDateRow) {
1148
- const key = getDateString(item[row], dateRange);
1149
- const value = getDateString(item[`comparison_${row}`], dateRange, dateBucket);
1150
- ROW_DATE_MAP[key] = value;
1151
- }
1152
- });
1153
- }
1154
- else {
1155
- const primaryColumns = [...uniqueColumns];
1156
- for (const primaryKey of primaryColumns) {
1157
- uniqueColumns.push(`comparison_${primaryKey}`);
1158
- }
1159
- }
1160
- compUniqueRows = (isDateField(pivot.rowFieldType || '')
1161
- ? getCompDateBuckets(dateRange, compRange ?? dateRange, pivot.rowField, data)
1162
- : (uniqueValuesMap[pivot.rowField] ?? [
1163
- ...new Set(data.map((item) => item[pivot.rowField || ''])),
1164
- ])).filter((col) => col !== null && col !== '');
1165
- }
1166
- // Special corner case for count with only rowField pivot
1167
- if (!pivot.valueField &&
1168
- !pivot.columnField &&
1169
- pivot.aggregationType === 'count') {
1170
- uniqueColumns.push('count');
1171
- if (isComparison) {
1172
- uniqueColumns.push('comparison_count');
1173
- }
1174
- }
1175
- const rowsToGenerate = rowLimit && rowLimit <= uniqueRows.length
1176
- ? uniqueRows.slice(0, rowLimit)
1177
- : uniqueRows;
1178
- rowsToGenerate.forEach((rowValue, rowIndex) => {
1179
- const row = {
1180
- [pivot.rowField || '']: isDateField(pivot.rowFieldType || '')
1181
- ? getDateString(rowValue, dateRange, dateBucket)
1182
- : rowValue === null
1183
- ? 'Null'
1184
- : rowValue === false
1185
- ? 'False'
1186
- : rowValue === true
1187
- ? 'True'
1188
- : rowValue,
1189
- };
1190
- uniqueColumns.forEach((colValue, colIndex) => {
1191
- let comparisonFilteredData = [];
1192
- let filteredData = [];
1193
- let comparisonValue;
1194
- let value;
1195
- const nextRowValue = isDateField(pivot.rowFieldType || '')
1196
- ? // @ts-ignore
1197
- (uniqueRows[rowIndex + 1] ?? endOfDay(rowDateRange.end))
1198
- : null;
1199
- const compRowValue = compUniqueRows[rowIndex];
1200
- const compNextRowValue = isDateField(pivot.rowFieldType || '')
1201
- ? (compUniqueRows[rowIndex + 1] ?? endOfDay(compRowDateRange.end))
1202
- : null;
1203
- if (pivot.columnField) {
1204
- const columnDateRange = getDateRange(dateRange, pivot.columnField, data);
1205
- const nextColumnValue = isDateField(pivot.columnFieldType || '')
1206
- ? uniqueColumns[colIndex + 1] || endOfDay(columnDateRange.end)
1207
- : null;
1208
- // If columnField is provided, filter by both rowField and columnField
1209
- if (isDateField(pivot.columnFieldType || '') &&
1210
- isDateField(pivot.rowFieldType || '')) {
1211
- filteredData = data.filter((item) => {
1212
- return (isWithinInterval(getValidDate(item[pivot.rowField || '']) ?? 0, {
1213
- start: rowValue,
1214
- end: subMilliseconds(nextRowValue, 1),
1215
- }) &&
1216
- isWithinInterval(getValidDate(item[pivot.columnField || '']) ?? 0, {
1217
- start: colValue,
1218
- end: subMilliseconds(nextColumnValue, 1),
1219
- }));
1220
- });
1221
- if (isComparison) {
1222
- comparisonFilteredData = data.filter((item) => {
1223
- return (isWithinInterval(getValidDate(item[pivot.rowField || '']) ?? 0, {
1224
- start: rowValue,
1225
- end: subMilliseconds(nextRowValue, 1),
1226
- }) &&
1227
- isWithinInterval(getValidDate(item[pivot.columnField || '']) ?? 0, {
1228
- start: colValue,
1229
- end: subMilliseconds(nextColumnValue, 1),
1230
- }));
1231
- });
1232
- }
1233
- }
1234
- else if (isDateField(pivot.columnFieldType || '') &&
1235
- !isDateField(pivot.rowFieldType || '')) {
1236
- filteredData = data.filter((item) => {
1237
- return (item[pivot.rowField || ''] === rowValue &&
1238
- isWithinInterval(getValidDate(item[pivot.columnField || '']) ?? 0, {
1239
- start: colValue,
1240
- end: subMilliseconds(nextColumnValue, 1),
1241
- }));
1242
- });
1243
- if (isComparison) {
1244
- comparisonFilteredData = data.filter((item) => {
1245
- return (item[`comparison_${pivot.rowField}`] === rowValue &&
1246
- isWithinInterval(getValidDate(item[pivot.columnField || '']) ?? 0, {
1247
- start: colValue,
1248
- end: subMilliseconds(nextColumnValue, 1),
1249
- }));
1250
- });
1251
- }
1252
- }
1253
- else if (!isDateField(pivot.columnFieldType || '') &&
1254
- isDateField(pivot.rowFieldType || '')) {
1255
- filteredData = data.filter((item) => {
1256
- return (isWithinInterval(getValidDate(item[pivot.rowField || '']) ?? 0, {
1257
- start: rowValue,
1258
- end: subMilliseconds(nextRowValue, 1),
1259
- }) && item[pivot.columnField || ''] === colValue);
1260
- });
1261
- if (isComparison) {
1262
- comparisonFilteredData = data.filter((item) => {
1263
- return (isWithinInterval(getValidDate(item[pivot.rowField || '']) ?? 0, {
1264
- start: rowValue,
1265
- end: subMilliseconds(nextRowValue, 1),
1266
- }) &&
1267
- item[`comparison_${pivot.columnField}` || ''] === colValue);
1268
- });
1269
- }
1270
- }
1271
- else {
1272
- filteredData = data.filter((item) => {
1273
- return (item[pivot.rowField || ''] === rowValue &&
1274
- item[pivot.columnField || ''] === colValue);
1275
- });
1276
- if (isComparison) {
1277
- comparisonFilteredData = data.filter((item) => {
1278
- return (item[`comparison_${pivot.rowField}`] === rowValue &&
1279
- item[`comparison_${pivot.columnField}`] === colValue);
1280
- });
1281
- }
1282
- }
1283
- }
1284
- else {
1285
- // NOTE: For 1D columns, the comparisons are handled inside the colvalue
1286
- // so there is no need to filter for comparisonFilteredData here.
1287
- // If columnField is not provided, filter by rowField only
1288
- if (colValue.startsWith('comparison_')) {
1289
- filteredData = isDateField(pivot.rowFieldType || '')
1290
- ? data.filter((item) => {
1291
- return (compRowValue &&
1292
- isWithinInterval(getValidDate(item[`comparison_${pivot.rowField}`]) ?? 0, {
1293
- start: compRowValue,
1294
- end: subMilliseconds(compNextRowValue, 1),
1295
- }));
1296
- })
1297
- : data.filter((item) => item[`comparison_${pivot.rowField}`] === compRowValue);
1298
- }
1299
- else {
1300
- filteredData = isDateField(pivot.rowFieldType || '')
1301
- ? data.filter((item) => {
1302
- return isWithinInterval(getValidDate(item[pivot.rowField || '']) ?? 0, {
1303
- start: rowValue,
1304
- end: subMilliseconds(nextRowValue, 1),
1305
- });
1306
- })
1307
- : data.filter((item) => item[pivot.rowField || ''] === rowValue);
1308
- }
1309
- }
1310
- // Aggregation logic remains the same
1311
- const key = pivot.columnField && pivot.columnField.trim()
1312
- ? pivot.valueField
1313
- : colValue;
1314
- switch (pivot.aggregationType) {
1315
- case 'sum':
1316
- value = filteredData.reduce((sum, item) => sum + parseFloat(item[key] ?? 0), 0);
1317
- if (isComparison) {
1318
- comparisonValue = comparisonFilteredData.reduce((sum, item) => sum + parseFloat(item[key] ?? 0), 0);
1319
- }
1320
- break;
1321
- case 'count':
1322
- value = filteredData.length;
1323
- if (isComparison) {
1324
- comparisonValue = comparisonFilteredData.length;
1325
- }
1326
- break;
1327
- case 'average':
1328
- case 'avg':
1329
- value = filteredData.length
1330
- ? filteredData.reduce((sum, item) => sum + parseFloat(item[key] ?? 0), 0) / filteredData.length
1331
- : 0;
1332
- if (isComparison) {
1333
- comparisonValue = comparisonFilteredData.length
1334
- ? comparisonFilteredData.reduce((sum, item) => sum + parseFloat(item[key] ?? 0), 0) / comparisonFilteredData.length
1335
- : 0;
1336
- }
1337
- break;
1338
- case 'max':
1339
- value = filteredData.reduce((max, item) => Math.max(max, parseFloat(item[key] ?? -Infinity)), -Infinity);
1340
- if (isComparison) {
1341
- comparisonValue = comparisonFilteredData.reduce((max, item) => Math.max(max, parseFloat(item[key] ?? -Infinity)), -Infinity);
1342
- }
1343
- break;
1344
- case 'min':
1345
- value = filteredData.reduce((min, item) => Math.min(min, parseFloat(item[key] ?? Infinity)), Infinity);
1346
- if (isComparison) {
1347
- comparisonValue = comparisonFilteredData.reduce((min, item) => Math.min(min, parseFloat(item[key] ?? Infinity)), Infinity);
1348
- }
1349
- break;
1350
- // Implement other aggregation types as needed
1351
- default:
1352
- throw new Error('Unsupported aggregation type');
1353
- }
1354
- // Set the value on the row
1355
- // If columnField is not provided, colValue will be valueField
1356
- row[isDateField(pivot.columnFieldType || '')
1357
- ? getDateString(colValue, dateRange)
1358
- : colValue] =
1359
- pivot.aggregationType === 'count' ? value.toFixed(0) : value.toFixed(2);
1360
- if (isComparison && pivot.columnField) {
1361
- if (isDateField(pivot.columnFieldType || '')) {
1362
- row[`comparison_${getDateString(colValue, dateRange)}`] =
1363
- pivot.aggregationType === 'count'
1364
- ? comparisonValue?.toFixed(0)
1365
- : comparisonValue?.toFixed(2);
1366
- }
1367
- else {
1368
- row[`comparison_${colValue}`] =
1369
- pivot.aggregationType === 'count'
1370
- ? comparisonValue?.toFixed(0)
1371
- : comparisonValue?.toFixed(2);
1372
- }
1373
- }
1374
- });
1375
- if (pivot.aggregationType === 'max' || pivot.aggregationType === 'min') {
1376
- for (const [key, value] of Object.entries(row)) {
1377
- if (value === '-Infinity' || value === 'Infinity') {
1378
- row[key] = null;
1379
- }
1380
- }
1381
- }
1382
- pivotRows.push(row);
1383
- });
1384
- const columns = [
1385
- {
1386
- label: pivot.rowField === null
1387
- ? 'Null'
1388
- : snakeAndCamelCaseToTitleCase(pivot.rowField),
1389
- field: pivot.rowField,
1390
- },
1391
- ...uniqueColumns.map((column) => {
1392
- const columnName = isDateField(pivot.columnFieldType || '')
1393
- ? getDateString(column, dateRange, dateBucket)
1394
- : column;
1395
- return {
1396
- label: column === null
1397
- ? 'Null'
1398
- : column === false
1399
- ? 'False'
1400
- : snakeAndCamelCaseToTitleCase(columnName).replace('Comparison', 'comparison'),
1401
- field: columnName,
1402
- };
1403
- }),
1404
- ...(isComparison && pivot.columnField
1405
- ? uniqueColumns.map((column, index) => {
1406
- const columnName = isDateField(pivot.columnFieldType || '')
1407
- ? getDateString(column, dateRange, dateBucket)
1408
- : column;
1409
- return {
1410
- label: column === null
1411
- ? 'Null'
1412
- : column === false
1413
- ? 'False'
1414
- : pivot.aggregationType === 'count' &&
1415
- !pivot.columnField &&
1416
- index === 1
1417
- ? `comparison Count`
1418
- : isDateField(pivot.columnFieldType || '')
1419
- ? (COL_DATE_MAP[getDateString(column, dateRange, dateBucket)] ?? 'comparison')
1420
- : `comparison ${snakeAndCamelCaseToTitleCase(columnName)}`,
1421
- field: `comparison_${columnName}`,
1422
- };
1423
- })
1424
- : []),
1425
- ];
1426
- if (pivot.sort) {
1427
- pivotRows.sort((a, b) => {
1428
- if (pivot.sortField) {
1429
- const result = compareValues(a, b, pivot.sortField);
1430
- return pivot.sortDirection === 'ASC' ? result : -result;
1431
- }
1432
- else {
1433
- return b[pivot.sortField].localeCompare(a[pivot.sortField]);
1434
- }
1435
- });
1436
- }
1437
- return { rows: pivotRows, columns };
1438
- }
1439
- function valueFieldAggregation(data, valueField, aggregationType, isComparison) {
1440
- if (!data || data.length === 0) {
1441
- return {
1442
- rows: [],
1443
- columns: [
1444
- { label: snakeAndCamelCaseToTitleCase(valueField), field: valueField },
1445
- ],
1446
- };
1447
- }
1448
- let value = 0;
1449
- let comparisonValue = 0;
1450
- let count = 0;
1451
- switch (aggregationType) {
1452
- case 'sum':
1453
- value = data.reduce((sum, item) =>
1454
- // Big(sum)
1455
- // .add(Big(parseFloat(item[valueField] ?? '0')))
1456
- // .toNumber(),
1457
- sum + parseFloat(item[valueField] ?? '0'), 0);
1458
- if (isComparison) {
1459
- comparisonValue = data.reduce((sum, item) =>
1460
- // Big(sum)
1461
- // .add(Big(parseFloat(item[`comparison_${valueField}`] ?? '0')))
1462
- // .toNumber(),
1463
- sum + parseFloat(item[`comparison_${valueField}`] ?? '0'), 0);
1464
- }
1465
- break;
1466
- case 'count':
1467
- value = data.reduce((count, item) => count + (item[valueField] ? 1 : 0), 0);
1468
- if (isComparison) {
1469
- comparisonValue = data.reduce((count, item) => count + (item[`comparison_${valueField}`] ? 1 : 0), 0);
1470
- }
1471
- break;
1472
- case 'avg':
1473
- case 'average':
1474
- count = data.reduce((count, item) => count + (item[valueField] ? 1 : 0), 0);
1475
- value =
1476
- data.reduce((sum, item) =>
1477
- // Big(sum)
1478
- // .add(Big(parseFloat(item[valueField] ?? '0')))
1479
- // .toNumber(),
1480
- sum + parseFloat(item[valueField] ?? '0'), 0) / (count === 0 ? 1 : count);
1481
- if (isComparison) {
1482
- const comparisonCount = data.reduce((count, item) => count + (item[`comparison_${valueField}`] ? 1 : 0), 0);
1483
- comparisonValue =
1484
- data.reduce((sum, item) =>
1485
- // Big(sum)
1486
- // .add(Big(parseFloat(item[`comparison_${valueField}`] ?? '0')))
1487
- // .toNumber(),
1488
- sum + parseFloat(item[`comparison_${valueField}`] ?? '0'), 0) / (comparisonCount === 0 ? 1 : comparisonCount);
1489
- }
1490
- break;
1491
- case 'max':
1492
- value = data.reduce((max, item) => Math.max(max, parseFloat(item[valueField] ?? -Infinity)), -Infinity);
1493
- if (isComparison) {
1494
- comparisonValue = data.reduce((max, item) => Math.max(max, parseFloat(item[`comparison_${valueField}`] ?? -Infinity)), -Infinity);
1495
- }
1496
- break;
1497
- case 'min':
1498
- value = data.reduce((min, item) => Math.min(min, parseFloat(item[valueField] ?? Infinity)), Infinity);
1499
- if (isComparison) {
1500
- comparisonValue = data.reduce((min, item) => Math.min(min, parseFloat(item[`comparison_${valueField}`] ?? Infinity)), Infinity);
1501
- }
1502
- break;
1503
- // Implement other aggregation types as needed
1504
- default:
1505
- throw new Error('Unsupported aggregation type');
1506
- }
1507
- const row = {};
1508
- row[valueField] = value;
1509
- if (isComparison) {
1510
- row[`comparison_${valueField}`] = comparisonValue;
1511
- }
1512
- return {
1513
- rows: [row],
1514
- columns: [
1515
- { label: snakeAndCamelCaseToTitleCase(valueField), field: valueField },
1516
- ],
1517
- };
1518
- }