@lightdash/common 0.1369.1 → 0.1369.2

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/index.d.ts CHANGED
@@ -49,6 +49,7 @@ export * from './compiler/exploreCompiler';
49
49
  export * from './compiler/filtersCompiler';
50
50
  export * from './compiler/translator';
51
51
  export * from './dbt/validation';
52
+ export * from './pivotTable/pivotQueryResults';
52
53
  export { default as lightdashDbtYamlSchema } from './schemas/json/lightdash-dbt-2.0.json';
53
54
  export * from './templating/template';
54
55
  export * from './types/analytics';
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ tslib_1.__exportStar(require("./compiler/exploreCompiler"), exports);
19
19
  tslib_1.__exportStar(require("./compiler/filtersCompiler"), exports);
20
20
  tslib_1.__exportStar(require("./compiler/translator"), exports);
21
21
  tslib_1.__exportStar(require("./dbt/validation"), exports);
22
+ tslib_1.__exportStar(require("./pivotTable/pivotQueryResults"), exports);
22
23
  var lightdash_dbt_2_0_json_1 = require("./schemas/json/lightdash-dbt-2.0.json");
23
24
  Object.defineProperty(exports, "lightdashDbtYamlSchema", { enumerable: true, get: function () { return tslib_1.__importDefault(lightdash_dbt_2_0_json_1).default; } });
24
25
  tslib_1.__exportStar(require("./templating/template"), exports);
