@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.
- package/dist/cjs/pivot/pivotQueryResults.d.ts +13 -0
- package/dist/cjs/pivot/pivotQueryResults.d.ts.map +1 -1
- package/dist/cjs/pivot/pivotQueryResults.js +507 -62
- package/dist/cjs/pivot/pivotQueryResults.js.map +1 -1
- package/dist/cjs/pivot/pivotQueryResults.mock.d.ts +14 -0
- package/dist/cjs/pivot/pivotQueryResults.mock.d.ts.map +1 -1
- package/dist/cjs/pivot/pivotQueryResults.mock.js +2343 -1
- package/dist/cjs/pivot/pivotQueryResults.mock.js.map +1 -1
- package/dist/cjs/pivot/pivotQueryResults.test.js +391 -0
- package/dist/cjs/pivot/pivotQueryResults.test.js.map +1 -1
- package/dist/esm/pivot/pivotQueryResults.d.ts +13 -0
- package/dist/esm/pivot/pivotQueryResults.d.ts.map +1 -1
- package/dist/esm/pivot/pivotQueryResults.js +505 -61
- package/dist/esm/pivot/pivotQueryResults.js.map +1 -1
- package/dist/esm/pivot/pivotQueryResults.mock.d.ts +14 -0
- package/dist/esm/pivot/pivotQueryResults.mock.d.ts.map +1 -1
- package/dist/esm/pivot/pivotQueryResults.mock.js +2341 -0
- package/dist/esm/pivot/pivotQueryResults.mock.js.map +1 -1
- package/dist/esm/pivot/pivotQueryResults.test.js +393 -2
- package/dist/esm/pivot/pivotQueryResults.test.js.map +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/pivot/pivotQueryResults.d.ts +13 -0
- package/dist/types/pivot/pivotQueryResults.d.ts.map +1 -1
- package/dist/types/pivot/pivotQueryResults.mock.d.ts +14 -0
- package/dist/types/pivot/pivotQueryResults.mock.d.ts.map +1 -1
- package/package.json +1 -1
@@ -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":"
|
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
|
268
|
-
|
269
|
-
|
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
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
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
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
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
|