@lightdash/common 0.1369.1 → 0.1369.2

Sign up to get free protection for your applications and to get access to all the features.
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 {};