@@ -0,0 +1,19 @@
1
+ import { type ItemsMap } from '../types/field';
2
+ import { type MetricQuery } from '../types/metricQuery';
3
+ import { type PivotConfig, type PivotData } from '../types/pivot';
4
+ import { type ResultRow } from '../types/results';
5
+ type FieldFunction = (fieldId: string) => ItemsMap[string] | undefined;
6
+ type FieldLabelFunction = (fieldId: string) => string | undefined;
7
+ type PivotQueryResultsArgs = {
8
+ pivotConfig: PivotConfig;
9
+ metricQuery: Pick<MetricQuery, 'dimensions' | 'metrics' | 'tableCalculations' | 'additionalMetrics' | 'customDimensions'>;
10
+ rows: ResultRow[];
11
+ options: {
12
+ maxColumns: number;
13
+ };
14
+ getField: FieldFunction;
15
+ getFieldLabel: FieldLabelFunction;
16
+ };
17
+ export declare const pivotQueryResults: ({ pivotConfig, metricQuery, rows, options, getField, getFieldLabel, }: PivotQueryResultsArgs) => PivotData;
18
+ export declare const pivotResultsAsCsv: (pivotConfig: PivotConfig, rows: ResultRow[], itemMap: ItemsMap, metricQuery: MetricQuery, customLabels: Record<string, string> | undefined, onlyRaw: boolean, maxColumnLimit: number) => string[][];
19
+ export {};
@@ -0,0 +1,544 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pivotResultsAsCsv = exports.pivotQueryResults = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const isNumber_1 = tslib_1.__importDefault(require("lodash/isNumber"));
6
+ const last_1 = tslib_1.__importDefault(require("lodash/last"));
7
+ const field_1 = require("../types/field");
8
+ const formatting_1 = require("../utils/formatting");
9
+ const isRecursiveRecord = (value) => typeof value === 'object' && value !== null;
10
+ const create2DArray = (rows, columns, value = null) => Array.from({ length: rows }, () => Array.from({ length: columns }, () => value));
11
+ const parseNumericValue = (value) => {
12
+ if (value === null)
13
+ return 0;
14
+ const parsedVal = Number(value.raw);
15
+ return Number.isNaN(parsedVal) ? 0 : parsedVal;
16
+ };
17
+ const setIndexByKey = (obj, keys, value) => {
18
+ if (keys.length === 0) {
19
+ return false;
20
+ }
21
+ const [key, ...rest] = keys;
22
+ if (rest.length === 0) {
23
+ if (obj[key] === undefined) {
24
+ // eslint-disable-next-line no-param-reassign
25
+ obj[key] = value;
26
+ return true;
27
+ }
28
+ return false;
29
+ }
30
+ if (obj[key] === undefined) {
31
+ // eslint-disable-next-line no-param-reassign
32
+ obj[key] = {};
33
+ }
34
+ const nextObject = obj[key];
35
+ if (!isRecursiveRecord(nextObject)) {
36
+ throw new Error('Cannot set key on non-object');
37
+ }
38
+ return setIndexByKey(nextObject, rest, value);
39
+ };
40
+ const getIndexByKey = (obj, keys) => {
41
+ if (keys.length === 0) {
42
+ throw new Error('Cannot get key from empty keys array');
43
+ }
44
+ const [key, ...rest] = keys;
45
+ if (rest.length === 0) {
46
+ const value = obj[key];
47
+ if (typeof value !== 'number') {
48
+ throw new Error('Expected a number');
49
+ }
50
+ else {
51
+ return value;
52
+ }
53
+ }
54
+ else {
55
+ const nextObj = obj[key];
56
+ if (isRecursiveRecord(nextObj)) {
57
+ return getIndexByKey(nextObj, rest);
58
+ }
59
+ throw new Error('Expected a RecursiveRecord object');
60
+ }
61
+ };
62
+ const getAllIndicesForFieldId = (obj, fieldId) => {
63
+ const entries = Object.entries(obj);
64
+ return entries.reduce((acc, [key, value]) => {
65
+ if (key === fieldId && (0, isNumber_1.default)(value)) {
66
+ return [...acc, value];
67
+ }
68
+ if (isRecursiveRecord(value)) {
69
+ return [...acc, ...getAllIndicesForFieldId(value, fieldId)];
70
+ }
71
+ return acc;
72
+ }, []);
73
+ };
74
+ const getAllIndices = (obj) => Object.values(obj).reduce((acc, value) => {
75
+ if ((0, isNumber_1.default)(value)) {
76
+ return [...acc, value];
77
+ }
78
+ return [...acc, ...getAllIndices(value)];
79
+ }, []);
80
+ const getAllIndicesByKey = (obj, keys) => {
81
+ const [key, ...rest] = keys;
82
+ if (rest.length === 0) {
83
+ const value = obj[key];
84
+ if ((0, isNumber_1.default)(value)) {
85
+ return [value];
86
+ }
87
+ return getAllIndices(value);
88
+ }
89
+ const nextObj = obj[key];
90
+ if (isRecursiveRecord(nextObj)) {
91
+ return getAllIndicesByKey(nextObj, rest);
92
+ }
93
+ throw new Error('Expected a RecursiveRecord object');
94
+ };
95
+ const getColSpanByKey = (currentColumnPosition, obj, keys) => {
96
+ const allIndices = getAllIndicesByKey(obj, keys).sort((a, b) => a - b);
97
+ if (allIndices.length === 0) {
98
+ throw new Error('Cannot get span from empty indices array');
99
+ }
100
+ const currentColumnPositionIndex = allIndices.indexOf(currentColumnPosition);
101
+ const previousColumnPosition = allIndices[currentColumnPositionIndex - 1];
102
+ if (currentColumnPositionIndex < 0) {
103
+ throw new Error('Cannot get span for index that does not exist in indices array');
104
+ }
105
+ const isFirstColInSpan = !(0, isNumber_1.default)(previousColumnPosition) ||
106
+ previousColumnPosition !== currentColumnPosition - 1;
107
+ if (!isFirstColInSpan) {
108
+ return 0;
109
+ }
110
+ return allIndices
111
+ .slice(currentColumnPositionIndex)
112
+ .reduce((acc, curr, i) => {
113
+ if (curr === currentColumnPosition + i) {
114
+ return acc + 1;
115
+ }
116
+ return acc;
117
+ }, 0);
118
+ };
119
+ const combinedRetrofit = (data, getField, getFieldLabel) => {
120
+ const indexValues = data.indexValues.length ? data.indexValues : [[]];
121
+ const baseIdInfo = (0, last_1.default)(data.headerValues);
122
+ const uniqueIdsForDataValueColumns = Array(data.headerValues[0].length);
123
+ data.headerValues.forEach((headerRow) => {
124
+ headerRow.forEach((headerColValue, colIndex) => {
125
+ uniqueIdsForDataValueColumns[colIndex] = `${(uniqueIdsForDataValueColumns[colIndex] ?? '') +
126
+ headerColValue.fieldId}__`;
127
+ });
128
+ });
129
+ const getMetricAsRowTotalValueFromAxis = (total, rowIndex) => {
130
+ const value = (0, last_1.default)(data.indexValues[rowIndex]);
131
+ if (!value || !value.fieldId)
132
+ throw new Error('Invalid pivot data');
133
+ const item = getField(value.fieldId);
134
+ if (!(0, field_1.isSummable)(item)) {
135
+ return null;
136
+ }
137
+ const formattedValue = (0, formatting_1.formatItemValue)(item, total);
138
+ return {
139
+ raw: total,
140
+ formatted: formattedValue,
141
+ };
142
+ };
143
+ const getRowTotalValueFromAxis = (field, total) => {
144
+ if (!field || !field.fieldId)
145
+ throw new Error('Invalid pivot data');
146
+ const item = getField(field.fieldId);
147
+ const formattedValue = (0, formatting_1.formatItemValue)(item, total);
148
+ return {
149
+ raw: total,
150
+ formatted: formattedValue,
151
+ };
152
+ };
153
+ let pivotColumnInfo = [];
154
+ const allCombinedData = indexValues.map((row, rowIndex) => {
155
+ const newRow = row.map((cell, colIndex) => {
156
+ if (cell.type === 'label') {
157
+ const cellValue = getFieldLabel(cell.fieldId);
158
+ return {
159
+ ...cell,
160
+ fieldId: `label-${colIndex}`,
161
+ value: {
162
+ raw: cellValue,
163
+ formatted: cellValue,
164
+ },
165
+ columnType: 'label',
166
+ };
167
+ }
168
+ return {
169
+ ...cell,
170
+ columnType: 'indexValue',
171
+ };
172
+ });
173
+ const remappedDataValues = data.dataValues[rowIndex].map((dataValue, colIndex) => {
174
+ const baseIdInfoForCol = baseIdInfo
175
+ ? baseIdInfo[colIndex]
176
+ : undefined;
177
+ const baseId = baseIdInfoForCol?.fieldId;
178
+ const id = uniqueIdsForDataValueColumns[colIndex] + colIndex;
179
+ return {
180
+ baseId,
181
+ fieldId: id,
182
+ value: dataValue || {},
183
+ };
184
+ });
185
+ const remappedRowTotals = data.rowTotals?.[rowIndex]?.map((total, colIndex) => {
186
+ const baseId = `row-total-${colIndex}`;
187
+ const id = baseId;
188
+ const underlyingData = (0, last_1.default)(data.rowTotalFields)?.[colIndex];
189
+ const value = data.pivotConfig.metricsAsRows
190
+ ? getMetricAsRowTotalValueFromAxis(total, rowIndex)
191
+ : getRowTotalValueFromAxis(underlyingData, total);
192
+ const underlyingId = data.pivotConfig.metricsAsRows
193
+ ? undefined
194
+ : underlyingData?.fieldId;
195
+ return {
196
+ baseId,
197
+ fieldId: id,
198
+ underlyingId,
199
+ value,
200
+ columnType: 'rowTotal',
201
+ };
202
+ });
203
+ const entireRow = [
204
+ ...newRow,
205
+ ...remappedDataValues,
206
+ ...(remappedRowTotals || []),
207
+ ];
208
+ if (rowIndex === 0) {
209
+ pivotColumnInfo = entireRow.map((cell) => ({
210
+ fieldId: cell.fieldId,
211
+ baseId: 'baseId' in cell ? cell.baseId : undefined,
212
+ underlyingId: 'underlyingId' in cell ? cell.underlyingId : undefined,
213
+ columnType: 'columnType' in cell ? cell.columnType : undefined,
214
+ }));
215
+ }
216
+ const altRow = {};
217
+ entireRow.forEach((cell) => {
218
+ const val = cell.value;
219
+ if (val && 'formatted' in val && val.formatted !== undefined) {
220
+ altRow[cell.fieldId] = {
221
+ value: {
222
+ raw: val.raw,
223
+ formatted: val.formatted,
224
+ },
225
+ };
226
+ }
227
+ });
228
+ return altRow;
229
+ });
230
+ // eslint-disable-next-line no-param-reassign
231
+ data.retrofitData = { allCombinedData, pivotColumnInfo };
232
+ return data;
233
+ };
234
+ const pivotQueryResults = ({ pivotConfig, metricQuery, rows, options, getField, getFieldLabel, }) => {
235
+ if (rows.length === 0) {
236
+ throw new Error('Cannot pivot results with no rows');
237
+ }
238
+ const hiddenMetricFieldIds = pivotConfig.hiddenMetricFieldIds || [];
239
+ const summableMetricFieldIds = pivotConfig.summableMetricFieldIds || [];
240
+ const columnOrder = (pivotConfig.columnOrder || []).filter((id) => !hiddenMetricFieldIds.includes(id));
241
+ const dimensions = [...metricQuery.dimensions];
242
+ // Headers (column index)
243
+ const headerDimensions = pivotConfig.pivotDimensions.filter((pivotDimension) => dimensions.includes(pivotDimension));
244
+ const headerDimensionValueTypes = headerDimensions.map((d) => ({
245
+ type: field_1.FieldType.DIMENSION,
246
+ fieldId: d,
247
+ }));
248
+ const headerMetricValueTypes = pivotConfig.metricsAsRows ? [] : [{ type: field_1.FieldType.METRIC }];
249
+ const headerValueTypes = [
250
+ ...headerDimensionValueTypes,
251
+ ...headerMetricValueTypes,
252
+ ];
253
+ // Indices (row index)
254
+ const indexDimensions = dimensions
255
+ .filter((d) => !pivotConfig.pivotDimensions.includes(d))
256
+ .slice()
257
+ .sort((a, b) => columnOrder.indexOf(a) - columnOrder.indexOf(b));
258
+ const indexDimensionValueTypes = indexDimensions.map((d) => ({
259
+ type: field_1.FieldType.DIMENSION,
260
+ fieldId: d,
261
+ }));
262
+ const indexMetricValueTypes = pivotConfig.metricsAsRows ? [{ type: field_1.FieldType.METRIC }] : [];
263
+ const indexValueTypes = [
264
+ ...indexDimensionValueTypes,
265
+ ...indexMetricValueTypes,
266
+ ];
267
+ // Metrics
268
+ const metrics = [
269
+ ...metricQuery.metrics,
270
+ ...metricQuery.tableCalculations.map((tc) => tc.name),
271
+ ]
272
+ .filter((m) => !hiddenMetricFieldIds.includes(m))
273
+ .sort((a, b) => columnOrder.indexOf(a) - columnOrder.indexOf(b))
274
+ .map((id) => ({ fieldId: id }));
275
+ if (metrics.length === 0) {
276
+ throw new Error('Cannot pivot results with no metrics');
277
+ }
278
+ const N_ROWS = rows.length;
279
+ // For every row in the results, compute the index and header values to determine the shape of the result set
280
+ const indexValues = [];
281
+ const headerValuesT = [];
282
+ const rowIndices = {};
283
+ const columnIndices = {};
284
+ let rowCount = 0;
285
+ let columnCount = 0;
286
+ for (let nRow = 0; nRow < N_ROWS; nRow += 1) {
287
+ const row = rows[nRow];
288
+ for (let nMetric = 0; nMetric < metrics.length; nMetric += 1) {
289
+ const metric = metrics[nMetric];
290
+ const indexRowValues = indexDimensions
291
+ .map((fieldId) => ({
292
+ type: 'value',
293
+ fieldId,
294
+ value: row[fieldId].value,
295
+ colSpan: 1,
296
+ }))
297
+ .concat(pivotConfig.metricsAsRows
298
+ ? [
299
+ {
300
+ type: 'label',
301
+ fieldId: metric.fieldId,
302
+ },
303
+ ]
304
+ : []);
305
+ const headerRowValues = headerDimensions
306
+ .map((fieldId) => ({
307
+ type: 'value',
308
+ fieldId,
309
+ value: row[fieldId].value,
310
+ colSpan: 1,
311
+ }))
312
+ .concat(pivotConfig.metricsAsRows
313
+ ? []
314
+ : [
315
+ {
316
+ type: 'label',
317
+ fieldId: metric.fieldId,
318
+ },
319
+ ]);
320
+ // Write the index values
321
+ if (setIndexByKey(rowIndices, indexRowValues.map((l) => l.type === 'value' ? String(l.value?.raw) : l.fieldId), rowCount)) {
322
+ rowCount += 1;
323
+ indexValues.push(indexRowValues);
324
+ }
325
+ // Write the header values
326
+ if (setIndexByKey(columnIndices, headerRowValues.map((l) => l.type === 'value' ? String(l.value.raw) : l.fieldId), columnCount)) {
327
+ columnCount += 1;
328
+ if (columnCount > options.maxColumns) {
329
+ throw new Error(`Cannot pivot results with more than ${options.maxColumns} columns. Try adding a filter to limit your results.`);
330
+ }
331
+ headerValuesT.push(headerRowValues);
332
+ }
333
+ }
334
+ }
335
+ const headerValues = headerValuesT[0]?.map((_, colIndex) => headerValuesT.map((row, rowIndex) => {
336
+ const cell = row[colIndex];
337
+ if (cell.type === 'label') {
338
+ return cell;
339
+ }
340
+ const keys = row
341
+ .slice(0, colIndex + 1)
342
+ .reduce((acc, l) => l.type === 'value'
343
+ ? [...acc, String(l.value.raw)]
344
+ : acc, []);
345
+ const cellWithSpan = {
346
+ ...cell,
347
+ colSpan: getColSpanByKey(rowIndex, columnIndices, keys),
348
+ };
349
+ return cellWithSpan;
350
+ })) ?? [];
351
+ const hasIndex = indexValueTypes.length > 0;
352
+ const hasHeader = headerValueTypes.length > 0;
353
+ // Compute the size of the data values
354
+ const N_DATA_ROWS = hasIndex ? rowCount : 1;
355
+ const N_DATA_COLUMNS = hasHeader ? columnCount : 1;
356
+ // Compute the data values
357
+ const dataValues = create2DArray(N_DATA_ROWS, N_DATA_COLUMNS);
358
+ if (N_DATA_ROWS === 0 || N_DATA_COLUMNS === 0) {
359
+ throw new Error('Cannot pivot results with no data');
360
+ }
361
+ // Compute pivoted data
362
+ for (let nRow = 0; nRow < N_ROWS; nRow += 1) {
363
+ const row = rows[nRow];
364
+ for (let nMetric = 0; nMetric < metrics.length; nMetric += 1) {
365
+ const metric = metrics[nMetric];
366
+ const { value } = row[metric.fieldId];
367
+ const rowKeys = [
368
+ ...indexDimensions.map((d) => row[d].value.raw),
369
+ ...(pivotConfig.metricsAsRows ? [metric.fieldId] : []),
370
+ ];
371
+ const columnKeys = [
372
+ ...headerDimensions.map((d) => row[d].value.raw),
373
+ ...(pivotConfig.metricsAsRows ? [] : [metric.fieldId]),
374
+ ];
375
+ const rowKeysString = rowKeys.map(String);
376
+ const columnKeysString = columnKeys.map(String);
377
+ const rowIndex = hasIndex
378
+ ? getIndexByKey(rowIndices, rowKeysString)
379
+ : 0;
380
+ const columnIndex = hasHeader
381
+ ? getIndexByKey(columnIndices, columnKeysString)
382
+ : 0;
383
+ dataValues[rowIndex][columnIndex] = value;
384
+ }
385
+ }
386
+ // compute row totals
387
+ let rowTotalFields;
388
+ let rowTotals;
389
+ if (pivotConfig.rowTotals && hasHeader) {
390
+ if (pivotConfig.metricsAsRows) {
391
+ const N_TOTAL_COLS = 1;
392
+ const N_TOTAL_ROWS = headerValues.length;
393
+ rowTotalFields = create2DArray(N_TOTAL_ROWS, N_TOTAL_COLS);
394
+ rowTotals = create2DArray(N_DATA_ROWS, N_TOTAL_COLS);
395
+ // set the last header cell as the "Total"
396
+ rowTotalFields[N_TOTAL_ROWS - 1][N_TOTAL_COLS - 1] = {
397
+ fieldId: undefined,
398
+ };
399
+ rowTotals = rowTotals.map((row, rowIndex) => row.map(() => dataValues[rowIndex].reduce((acc, value) => acc + parseNumericValue(value), 0)));
400
+ }
401
+ else {
402
+ const N_TOTAL_COLS = summableMetricFieldIds.length;
403
+ const N_TOTAL_ROWS = headerValues.length;
404
+ rowTotalFields = create2DArray(N_TOTAL_ROWS, N_TOTAL_COLS);
405
+ rowTotals = create2DArray(N_DATA_ROWS, N_TOTAL_COLS);
406
+ summableMetricFieldIds.forEach((fieldId, metricIndex) => {
407
+ rowTotalFields[N_TOTAL_ROWS - 1][metricIndex] = {
408
+ fieldId,
409
+ };
410
+ });
411
+ rowTotals = rowTotals.map((row, rowIndex) => row.map((_, totalColIndex) => {
412
+ const totalColFieldId = rowTotalFields[N_TOTAL_ROWS - 1][totalColIndex]
413
+ ?.fieldId;
414
+ const valueColIndices = totalColFieldId
415
+ ? getAllIndicesForFieldId(columnIndices, totalColFieldId)
416
+ : [];
417
+ return dataValues[rowIndex]
418
+ .filter((__, dataValueColIndex) => valueColIndices.includes(dataValueColIndex))
419
+ .reduce((acc, value) => acc + parseNumericValue(value), 0);
420
+ }));
421
+ }
422
+ }
423
+ let columnTotalFields;
424
+ let columnTotals;
425
+ if (pivotConfig.columnTotals && hasIndex) {
426
+ if (pivotConfig.metricsAsRows) {
427
+ const N_TOTAL_ROWS = summableMetricFieldIds.length;
428
+ const N_TOTAL_COLS = indexValueTypes.length;
429
+ columnTotalFields = create2DArray(N_TOTAL_ROWS, N_TOTAL_COLS);
430
+ columnTotals = create2DArray(N_TOTAL_ROWS, N_DATA_COLUMNS);
431
+ summableMetricFieldIds.forEach((fieldId, metricIndex) => {
432
+ columnTotalFields[metricIndex][N_TOTAL_COLS - 1] = {
433
+ fieldId,
434
+ };
435
+ });
436
+ columnTotals = columnTotals.map((row, rowIndex) => row.map((_, totalColIndex) => {
437
+ const totalColFieldId = columnTotalFields[rowIndex][N_TOTAL_COLS - 1]?.fieldId;
438
+ const valueColIndices = totalColFieldId
439
+ ? getAllIndicesForFieldId(rowIndices, totalColFieldId)
440
+ : [];
441
+ return dataValues
442
+ .filter((__, dataValueColIndex) => valueColIndices.includes(dataValueColIndex))
443
+ .reduce((acc, value) => acc + parseNumericValue(value[totalColIndex]), 0);
444
+ }));
445
+ }
446
+ else {
447
+ const N_TOTAL_COLS = indexValues[0].length;
448
+ const N_TOTAL_ROWS = 1;
449
+ columnTotalFields = create2DArray(N_TOTAL_ROWS, N_TOTAL_COLS);
450
+ columnTotals = create2DArray(N_TOTAL_ROWS, N_DATA_COLUMNS);
451
+ // set the last index cell as the "Total"
452
+ columnTotalFields[N_TOTAL_ROWS - 1][N_TOTAL_COLS - 1] = {
453
+ fieldId: undefined,
454
+ };
455
+ columnTotals = columnTotals.map((row, _totalRowIndex) => row.map((_col, colIndex) => dataValues
456
+ .map((dataRow) => dataRow[colIndex])
457
+ .reduce((acc, value) => acc + parseNumericValue(value), 0)));
458
+ }
459
+ }
460
+ const titleFields = create2DArray(hasHeader ? headerValueTypes.length : 1, hasIndex ? indexValueTypes.length : 1);
461
+ headerValueTypes.forEach((headerValueType, headerIndex) => {
462
+ if (headerValueType.type === field_1.FieldType.DIMENSION) {
463
+ titleFields[headerIndex][hasIndex ? indexValueTypes.length - 1 : 0] = {
464
+ fieldId: headerValueType.fieldId,
465
+ direction: 'header',
466
+ };
467
+ }
468
+ });
469
+ indexValueTypes.forEach((indexValueType, indexIndex) => {
470
+ if (indexValueType.type === field_1.FieldType.DIMENSION) {
471
+ titleFields[hasHeader ? headerValueTypes.length - 1 : 0][indexIndex] = {
472
+ fieldId: indexValueType.fieldId,
473
+ direction: 'index',
474
+ };
475
+ }
476
+ });
477
+ const cellsCount = (indexValueTypes.length === 0 ? titleFields[0].length : 0) +
478
+ indexValueTypes.length +
479
+ dataValues[0].length +
480
+ (pivotConfig.rowTotals && rowTotals ? rowTotals[0].length : 0);
481
+ const rowsCount = dataValues.length || 0;
482
+ const pivotData = {
483
+ titleFields,
484
+ headerValueTypes,
485
+ headerValues,
486
+ indexValueTypes,
487
+ indexValues,
488
+ dataColumnCount: N_DATA_COLUMNS,
489
+ dataValues,
490
+ rowTotalFields,
491
+ columnTotalFields,
492
+ rowTotals,
493
+ columnTotals,
494
+ cellsCount,
495
+ rowsCount,
496
+ pivotConfig,
497
+ retrofitData: {
498
+ allCombinedData: [],
499
+ pivotColumnInfo: [],
500
+ },
501
+ };
502
+ return combinedRetrofit(pivotData, getField, getFieldLabel);
503
+ };
504
+ exports.pivotQueryResults = pivotQueryResults;
505
+ const pivotResultsAsCsv = (pivotConfig, rows, itemMap, metricQuery, customLabels, onlyRaw, maxColumnLimit) => {
506
+ const getFieldLabel = (fieldId) => {
507
+ const customLabel = customLabels?.[fieldId];
508
+ if (customLabel !== undefined)
509
+ return customLabel;
510
+ const field = itemMap[fieldId];
511
+ return (field && (0, field_1.isField)(field) && field?.label) || fieldId;
512
+ };
513
+ const pivotedResults = (0, exports.pivotQueryResults)({
514
+ pivotConfig,
515
+ metricQuery,
516
+ rows,
517
+ options: {
518
+ maxColumns: maxColumnLimit,
519
+ },
520
+ getField: (fieldId) => itemMap && itemMap[fieldId],
521
+ getFieldLabel,
522
+ });
523
+ const formatField = onlyRaw ? 'raw' : 'formatted';
524
+ const headers = pivotedResults.headerValues.reduce((acc, row, i) => {
525
+ const values = row.map((header) => 'value' in header
526
+ ? header.value[formatField]
527
+ : getFieldLabel(header.fieldId));
528
+ const fields = pivotedResults.titleFields[i];
529
+ const fieldLabels = fields.map((field) => field ? getFieldLabel(field.fieldId) : '-');
530
+ acc[i] = [...fieldLabels, ...values];
531
+ return acc;
532
+ }, [[]]);
533
+ const fieldIds = Object.values(pivotedResults.retrofitData.pivotColumnInfo).map((field) => field.fieldId);
534
+ const hasIndex = pivotedResults.indexValues.length > 0;
535
+ const pivotedRows = pivotedResults.retrofitData.allCombinedData.map((row) => {
536
+ // Fields that return `null` don't appear in the pivot table
537
+ // If there are no index fields, we need to add an empty string to the beginning of the row
538
+ const noIndexPrefix = hasIndex ? [] : [''];
539
+ const formattedRows = fieldIds.map((fieldId) => row[fieldId]?.value?.[formatField] || '-');
540
+ return [...noIndexPrefix, ...formattedRows];
541
+ });
542
+ return [...headers, ...pivotedRows];
543
+ };
544
+ exports.pivotResultsAsCsv = pivotResultsAsCsv;
@@ -0,0 +1,8 @@
1
+ import { type MetricQuery } from '../types/metricQuery';
2
+ import { type ResultRow } from '../types/results';
3
+ export declare const METRIC_QUERY_2DIM_2METRIC: Pick<MetricQuery, 'metrics' | 'dimensions' | 'tableCalculations' | 'additionalMetrics'>;
4
+ export declare const RESULT_ROWS_2DIM_2METRIC: ResultRow[];
5
+ export declare const METRIC_QUERY_1DIM_2METRIC: Pick<MetricQuery, 'metrics' | 'dimensions' | 'tableCalculations' | 'additionalMetrics'>;
6
+ export declare const RESULT_ROWS_1DIM_2METRIC: ResultRow[];
7
+ export declare const METRIC_QUERY_0DIM_2METRIC: Pick<MetricQuery, 'metrics' | 'dimensions' | 'tableCalculations' | 'additionalMetrics'>;
8
+ export declare const RESULT_ROWS_0DIM_2METRIC: ResultRow[];
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RESULT_ROWS_0DIM_2METRIC = exports.METRIC_QUERY_0DIM_2METRIC = exports.RESULT_ROWS_1DIM_2METRIC = exports.METRIC_QUERY_1DIM_2METRIC = exports.RESULT_ROWS_2DIM_2METRIC = exports.METRIC_QUERY_2DIM_2METRIC = void 0;
4
+ exports.METRIC_QUERY_2DIM_2METRIC = {
5
+ metrics: ['views', 'devices'],
6
+ dimensions: ['page', 'site'],
7
+ tableCalculations: [],
8
+ };
9
+ exports.RESULT_ROWS_2DIM_2METRIC = [
10
+ {
11
+ page: { value: { raw: '/home', formatted: '/home' } },
12
+ site: { value: { raw: 'blog', formatted: 'Blog' } },
13
+ views: { value: { raw: 6, formatted: '6.0' } },
14
+ devices: { value: { raw: 7, formatted: '7.0' } },
15
+ },
16
+ {
17
+ page: { value: { raw: '/about', formatted: '/about' } },
18
+ site: { value: { raw: 'blog', formatted: 'Blog' } },
19
+ views: { value: { raw: 12, formatted: '12.0' } },
20
+ devices: { value: { raw: 0, formatted: '0.0' } },
21
+ },
22
+ {
23
+ page: { value: { raw: '/first-post', formatted: '/first-post' } },
24
+ site: { value: { raw: 'blog', formatted: 'Blog' } },
25
+ views: { value: { raw: 11, formatted: '11.0' } },
26
+ devices: { value: { raw: 1, formatted: '1.0' } },
27
+ },
28
+ {
29
+ page: { value: { raw: '/home', formatted: '/home' } },
30
+ site: { value: { raw: 'docs', formatted: 'Docs' } },
31
+ views: { value: { raw: 2, formatted: '2.0' } },
32
+ devices: { value: { raw: 10, formatted: '10.0' } },
33
+ },
34
+ {
35
+ page: { value: { raw: '/about', formatted: '/about' } },
36
+ site: { value: { raw: 'docs', formatted: 'Docs' } },
37
+ views: { value: { raw: 2, formatted: '2.0' } },
38
+ devices: { value: { raw: 13, formatted: '13.0' } },
39
+ },
40
+ ];
41
+ exports.METRIC_QUERY_1DIM_2METRIC = {
42
+ metrics: ['views', 'devices'],
43
+ dimensions: ['page'],
44
+ tableCalculations: [],
45
+ };
46
+ exports.RESULT_ROWS_1DIM_2METRIC = [
47
+ {
48
+ page: { value: { raw: '/home', formatted: '/home' } },
49
+ views: { value: { raw: 6, formatted: '6.0' } },
50
+ devices: { value: { raw: 7, formatted: '7.0' } },
51
+ },
52
+ {
53
+ page: { value: { raw: '/about', formatted: '/about' } },
54
+ views: { value: { raw: 12, formatted: '12.0' } },
55
+ devices: { value: { raw: 0, formatted: '0.0' } },
56
+ },
57
+ {
58
+ page: { value: { raw: '/first-post', formatted: '/first-post' } },
59
+ views: { value: { raw: 11, formatted: '11.0' } },
60
+ devices: { value: { raw: 1, formatted: '1.0' } },
61
+ },
62
+ ];
63
+ exports.METRIC_QUERY_0DIM_2METRIC = {
64
+ metrics: ['views', 'devices'],
65
+ dimensions: [],
66
+ tableCalculations: [],
67
+ };
68
+ exports.RESULT_ROWS_0DIM_2METRIC = [
69
+ {
70
+ views: { value: { raw: 6, formatted: '6.0' } },
71
+ devices: { value: { raw: 7, formatted: '7.0' } },
72
+ },
73
+ ];
@@ -0,0 +1 @@
1
+ export {};