@lightdash/common 0.1973.0 → 0.1973.1

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.
@@ -1,3 +1,4 @@
1
+ import type { ReadyQueryResultsPage } from '../index';
1
2
  import { type ItemsMap } from '../types/field';
2
3
  import { type MetricQuery } from '../types/metricQuery';
3
4
  import { type PivotConfig, type PivotData } from '../types/pivot';
@@ -26,5 +27,17 @@ export declare const pivotResultsAsCsv: ({ pivotConfig, rows, itemMap, metricQue
26
27
  maxColumnLimit: number;
27
28
  undefinedCharacter?: string;
28
29
  }) => string[][];
30
+ /**
31
+ * Converts SQL-pivoted results to PivotData format
32
+ * This handles results that are already pivoted at the SQL level (e.g., payments_total_revenue_any_bank_transfer)
33
+ * and transforms them into the same PivotData structure as pivotQueryResults
34
+ */
35
+ export declare const convertSqlPivotedRowsToPivotData: ({ rows, pivotDetails, pivotConfig, getField, getFieldLabel, }: {
36
+ rows: ResultRow[];
37
+ pivotDetails: NonNullable<ReadyQueryResultsPage["pivotDetails"]>;
38
+ pivotConfig: Pick<PivotConfig, "rowTotals" | "columnTotals" | "metricsAsRows" | "hiddenMetricFieldIds" | "columnOrder">;
39
+ getField: FieldFunction;
40
+ getFieldLabel: FieldLabelFunction;
41
+ }) => PivotData;
29
42
  export {};
30
43
  //# sourceMappingURL=pivotQueryResults.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pivotQueryResults.d.ts","sourceRoot":"","sources":["../../../src/pivot/pivotQueryResults.ts"],"names":[],"mappings":"AAIA,OAAO,EAKH,KAAK,QAAQ,EAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAEH,KAAK,WAAW,EAChB,KAAK,SAAS,EAEjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,SAAS,EAAoB,MAAM,kBAAkB,CAAC;AAIpE,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;AAEvE,KAAK,kBAAkB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAElE,KAAK,qBAAqB,GAAG;IACzB,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,IAAI,CACb,WAAW,EACT,YAAY,GACZ,SAAS,GACT,mBAAmB,GACnB,mBAAmB,GACnB,kBAAkB,CACvB,CAAC;IACF,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,QAAQ,EAAE,aAAa,CAAC;IACxB,aAAa,EAAE,kBAAkB,CAAC;CACrC,CAAC;AA8UF,eAAO,MAAM,iBAAiB,4FAQ3B,qBAAqB,KAAG,SAma1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,4GAS3B;IACC,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,OAAO,EAAE,QAAQ,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC/B,eA8DA,CAAC"}
1
+ {"version":3,"file":"pivotQueryResults.d.ts","sourceRoot":"","sources":["../../../src/pivot/pivotQueryResults.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAEtD,OAAO,EAKH,KAAK,QAAQ,EAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAEH,KAAK,WAAW,EAChB,KAAK,SAAS,EAEjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,SAAS,EAAoB,MAAM,kBAAkB,CAAC;AAIpE,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;AAEvE,KAAK,kBAAkB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAElE,KAAK,qBAAqB,GAAG;IACzB,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,IAAI,CACb,WAAW,EACT,YAAY,GACZ,SAAS,GACT,mBAAmB,GACnB,mBAAmB,GACnB,kBAAkB,CACvB,CAAC;IACF,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,QAAQ,EAAE,aAAa,CAAC;IACxB,aAAa,EAAE,kBAAkB,CAAC;CACrC,CAAC;AAydF,eAAO,MAAM,iBAAiB,4FAQ3B,qBAAqB,KAAG,SAuV1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,4GAS3B;IACC,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,OAAO,EAAE,QAAQ,CAAC;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC/B,eA8DA,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,kEAM1C;IACC,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,YAAY,EAAE,WAAW,CAAC,qBAAqB,CAAC,cAAc,CAAC,CAAC,CAAC;IACjE,WAAW,EAAE,IAAI,CACb,WAAW,EACT,WAAW,GACX,cAAc,GACd,eAAe,GACf,sBAAsB,GACtB,aAAa,CAClB,CAAC;IACF,QAAQ,EAAE,aAAa,CAAC;IACxB,aAAa,EAAE,kBAAkB,CAAC;CACrC,KAAG,SAyhBH,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pivotResultsAsCsv = exports.pivotQueryResults = void 0;
3
+ exports.convertSqlPivotedRowsToPivotData = exports.pivotResultsAsCsv = exports.pivotQueryResults = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const isNumber_1 = tslib_1.__importDefault(require("lodash/isNumber"));
6
6
  const last_1 = tslib_1.__importDefault(require("lodash/last"));
@@ -246,6 +246,77 @@ const combinedRetrofit = (data, getField, getFieldLabel) => {
246
246
  data.retrofitData = { allCombinedData, pivotColumnInfo };
247
247
  return data;
248
248
  };
249
+ const getHeaderValueTypes = ({ metricsAsRows, pivotDimensionNames, }) => {
250
+ const headerDimensionValueTypes = pivotDimensionNames.map((fieldId) => ({
251
+ type: field_1.FieldType.DIMENSION,
252
+ fieldId,
253
+ }));
254
+ const headerMetricValueTypes = metricsAsRows
255
+ ? []
256
+ : [{ type: field_1.FieldType.METRIC }];
257
+ return [...headerDimensionValueTypes, ...headerMetricValueTypes];
258
+ };
259
+ const getColumnTotals = ({ summableMetricFieldIds, rowIndices, totalColumns, dataColumns, dataValues, hasIndex, pivotConfig, indexValues, }) => {
260
+ let columnTotalFields;
261
+ let columnTotals;
262
+ const N_DATA_COLUMNS = dataColumns;
263
+ if (pivotConfig.columnTotals && hasIndex) {
264
+ if (pivotConfig.metricsAsRows) {
265
+ const N_TOTAL_ROWS = summableMetricFieldIds.length;
266
+ const N_TOTAL_COLS = totalColumns;
267
+ columnTotalFields = create2DArray(N_TOTAL_ROWS, N_TOTAL_COLS);
268
+ columnTotals = create2DArray(N_TOTAL_ROWS, N_DATA_COLUMNS);
269
+ summableMetricFieldIds.forEach((fieldId, metricIndex) => {
270
+ columnTotalFields[metricIndex][N_TOTAL_COLS - 1] = {
271
+ fieldId,
272
+ };
273
+ });
274
+ columnTotals = columnTotals.map((row, rowIndex) => row.map((_, totalColIndex) => {
275
+ const totalColFieldId = columnTotalFields[rowIndex][N_TOTAL_COLS - 1]?.fieldId;
276
+ const valueColIndices = totalColFieldId
277
+ ? getAllIndicesForFieldId(rowIndices, totalColFieldId)
278
+ : [];
279
+ return dataValues
280
+ .filter((__, dataValueColIndex) => valueColIndices.includes(dataValueColIndex))
281
+ .reduce((acc, value) => acc + parseNumericValue(value[totalColIndex]), 0);
282
+ }));
283
+ }
284
+ else {
285
+ const N_TOTAL_COLS = indexValues[0].length;
286
+ const N_TOTAL_ROWS = 1;
287
+ columnTotalFields = create2DArray(N_TOTAL_ROWS, N_TOTAL_COLS);
288
+ columnTotals = create2DArray(N_TOTAL_ROWS, N_DATA_COLUMNS);
289
+ // set the last index cell as the "Total"
290
+ columnTotalFields[N_TOTAL_ROWS - 1][N_TOTAL_COLS - 1] = {
291
+ fieldId: undefined,
292
+ };
293
+ columnTotals = columnTotals.map((row, _totalRowIndex) => row.map((_col, colIndex) => dataValues
294
+ .map((dataRow) => dataRow[colIndex])
295
+ .reduce((acc, value) => acc + parseNumericValue(value), 0)));
296
+ }
297
+ }
298
+ return { columnTotalFields, columnTotals };
299
+ };
300
+ const getTitleFields = ({ hasHeader, hasIndex, headerValueTypes, indexValueTypes, }) => {
301
+ const titleFields = create2DArray(hasHeader ? headerValueTypes.length : 1, hasIndex ? indexValueTypes.length : 1);
302
+ headerValueTypes.forEach((headerValueType, headerIndex) => {
303
+ if (headerValueType.type === field_1.FieldType.DIMENSION) {
304
+ titleFields[headerIndex][hasIndex ? indexValueTypes.length - 1 : 0] = {
305
+ fieldId: headerValueType.fieldId,
306
+ direction: 'header',
307
+ };
308
+ }
309
+ });
310
+ indexValueTypes.forEach((indexValueType, indexIndex) => {
311
+ if (indexValueType.type === field_1.FieldType.DIMENSION) {
312
+ titleFields[hasHeader ? headerValueTypes.length - 1 : 0][indexIndex] = {
313
+ fieldId: indexValueType.fieldId,
314
+ direction: 'index',
315
+ };
316
+ }
317
+ });
318
+ return titleFields;
319
+ };
249
320
  const pivotQueryResults = ({ pivotConfig, metricQuery, rows, options, getField, getFieldLabel, groupedSubtotals, }) => {
250
321
  if (rows.length === 0) {
251
322
  throw new Error('Cannot pivot results with no rows');
@@ -264,15 +335,10 @@ const pivotQueryResults = ({ pivotConfig, metricQuery, rows, options, getField,
264
335
  const dimensions = [...metricQuery.dimensions];
265
336
  // Headers (column index)
266
337
  const headerDimensions = pivotConfig.pivotDimensions.filter((pivotDimension) => dimensions.includes(pivotDimension));
267
- const headerDimensionValueTypes = headerDimensions.map((d) => ({
268
- type: field_1.FieldType.DIMENSION,
269
- fieldId: d,
270
- }));
271
- const headerMetricValueTypes = pivotConfig.metricsAsRows ? [] : [{ type: field_1.FieldType.METRIC }];
272
- const headerValueTypes = [
273
- ...headerDimensionValueTypes,
274
- ...headerMetricValueTypes,
275
- ];
338
+ const headerValueTypes = getHeaderValueTypes({
339
+ metricsAsRows: pivotConfig.metricsAsRows,
340
+ pivotDimensionNames: headerDimensions,
341
+ });
276
342
  // Indices (row index)
277
343
  const indexDimensions = dimensions
278
344
  .filter((d) => !pivotConfig.pivotDimensions.includes(d))
@@ -444,59 +510,21 @@ const pivotQueryResults = ({ pivotConfig, metricQuery, rows, options, getField,
444
510
  }));
445
511
  }
446
512
  }
447
- let columnTotalFields;
448
- let columnTotals;
449
- if (pivotConfig.columnTotals && hasIndex) {
450
- if (pivotConfig.metricsAsRows) {
451
- const N_TOTAL_ROWS = summableMetricFieldIds.length;
452
- const N_TOTAL_COLS = indexValueTypes.length;
453
- columnTotalFields = create2DArray(N_TOTAL_ROWS, N_TOTAL_COLS);
454
- columnTotals = create2DArray(N_TOTAL_ROWS, N_DATA_COLUMNS);
455
- summableMetricFieldIds.forEach((fieldId, metricIndex) => {
456
- columnTotalFields[metricIndex][N_TOTAL_COLS - 1] = {
457
- fieldId,
458
- };
459
- });
460
- columnTotals = columnTotals.map((row, rowIndex) => row.map((_, totalColIndex) => {
461
- const totalColFieldId = columnTotalFields[rowIndex][N_TOTAL_COLS - 1]?.fieldId;
462
- const valueColIndices = totalColFieldId
463
- ? getAllIndicesForFieldId(rowIndices, totalColFieldId)
464
- : [];
465
- return dataValues
466
- .filter((__, dataValueColIndex) => valueColIndices.includes(dataValueColIndex))
467
- .reduce((acc, value) => acc + parseNumericValue(value[totalColIndex]), 0);
468
- }));
469
- }
470
- else {
471
- const N_TOTAL_COLS = indexValues[0].length;
472
- const N_TOTAL_ROWS = 1;
473
- columnTotalFields = create2DArray(N_TOTAL_ROWS, N_TOTAL_COLS);
474
- columnTotals = create2DArray(N_TOTAL_ROWS, N_DATA_COLUMNS);
475
- // set the last index cell as the "Total"
476
- columnTotalFields[N_TOTAL_ROWS - 1][N_TOTAL_COLS - 1] = {
477
- fieldId: undefined,
478
- };
479
- columnTotals = columnTotals.map((row, _totalRowIndex) => row.map((_col, colIndex) => dataValues
480
- .map((dataRow) => dataRow[colIndex])
481
- .reduce((acc, value) => acc + parseNumericValue(value), 0)));
482
- }
483
- }
484
- const titleFields = create2DArray(hasHeader ? headerValueTypes.length : 1, hasIndex ? indexValueTypes.length : 1);
485
- headerValueTypes.forEach((headerValueType, headerIndex) => {
486
- if (headerValueType.type === field_1.FieldType.DIMENSION) {
487
- titleFields[headerIndex][hasIndex ? indexValueTypes.length - 1 : 0] = {
488
- fieldId: headerValueType.fieldId,
489
- direction: 'header',
490
- };
491
- }
513
+ const { columnTotalFields, columnTotals } = getColumnTotals({
514
+ summableMetricFieldIds,
515
+ totalColumns: indexValueTypes.length,
516
+ dataColumns: N_DATA_COLUMNS,
517
+ dataValues,
518
+ rowIndices,
519
+ pivotConfig,
520
+ indexValues,
521
+ hasIndex,
492
522
  });
493
- indexValueTypes.forEach((indexValueType, indexIndex) => {
494
- if (indexValueType.type === field_1.FieldType.DIMENSION) {
495
- titleFields[hasHeader ? headerValueTypes.length - 1 : 0][indexIndex] = {
496
- fieldId: indexValueType.fieldId,
497
- direction: 'index',
498
- };
499
- }
523
+ const titleFields = getTitleFields({
524
+ hasIndex,
525
+ hasHeader,
526
+ headerValueTypes,
527
+ indexValueTypes,
500
528
  });
501
529
  const cellsCount = (indexValueTypes.length === 0 ? titleFields[0].length : 0) +
502
530
  indexValueTypes.length +
@@ -572,4 +600,421 @@ const pivotResultsAsCsv = ({ pivotConfig, rows, itemMap, metricQuery, customLabe
572
600
  return [...headers, ...pivotedRows];
573
601
  };
574
602
  exports.pivotResultsAsCsv = pivotResultsAsCsv;
603
+ /**
604
+ * Converts SQL-pivoted results to PivotData format
605
+ * This handles results that are already pivoted at the SQL level (e.g., payments_total_revenue_any_bank_transfer)
606
+ * and transforms them into the same PivotData structure as pivotQueryResults
607
+ */
608
+ const convertSqlPivotedRowsToPivotData = ({ rows, pivotDetails, pivotConfig, getField, getFieldLabel, }) => {
609
+ if (rows.length === 0) {
610
+ throw new Error('Cannot convert SQL pivoted results with no rows');
611
+ }
612
+ const hiddenMetricFieldIds = pivotConfig.hiddenMetricFieldIds || [];
613
+ // Extract information from pivot details metadata
614
+ let indexColumns;
615
+ if (pivotDetails.indexColumn) {
616
+ if (Array.isArray(pivotDetails.indexColumn)) {
617
+ indexColumns = pivotDetails.indexColumn.map((col) => col.reference);
618
+ }
619
+ else {
620
+ indexColumns = [pivotDetails.indexColumn.reference];
621
+ }
622
+ }
623
+ else {
624
+ indexColumns = [];
625
+ }
626
+ // Get unique base metrics from valuesColumns
627
+ const baseMetricsArray = Array.from(new Set(pivotDetails.valuesColumns.map((col) => col.referenceField)));
628
+ const headerValueTypes = getHeaderValueTypes({
629
+ metricsAsRows: pivotConfig.metricsAsRows,
630
+ pivotDimensionNames: (pivotDetails.groupByColumns ?? []).map(({ reference }) => reference),
631
+ });
632
+ const indexValueTypes = pivotConfig.metricsAsRows
633
+ ? [
634
+ ...indexColumns.map((col) => ({
635
+ type: field_1.FieldType.DIMENSION,
636
+ fieldId: col,
637
+ })),
638
+ { type: field_1.FieldType.METRIC },
639
+ ]
640
+ : indexColumns.map((col) => ({
641
+ type: field_1.FieldType.DIMENSION,
642
+ fieldId: col,
643
+ }));
644
+ // Build header values (pivot dimension values)
645
+ const headerValues = [];
646
+ pivotDetails.groupByColumns?.forEach(({ reference }, index) => {
647
+ headerValues.push([]);
648
+ let columns = pivotDetails.valuesColumns;
649
+ if (pivotConfig.metricsAsRows) {
650
+ // For metrics as rows, we only need unique combinations of pivot values, excluding per metric duplicates
651
+ columns = Array.from(new Map(columns.map((col) => [
652
+ col.pivotValues
653
+ .map(({ referenceField, value }) => `${referenceField}:${value}`)
654
+ .join('|'),
655
+ col,
656
+ ])).values());
657
+ }
658
+ columns.forEach(({ pivotValues, pivotColumnName }) => {
659
+ const pivotValue = pivotValues.find(({ referenceField }) => referenceField === reference);
660
+ if (pivotValue) {
661
+ const field = getField(pivotValue.referenceField);
662
+ const formattedValue = field
663
+ ? (0, formatting_1.formatItemValue)(field, pivotValue.value)
664
+ : String(pivotValue.value);
665
+ const allColumnsWithSamePivotValues = columns.filter((matchingColumn) => {
666
+ const referencesToMatch = (pivotDetails.groupByColumns || [])
667
+ .map((groupByColumn) => groupByColumn.reference)
668
+ .slice(0, index + 1);
669
+ // Match all pivot values
670
+ return referencesToMatch.every((referenceToMatch) => {
671
+ const pivotValueA = pivotValues.find(({ referenceField: referenceField3 }) => referenceField3 === referenceToMatch);
672
+ const pivotValueB = matchingColumn.pivotValues.find(({ referenceField: referenceField3 }) => referenceField3 === referenceToMatch);
673
+ return pivotValueA?.value === pivotValueB?.value;
674
+ });
675
+ });
676
+ const isFirstInGroup = allColumnsWithSamePivotValues[0].pivotColumnName ===
677
+ pivotColumnName;
678
+ headerValues[index].push({
679
+ type: 'value',
680
+ fieldId: reference,
681
+ value: {
682
+ raw: pivotValue.value,
683
+ formatted: formattedValue,
684
+ },
685
+ colSpan: isFirstInGroup
686
+ ? allColumnsWithSamePivotValues.length
687
+ : 0,
688
+ });
689
+ }
690
+ });
691
+ });
692
+ // Add metric labels for columns if not metrics as rows
693
+ if (!pivotConfig.metricsAsRows && baseMetricsArray.length > 0) {
694
+ headerValues.push(pivotDetails.valuesColumns.map(({ referenceField }) => ({
695
+ type: 'label',
696
+ fieldId: referenceField,
697
+ })));
698
+ }
699
+ const filteredHeaderValues = headerValues.filter((row) => row.length > 0);
700
+ // Build index values (row identifiers)
701
+ let indexValues;
702
+ if (pivotConfig.metricsAsRows) {
703
+ indexValues = rows.reduce((acc, row) => {
704
+ // multiply rows per metric
705
+ baseMetricsArray.forEach((metric) => {
706
+ acc.push([
707
+ ...indexColumns.map((col) => ({
708
+ type: 'value',
709
+ fieldId: col,
710
+ value: row[col].value,
711
+ colSpan: 1,
712
+ })),
713
+ {
714
+ type: 'label',
715
+ fieldId: metric,
716
+ },
717
+ ]);
718
+ });
719
+ return acc;
720
+ }, []);
721
+ }
722
+ else {
723
+ indexValues = rows.map((row) => indexColumns.map((col) => ({
724
+ type: 'value',
725
+ fieldId: col,
726
+ value: row[col].value,
727
+ colSpan: 1,
728
+ })));
729
+ }
730
+ // Build data values (the actual pivot data)
731
+ let dataValues;
732
+ const rowIndices = {};
733
+ let rowCount = 0;
734
+ // Get unique columns
735
+ const uniqueColumns = pivotConfig.metricsAsRows
736
+ ? Array.from(new Map(pivotDetails.valuesColumns.map((col) => [
737
+ col.pivotValues
738
+ .map(({ referenceField, value }) => `${referenceField}:${value}`)
739
+ .join('|'),
740
+ col,
741
+ ])).values())
742
+ : pivotDetails.valuesColumns;
743
+ if (pivotConfig.metricsAsRows) {
744
+ // multiply rows per metric
745
+ dataValues = rows.reduce((acc, row) => {
746
+ baseMetricsArray.forEach((metric) => {
747
+ const indexRowValues = [
748
+ ...indexColumns.map((fieldId) => String(row[fieldId].value.raw)),
749
+ metric,
750
+ ];
751
+ // Build row data for this metric using unique columns
752
+ // For each unique column combination, find the corresponding value for this metric
753
+ const rowData = uniqueColumns.map((uniqueCol) => {
754
+ // Find the actual column in sortedValuesColumns that matches this unique combination and metric
755
+ const matchingColumn = pivotDetails.valuesColumns.find((col) => col.referenceField === metric &&
756
+ col.pivotValues.every((pv) => uniqueCol.pivotValues.some((upv) => upv.referenceField ===
757
+ pv.referenceField &&
758
+ upv.value === pv.value)));
759
+ return matchingColumn && row[matchingColumn.pivotColumnName]
760
+ ? row[matchingColumn.pivotColumnName].value
761
+ : null;
762
+ });
763
+ acc.push(rowData);
764
+ setIndexByKey(rowIndices, indexRowValues, rowCount);
765
+ rowCount += 1;
766
+ });
767
+ return acc;
768
+ }, []);
769
+ }
770
+ else {
771
+ dataValues = rows.map((row, rowIndex) => {
772
+ const indexRowValues = indexColumns.map((fieldId) => String(row[fieldId].value.raw));
773
+ setIndexByKey(rowIndices, indexRowValues, rowIndex);
774
+ return pivotDetails.valuesColumns.map((valueCol) => row[valueCol.pivotColumnName]
775
+ ? row[valueCol.pivotColumnName].value
776
+ : null);
777
+ });
778
+ rowCount = rows.length;
779
+ }
780
+ const fullPivotConfig = {
781
+ pivotDimensions: pivotDetails.groupByColumns?.map((col) => col.reference) || [],
782
+ metricsAsRows: pivotConfig.metricsAsRows || false,
783
+ columnOrder: pivotConfig.columnOrder,
784
+ hiddenMetricFieldIds: pivotConfig.hiddenMetricFieldIds || [],
785
+ columnTotals: pivotConfig.columnTotals,
786
+ rowTotals: pivotConfig.rowTotals,
787
+ };
788
+ // Compute row totals if requested
789
+ let rowTotalFields;
790
+ let rowTotals;
791
+ const hasHeader = headerValueTypes.length > 0;
792
+ const hasIndex = indexValueTypes.length > 0;
793
+ const N_DATA_ROWS = hasIndex ? dataValues.length : 1;
794
+ const N_DATA_COLUMNS = hasHeader ? uniqueColumns.length : 1;
795
+ // Build column indices using unique columns
796
+ const columnIndices = uniqueColumns.reduce((acc, valuesColumn, index) => {
797
+ const pivotValues = (pivotDetails.groupByColumns || []).map((col) => valuesColumn.pivotValues.find(({ referenceField }) => referenceField === col.reference)?.value);
798
+ // Create nested structure based on pivotValues
799
+ let current = acc;
800
+ for (let i = 0; i < pivotValues.length; i += 1) {
801
+ const pivotValue = String(pivotValues[i]);
802
+ if (!Object.prototype.hasOwnProperty.call(current, pivotValue)) {
803
+ Object.defineProperty(current, pivotValue, {
804
+ value: {},
805
+ writable: true,
806
+ enumerable: true,
807
+ configurable: true,
808
+ });
809
+ }
810
+ current = current[pivotValue];
811
+ }
812
+ // For metricsAsRows, don't include metric in column key
813
+ if (!pivotConfig.metricsAsRows) {
814
+ Object.defineProperty(current, valuesColumn.referenceField, {
815
+ value: index,
816
+ writable: true,
817
+ enumerable: true,
818
+ configurable: true,
819
+ });
820
+ }
821
+ else {
822
+ // Use a generic key since metrics are in rows, not columns
823
+ Object.defineProperty(current, 'value', {
824
+ value: index,
825
+ writable: true,
826
+ enumerable: true,
827
+ configurable: true,
828
+ });
829
+ }
830
+ return acc;
831
+ }, {});
832
+ const summableMetricFieldIds = baseMetricsArray.filter((metricId) => {
833
+ const field = getField(metricId);
834
+ // Skip if field is not found or is a dimension or is hidden
835
+ if (!field || (0, field_1.isDimension)(field))
836
+ return false;
837
+ if (hiddenMetricFieldIds.includes(metricId))
838
+ return false;
839
+ return (0, field_1.isSummable)(field);
840
+ });
841
+ if (fullPivotConfig.rowTotals && hasHeader) {
842
+ if (fullPivotConfig.metricsAsRows) {
843
+ const N_TOTAL_COLS = 1;
844
+ const N_TOTAL_ROWS = headerValues.length;
845
+ rowTotalFields = create2DArray(N_TOTAL_ROWS, N_TOTAL_COLS);
846
+ rowTotals = create2DArray(N_DATA_ROWS, N_TOTAL_COLS);
847
+ // set the last header cell as the "Total"
848
+ rowTotalFields[N_TOTAL_ROWS - 1][N_TOTAL_COLS - 1] = {
849
+ fieldId: undefined,
850
+ };
851
+ rowTotals = rowTotals.map((row, rowIndex) => row.map(() => dataValues[rowIndex].reduce((acc, value) => acc + parseNumericValue(value), 0)));
852
+ }
853
+ else {
854
+ const N_TOTAL_COLS = summableMetricFieldIds.length;
855
+ const N_TOTAL_ROWS = headerValues.length;
856
+ rowTotalFields = create2DArray(N_TOTAL_ROWS, N_TOTAL_COLS);
857
+ rowTotals = create2DArray(rows.length, N_TOTAL_COLS);
858
+ // Set the last header cell as the "Total"
859
+ summableMetricFieldIds.forEach((fieldId, metricIndex) => {
860
+ rowTotalFields[N_TOTAL_ROWS - 1][metricIndex] = {
861
+ fieldId,
862
+ };
863
+ });
864
+ rowTotals = rowTotals.map((row, rowIndex) => row.map((_, totalColIndex) => {
865
+ const totalColFieldId = rowTotalFields[N_TOTAL_ROWS - 1][totalColIndex]
866
+ ?.fieldId;
867
+ const valueColIndices = totalColFieldId
868
+ ? getAllIndicesForFieldId(columnIndices, totalColFieldId)
869
+ : [];
870
+ return dataValues[rowIndex]
871
+ .filter((__, dataValueColIndex) => valueColIndices.includes(dataValueColIndex))
872
+ .reduce((acc, value) => acc + parseNumericValue(value), 0);
873
+ }));
874
+ }
875
+ }
876
+ const { columnTotalFields, columnTotals } = getColumnTotals({
877
+ summableMetricFieldIds,
878
+ totalColumns: indexValueTypes.length,
879
+ dataColumns: N_DATA_COLUMNS,
880
+ dataValues,
881
+ rowIndices,
882
+ pivotConfig,
883
+ indexValues,
884
+ hasIndex,
885
+ });
886
+ // Build retrofit data for backwards compatibility
887
+ const allCombinedData = rows
888
+ .map((row) => {
889
+ const combinedRow = {};
890
+ // Add index columns
891
+ indexColumns.forEach((col) => {
892
+ combinedRow[col] = row[col];
893
+ });
894
+ if (pivotConfig.metricsAsRows) {
895
+ // Add metric label with correct index (after all index columns)
896
+ const labelIndex = indexColumns.length;
897
+ const metricLabel = getFieldLabel(baseMetricsArray[0]) || baseMetricsArray[0];
898
+ combinedRow[`label-${labelIndex}`] = {
899
+ value: {
900
+ raw: metricLabel,
901
+ formatted: metricLabel,
902
+ },
903
+ };
904
+ }
905
+ // Add data columns with generated field IDs using metadata
906
+ pivotDetails.valuesColumns.forEach((valueCol, colIndex) => {
907
+ const pivotDimensions = (pivotDetails.groupByColumns || []).map((col) => col.reference);
908
+ const fieldId = pivotConfig.metricsAsRows
909
+ ? `${pivotDimensions.join('__')}__${colIndex}`
910
+ : `${pivotDimensions.join('__')}__${valueCol.referenceField}__${colIndex}`;
911
+ if (row[valueCol.pivotColumnName]) {
912
+ combinedRow[fieldId] = row[valueCol.pivotColumnName];
913
+ }
914
+ });
915
+ return combinedRow;
916
+ })
917
+ .map((combinedRow, rowIndex) => {
918
+ // Add row totals if enabled
919
+ if (fullPivotConfig.rowTotals && rowTotals) {
920
+ const rowTotalValue = rowTotals[rowIndex]?.[0];
921
+ if (rowTotalValue !== undefined) {
922
+ const field = getField(baseMetricsArray[0]);
923
+ const formattedValue = field
924
+ ? (0, formatting_1.formatItemValue)(field, rowTotalValue)
925
+ : String(rowTotalValue);
926
+ return {
927
+ ...combinedRow,
928
+ 'row-total-0': {
929
+ value: {
930
+ raw: rowTotalValue,
931
+ formatted: formattedValue,
932
+ },
933
+ },
934
+ };
935
+ }
936
+ }
937
+ return combinedRow;
938
+ });
939
+ const pivotColumnInfo = [
940
+ ...indexColumns.map((col) => ({
941
+ fieldId: col,
942
+ baseId: undefined,
943
+ underlyingId: undefined,
944
+ columnType: 'indexValue',
945
+ })),
946
+ ...(pivotConfig.metricsAsRows
947
+ ? [
948
+ {
949
+ fieldId: `label-${indexColumns.length}`,
950
+ baseId: undefined,
951
+ underlyingId: undefined,
952
+ columnType: 'label',
953
+ },
954
+ ]
955
+ : []),
956
+ ...pivotDetails.valuesColumns.map((valueCol, colIndex) => {
957
+ const pivotDimensions = (pivotDetails.groupByColumns || []).map((col) => col.reference);
958
+ const fieldId = pivotConfig.metricsAsRows
959
+ ? `${pivotDimensions.join('__')}__${colIndex}`
960
+ : `${pivotDimensions.join('__')}__${valueCol.referenceField}__${colIndex}`;
961
+ return {
962
+ fieldId,
963
+ baseId: pivotConfig.metricsAsRows
964
+ ? pivotDimensions[0]
965
+ : valueCol.referenceField,
966
+ underlyingId: undefined,
967
+ columnType: undefined,
968
+ };
969
+ }),
970
+ ...(fullPivotConfig.rowTotals
971
+ ? [
972
+ {
973
+ fieldId: 'row-total-0',
974
+ baseId: 'row-total-0',
975
+ underlyingId: undefined,
976
+ columnType: 'rowTotal',
977
+ },
978
+ ]
979
+ : []),
980
+ ];
981
+ const titleFields = getTitleFields({
982
+ hasIndex,
983
+ hasHeader,
984
+ headerValueTypes,
985
+ indexValueTypes,
986
+ });
987
+ const pivotData = {
988
+ titleFields,
989
+ headerValueTypes,
990
+ headerValues: filteredHeaderValues,
991
+ indexValueTypes,
992
+ indexValues,
993
+ dataColumnCount: N_DATA_COLUMNS,
994
+ dataValues,
995
+ rowTotalFields,
996
+ columnTotalFields,
997
+ rowTotals,
998
+ columnTotals,
999
+ cellsCount: pivotConfig.metricsAsRows
1000
+ ? indexColumns.length +
1001
+ 1 + // label column
1002
+ uniqueColumns.length +
1003
+ (rowTotals ? rowTotals[0].length : 0)
1004
+ : indexColumns.length +
1005
+ uniqueColumns.length +
1006
+ (rowTotals ? rowTotals[0].length : 0),
1007
+ rowsCount: pivotConfig.metricsAsRows
1008
+ ? rows.length * baseMetricsArray.length
1009
+ : rows.length,
1010
+ pivotConfig: fullPivotConfig,
1011
+ retrofitData: {
1012
+ allCombinedData,
1013
+ pivotColumnInfo,
1014
+ },
1015
+ groupedSubtotals: undefined,
1016
+ };
1017
+ return combinedRetrofit(pivotData, getField, getFieldLabel);
1018
+ };
1019
+ exports.convertSqlPivotedRowsToPivotData = convertSqlPivotedRowsToPivotData;
575
1020
  //# sourceMappingURL=pivotQueryResults.js.map