@bygd/nc-report-ui 0.1.41 → 0.1.43
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/app/esm/index.js +970 -529
- package/dist/default/cjs/index.cjs +963 -528
- package/dist/default/esm/index.js +962 -529
- package/package.json +1 -1
package/dist/app/esm/index.js
CHANGED
|
@@ -30273,6 +30273,7 @@ function useInView({
|
|
|
30273
30273
|
delay,
|
|
30274
30274
|
trackVisibility,
|
|
30275
30275
|
rootMargin,
|
|
30276
|
+
scrollMargin,
|
|
30276
30277
|
root,
|
|
30277
30278
|
triggerOnce,
|
|
30278
30279
|
skip,
|
|
@@ -30317,8 +30318,8 @@ function useInView({
|
|
|
30317
30318
|
{
|
|
30318
30319
|
root,
|
|
30319
30320
|
rootMargin,
|
|
30321
|
+
scrollMargin,
|
|
30320
30322
|
threshold,
|
|
30321
|
-
// @ts-expect-error
|
|
30322
30323
|
trackVisibility,
|
|
30323
30324
|
delay
|
|
30324
30325
|
},
|
|
@@ -30338,6 +30339,7 @@ function useInView({
|
|
|
30338
30339
|
ref,
|
|
30339
30340
|
root,
|
|
30340
30341
|
rootMargin,
|
|
30342
|
+
scrollMargin,
|
|
30341
30343
|
triggerOnce,
|
|
30342
30344
|
skip,
|
|
30343
30345
|
trackVisibility,
|
|
@@ -43266,6 +43268,10 @@ const ChartFilterMultiSelect = ({
|
|
|
43266
43268
|
})));
|
|
43267
43269
|
};
|
|
43268
43270
|
|
|
43271
|
+
var CheckCircleIcon = createSvgIcon$1(/*#__PURE__*/u("path", {
|
|
43272
|
+
d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m-2 15-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8z"
|
|
43273
|
+
}));
|
|
43274
|
+
|
|
43269
43275
|
const formatLabel = key => {
|
|
43270
43276
|
return key?.replace(/([a-z])([A-Z])/g, "$1 $2") // insert space before capital letters
|
|
43271
43277
|
?.replace(/_/g, " ")?.replace(/-/g, " ") // replace underscores with spaces (optional)
|
|
@@ -43329,7 +43335,8 @@ function SingleSelect({
|
|
|
43329
43335
|
key,
|
|
43330
43336
|
value,
|
|
43331
43337
|
disabled,
|
|
43332
|
-
icon
|
|
43338
|
+
icon,
|
|
43339
|
+
alreadySelected
|
|
43333
43340
|
} = itm;
|
|
43334
43341
|
return /*#__PURE__*/gn.createElement(MenuItem, {
|
|
43335
43342
|
key: key,
|
|
@@ -43340,7 +43347,12 @@ function SingleSelect({
|
|
|
43340
43347
|
minHeight: "36px",
|
|
43341
43348
|
display: "flex",
|
|
43342
43349
|
alignItems: "center",
|
|
43343
|
-
gap: "6px"
|
|
43350
|
+
gap: "6px",
|
|
43351
|
+
...(alreadySelected && {
|
|
43352
|
+
color: "rgb(70, 134, 128)",
|
|
43353
|
+
backgroundColor: "rgba(70, 134, 128, 0.08)",
|
|
43354
|
+
fontWeight: 600
|
|
43355
|
+
})
|
|
43344
43356
|
}
|
|
43345
43357
|
}, icon && /*#__PURE__*/gn.createElement(Box, {
|
|
43346
43358
|
component: "span",
|
|
@@ -43348,7 +43360,13 @@ function SingleSelect({
|
|
|
43348
43360
|
display: "inline-flex",
|
|
43349
43361
|
alignItems: "center"
|
|
43350
43362
|
}
|
|
43351
|
-
}, icon),
|
|
43363
|
+
}, icon), alreadySelected && /*#__PURE__*/gn.createElement(CheckCircleIcon, {
|
|
43364
|
+
fontSize: "small",
|
|
43365
|
+
sx: {
|
|
43366
|
+
color: "rgb(70, 134, 128)",
|
|
43367
|
+
fontSize: "16px"
|
|
43368
|
+
}
|
|
43369
|
+
}), formatLabel(value));
|
|
43352
43370
|
}))));
|
|
43353
43371
|
}
|
|
43354
43372
|
|
|
@@ -77168,6 +77186,140 @@ var ArrowDownwardIcon = createSvgIcon$1(/*#__PURE__*/u("path", {
|
|
|
77168
77186
|
d: "m20 12-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8z"
|
|
77169
77187
|
}));
|
|
77170
77188
|
|
|
77189
|
+
// Shared drag-and-drop primitives for the index-identified, arrow-reorderable
|
|
77190
|
+
// lists used across the report builder (Dimensions, Metrics, Column Order).
|
|
77191
|
+
// Previously duplicated per-tab; consolidated here so all three behave and
|
|
77192
|
+
// look identical and only need to be fixed in one place.
|
|
77193
|
+
|
|
77194
|
+
const useSortableListSensors = () => useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, {
|
|
77195
|
+
coordinateGetter: sortableKeyboardCoordinates
|
|
77196
|
+
}));
|
|
77197
|
+
|
|
77198
|
+
// Builds drag/move handlers for a list where dnd-kit item ids are the array
|
|
77199
|
+
// index. `onReorder` receives the whole reordered list.
|
|
77200
|
+
const makeListReorderHandlers = (list, onReorder) => {
|
|
77201
|
+
const handleDragEnd = ({
|
|
77202
|
+
active,
|
|
77203
|
+
over
|
|
77204
|
+
}) => {
|
|
77205
|
+
if (over && active.id !== over.id) {
|
|
77206
|
+
const oldIndex = list.findIndex((_, i) => i === active.id);
|
|
77207
|
+
const newIndex = list.findIndex((_, i) => i === over.id);
|
|
77208
|
+
onReorder(arrayMove(list, oldIndex, newIndex));
|
|
77209
|
+
}
|
|
77210
|
+
};
|
|
77211
|
+
const handleMoveUp = index => {
|
|
77212
|
+
if (index > 0) {
|
|
77213
|
+
onReorder(arrayMove(list, index, index - 1));
|
|
77214
|
+
}
|
|
77215
|
+
};
|
|
77216
|
+
const handleMoveDown = index => {
|
|
77217
|
+
if (index < list.length - 1) {
|
|
77218
|
+
onReorder(arrayMove(list, index, index + 1));
|
|
77219
|
+
}
|
|
77220
|
+
};
|
|
77221
|
+
return {
|
|
77222
|
+
handleDragEnd,
|
|
77223
|
+
handleMoveUp,
|
|
77224
|
+
handleMoveDown
|
|
77225
|
+
};
|
|
77226
|
+
};
|
|
77227
|
+
|
|
77228
|
+
// Wraps DndContext + SortableContext + the vertical list container.
|
|
77229
|
+
// `itemIds` must line up with the ids passed to each item's useSortable().
|
|
77230
|
+
const SortableListContext = ({
|
|
77231
|
+
sensors,
|
|
77232
|
+
itemIds,
|
|
77233
|
+
onDragEnd,
|
|
77234
|
+
children
|
|
77235
|
+
}) => /*#__PURE__*/gn.createElement(DndContext, {
|
|
77236
|
+
sensors: sensors,
|
|
77237
|
+
collisionDetection: closestCenter,
|
|
77238
|
+
onDragEnd: onDragEnd
|
|
77239
|
+
}, /*#__PURE__*/gn.createElement(SortableContext, {
|
|
77240
|
+
items: itemIds,
|
|
77241
|
+
strategy: verticalListSortingStrategy
|
|
77242
|
+
}, /*#__PURE__*/gn.createElement(Box, {
|
|
77243
|
+
sx: {
|
|
77244
|
+
display: "flex",
|
|
77245
|
+
flexDirection: "column",
|
|
77246
|
+
gap: 1
|
|
77247
|
+
}
|
|
77248
|
+
}, children)));
|
|
77249
|
+
|
|
77250
|
+
// Per-row useSortable() wiring shared by every sortable chip/row component.
|
|
77251
|
+
const useSortableRow = id => {
|
|
77252
|
+
const {
|
|
77253
|
+
attributes,
|
|
77254
|
+
listeners,
|
|
77255
|
+
setNodeRef,
|
|
77256
|
+
transform,
|
|
77257
|
+
transition,
|
|
77258
|
+
isDragging
|
|
77259
|
+
} = useSortable({
|
|
77260
|
+
id
|
|
77261
|
+
});
|
|
77262
|
+
const style = {
|
|
77263
|
+
transform: CSS$1.Transform.toString(transform),
|
|
77264
|
+
transition,
|
|
77265
|
+
opacity: isDragging ? 0.5 : 1,
|
|
77266
|
+
display: "flex",
|
|
77267
|
+
alignItems: "center",
|
|
77268
|
+
width: "100%"
|
|
77269
|
+
};
|
|
77270
|
+
return {
|
|
77271
|
+
attributes,
|
|
77272
|
+
listeners,
|
|
77273
|
+
setNodeRef,
|
|
77274
|
+
style
|
|
77275
|
+
};
|
|
77276
|
+
};
|
|
77277
|
+
const DragHandle = ({
|
|
77278
|
+
listeners
|
|
77279
|
+
}) => /*#__PURE__*/gn.createElement(Box, _extends({}, listeners, {
|
|
77280
|
+
sx: {
|
|
77281
|
+
display: "flex",
|
|
77282
|
+
alignItems: "center",
|
|
77283
|
+
cursor: "grab",
|
|
77284
|
+
"&:active": {
|
|
77285
|
+
cursor: "grabbing"
|
|
77286
|
+
}
|
|
77287
|
+
}
|
|
77288
|
+
}), /*#__PURE__*/gn.createElement(DragIndicatorIcon, {
|
|
77289
|
+
sx: {
|
|
77290
|
+
cursor: "grab",
|
|
77291
|
+
color: "rgba(110, 110, 110, 0.62)"
|
|
77292
|
+
}
|
|
77293
|
+
}));
|
|
77294
|
+
const MoveButtons = ({
|
|
77295
|
+
onMoveUp,
|
|
77296
|
+
onMoveDown,
|
|
77297
|
+
isFirst,
|
|
77298
|
+
isLast
|
|
77299
|
+
}) => /*#__PURE__*/gn.createElement(Box, {
|
|
77300
|
+
className: "hover-icons",
|
|
77301
|
+
sx: {
|
|
77302
|
+
display: "flex",
|
|
77303
|
+
gap: 0.5,
|
|
77304
|
+
opacity: 0,
|
|
77305
|
+
transition: "opacity 0.2s"
|
|
77306
|
+
}
|
|
77307
|
+
}, /*#__PURE__*/gn.createElement(IconButton, {
|
|
77308
|
+
size: "small",
|
|
77309
|
+
onClick: onMoveUp,
|
|
77310
|
+
disabled: isFirst,
|
|
77311
|
+
"aria-label": "move up"
|
|
77312
|
+
}, /*#__PURE__*/gn.createElement(ArrowUpwardIcon, {
|
|
77313
|
+
fontSize: "small"
|
|
77314
|
+
})), /*#__PURE__*/gn.createElement(IconButton, {
|
|
77315
|
+
size: "small",
|
|
77316
|
+
onClick: onMoveDown,
|
|
77317
|
+
disabled: isLast,
|
|
77318
|
+
"aria-label": "move down"
|
|
77319
|
+
}, /*#__PURE__*/gn.createElement(ArrowDownwardIcon, {
|
|
77320
|
+
fontSize: "small"
|
|
77321
|
+
})));
|
|
77322
|
+
|
|
77171
77323
|
var SortIcon = createSvgIcon$1(/*#__PURE__*/u("path", {
|
|
77172
77324
|
d: "M3 18h6v-2H3zM3 6v2h18V6zm0 7h12v-2H3z"
|
|
77173
77325
|
}));
|
|
@@ -77192,6 +77344,29 @@ var FunctionsIcon = createSvgIcon$1(/*#__PURE__*/u("path", {
|
|
|
77192
77344
|
d: "M18 4H6v2l6.5 6L6 18v2h12v-3h-7l5-5-5-5h7z"
|
|
77193
77345
|
}));
|
|
77194
77346
|
|
|
77347
|
+
// Builds an identity string for a saved dimension/metric instance. Deliberate
|
|
77348
|
+
// duplicates (the same field added more than once) are allowed - the first
|
|
77349
|
+
// occurrence keeps the plain fullPath (so existing saved reports and their
|
|
77350
|
+
// title-override keys stay backward compatible), later occurrences get a
|
|
77351
|
+
// `#n` suffix.
|
|
77352
|
+
const makeInstanceId = (fullPath, occurrenceIndex) => occurrenceIndex === 0 ? fullPath : `${fullPath}#${occurrenceIndex}`;
|
|
77353
|
+
|
|
77354
|
+
// Picks the next free instance id for a newly-added fullPath, given the
|
|
77355
|
+
// items already saved. Scans occurrence numbers from 0 rather than just
|
|
77356
|
+
// counting existing instances with this fullPath, because counting alone
|
|
77357
|
+
// can collide: e.g. add "ft.currency" twice (ids "ft.currency",
|
|
77358
|
+
// "ft.currency#1"), remove the first, then add "ft.currency" again - a
|
|
77359
|
+
// plain count of 1 would regenerate "ft.currency#1", clashing with the
|
|
77360
|
+
// surviving duplicate.
|
|
77361
|
+
const nextInstanceId = (fullPath, existingItems) => {
|
|
77362
|
+
const existingIds = new Set(existingItems.map(item => item.id));
|
|
77363
|
+
let occurrenceIndex = 0;
|
|
77364
|
+
while (existingIds.has(makeInstanceId(fullPath, occurrenceIndex))) {
|
|
77365
|
+
occurrenceIndex += 1;
|
|
77366
|
+
}
|
|
77367
|
+
return makeInstanceId(fullPath, occurrenceIndex);
|
|
77368
|
+
};
|
|
77369
|
+
|
|
77195
77370
|
// Replace {{key}} placeholders in a title with values from the report
|
|
77196
77371
|
// parameters (e.g. "Balance ({{base_currency}})" -> "Balance (EUR)").
|
|
77197
77372
|
// Unknown keys are left as-is so missing parameters remain visible.
|
|
@@ -77203,6 +77378,53 @@ const interpolateTitle = (template, params) => {
|
|
|
77203
77378
|
});
|
|
77204
77379
|
};
|
|
77205
77380
|
|
|
77381
|
+
// Resolves the final column display order for the results grid: dimensions,
|
|
77382
|
+
// computed dimensions, and metrics combined into a single list.
|
|
77383
|
+
//
|
|
77384
|
+
// `columnOrder` is the optional, explicitly-arranged order (raw entries
|
|
77385
|
+
// `{ type: 'dimension' | 'computed' | 'metric', key }`) set on the Column
|
|
77386
|
+
// Order tab. When present, its entries come first, in that order. Anything
|
|
77387
|
+
// not covered by it (new fields added since it was last touched, or when
|
|
77388
|
+
// it's empty/undefined because no custom order has ever been defined) is
|
|
77389
|
+
// appended in the pre-existing default order: dimensions/computed (in their
|
|
77390
|
+
// own `dimensionOrder`) first, then metrics (in their own array order) —
|
|
77391
|
+
// this keeps behavior unchanged for reports that never define a column order.
|
|
77392
|
+
const resolveColumnOrder = (columnOrder, orderedDimensionItems, metrics) => {
|
|
77393
|
+
// Normalise every candidate item to a uniform { kind, key, data } shape
|
|
77394
|
+
// up front, so callers (e.g. the Column Order tab) can always read
|
|
77395
|
+
// `.key` regardless of where the item came from.
|
|
77396
|
+
// Keyed by instance id (not fullPath) so that two instances of the same
|
|
77397
|
+
// dimension/metric - added deliberately as duplicates - are treated as
|
|
77398
|
+
// distinct columns rather than collapsing into one.
|
|
77399
|
+
const normalizedDimensionItems = orderedDimensionItems.map(item => ({
|
|
77400
|
+
kind: item.kind,
|
|
77401
|
+
key: item.kind === 'computed' ? item.data.name : item.data.id,
|
|
77402
|
+
data: item.data
|
|
77403
|
+
}));
|
|
77404
|
+
const normalizedMetricItems = metrics.map(m => ({
|
|
77405
|
+
kind: 'metric',
|
|
77406
|
+
key: m.id,
|
|
77407
|
+
data: m
|
|
77408
|
+
}));
|
|
77409
|
+
const byKey = Object.fromEntries([...normalizedDimensionItems, ...normalizedMetricItems].map(item => [`${item.kind}:${item.key}`, item]));
|
|
77410
|
+
const resolved = [];
|
|
77411
|
+
const seen = new Set();
|
|
77412
|
+
(columnOrder || []).forEach(entry => {
|
|
77413
|
+
const seenKey = `${entry.type}:${entry.key}`;
|
|
77414
|
+
if (seen.has(seenKey) || !byKey[seenKey]) return;
|
|
77415
|
+
resolved.push(byKey[seenKey]);
|
|
77416
|
+
seen.add(seenKey);
|
|
77417
|
+
});
|
|
77418
|
+
[...normalizedDimensionItems, ...normalizedMetricItems].forEach(item => {
|
|
77419
|
+
const seenKey = `${item.kind}:${item.key}`;
|
|
77420
|
+
if (!seen.has(seenKey)) {
|
|
77421
|
+
resolved.push(item);
|
|
77422
|
+
seen.add(seenKey);
|
|
77423
|
+
}
|
|
77424
|
+
});
|
|
77425
|
+
return resolved;
|
|
77426
|
+
};
|
|
77427
|
+
|
|
77206
77428
|
// Sortable Chip Component
|
|
77207
77429
|
const SortableChip$1 = ({
|
|
77208
77430
|
id,
|
|
@@ -77215,7 +77437,7 @@ const SortableChip$1 = ({
|
|
|
77215
77437
|
isLast,
|
|
77216
77438
|
sortOrder,
|
|
77217
77439
|
onSortOrderChange,
|
|
77218
|
-
|
|
77440
|
+
instanceId,
|
|
77219
77441
|
defaultTitle,
|
|
77220
77442
|
customTitle,
|
|
77221
77443
|
onUpdateTitle,
|
|
@@ -77231,20 +77453,8 @@ const SortableChip$1 = ({
|
|
|
77231
77453
|
attributes,
|
|
77232
77454
|
listeners,
|
|
77233
77455
|
setNodeRef,
|
|
77234
|
-
|
|
77235
|
-
|
|
77236
|
-
isDragging
|
|
77237
|
-
} = useSortable({
|
|
77238
|
-
id
|
|
77239
|
-
});
|
|
77240
|
-
const style = {
|
|
77241
|
-
transform: CSS$1.Transform.toString(transform),
|
|
77242
|
-
transition,
|
|
77243
|
-
opacity: isDragging ? 0.5 : 1,
|
|
77244
|
-
display: "flex",
|
|
77245
|
-
alignItems: "center",
|
|
77246
|
-
width: "100%"
|
|
77247
|
-
};
|
|
77456
|
+
style
|
|
77457
|
+
} = useSortableRow(id);
|
|
77248
77458
|
|
|
77249
77459
|
// Focus input when entering edit mode
|
|
77250
77460
|
h(() => {
|
|
@@ -77310,7 +77520,7 @@ const SortableChip$1 = ({
|
|
|
77310
77520
|
handleCancel();
|
|
77311
77521
|
return;
|
|
77312
77522
|
}
|
|
77313
|
-
onUpdateTitle(
|
|
77523
|
+
onUpdateTitle(instanceId, trimmedValue);
|
|
77314
77524
|
setIsEditing(false);
|
|
77315
77525
|
};
|
|
77316
77526
|
const handleCancel = () => {
|
|
@@ -77318,7 +77528,7 @@ const SortableChip$1 = ({
|
|
|
77318
77528
|
setEditValue("");
|
|
77319
77529
|
};
|
|
77320
77530
|
const handleReset = () => {
|
|
77321
|
-
onResetTitle(
|
|
77531
|
+
onResetTitle(instanceId);
|
|
77322
77532
|
setIsEditing(false);
|
|
77323
77533
|
setEditValue("");
|
|
77324
77534
|
};
|
|
@@ -77353,21 +77563,9 @@ const SortableChip$1 = ({
|
|
|
77353
77563
|
opacity: 1 // show icons on hover
|
|
77354
77564
|
}
|
|
77355
77565
|
}
|
|
77356
|
-
}, /*#__PURE__*/gn.createElement(
|
|
77357
|
-
|
|
77358
|
-
|
|
77359
|
-
alignItems: "center",
|
|
77360
|
-
cursor: "grab",
|
|
77361
|
-
"&:active": {
|
|
77362
|
-
cursor: "grabbing"
|
|
77363
|
-
}
|
|
77364
|
-
}
|
|
77365
|
-
}), /*#__PURE__*/gn.createElement(DragIndicatorIcon, {
|
|
77366
|
-
sx: {
|
|
77367
|
-
cursor: "grab",
|
|
77368
|
-
color: "rgba(110, 110, 110, 0.62)"
|
|
77369
|
-
}
|
|
77370
|
-
})), !isEditing ? /*#__PURE__*/gn.createElement(gn.Fragment, null, /*#__PURE__*/gn.createElement(Box, {
|
|
77566
|
+
}, /*#__PURE__*/gn.createElement(DragHandle, {
|
|
77567
|
+
listeners: listeners
|
|
77568
|
+
}), !isEditing ? /*#__PURE__*/gn.createElement(gn.Fragment, null, /*#__PURE__*/gn.createElement(Box, {
|
|
77371
77569
|
sx: {
|
|
77372
77570
|
minWidth: 0
|
|
77373
77571
|
}
|
|
@@ -77430,29 +77628,12 @@ const SortableChip$1 = ({
|
|
|
77430
77628
|
sx: {
|
|
77431
77629
|
flex: 1
|
|
77432
77630
|
}
|
|
77433
|
-
}), /*#__PURE__*/gn.createElement(
|
|
77434
|
-
|
|
77435
|
-
|
|
77436
|
-
|
|
77437
|
-
|
|
77438
|
-
|
|
77439
|
-
transition: "opacity 0.2s"
|
|
77440
|
-
}
|
|
77441
|
-
}, /*#__PURE__*/gn.createElement(IconButton, {
|
|
77442
|
-
size: "small",
|
|
77443
|
-
onClick: onMoveUp,
|
|
77444
|
-
disabled: isFirst,
|
|
77445
|
-
"aria-label": "move up"
|
|
77446
|
-
}, /*#__PURE__*/gn.createElement(ArrowUpwardIcon, {
|
|
77447
|
-
fontSize: "small"
|
|
77448
|
-
})), /*#__PURE__*/gn.createElement(IconButton, {
|
|
77449
|
-
size: "small",
|
|
77450
|
-
onClick: onMoveDown,
|
|
77451
|
-
disabled: isLast,
|
|
77452
|
-
"aria-label": "move down"
|
|
77453
|
-
}, /*#__PURE__*/gn.createElement(ArrowDownwardIcon, {
|
|
77454
|
-
fontSize: "small"
|
|
77455
|
-
})))) : /*#__PURE__*/gn.createElement(gn.Fragment, null, /*#__PURE__*/gn.createElement(TextField, {
|
|
77631
|
+
}), /*#__PURE__*/gn.createElement(MoveButtons, {
|
|
77632
|
+
onMoveUp: onMoveUp,
|
|
77633
|
+
onMoveDown: onMoveDown,
|
|
77634
|
+
isFirst: isFirst,
|
|
77635
|
+
isLast: isLast
|
|
77636
|
+
})) : /*#__PURE__*/gn.createElement(gn.Fragment, null, /*#__PURE__*/gn.createElement(TextField, {
|
|
77456
77637
|
inputRef: inputRef,
|
|
77457
77638
|
value: editValue,
|
|
77458
77639
|
onChange: e => setEditValue(e.target.value),
|
|
@@ -77528,24 +77709,12 @@ const SortableComputedChip = ({
|
|
|
77528
77709
|
attributes,
|
|
77529
77710
|
listeners,
|
|
77530
77711
|
setNodeRef,
|
|
77531
|
-
|
|
77532
|
-
|
|
77533
|
-
isDragging
|
|
77534
|
-
} = useSortable({
|
|
77535
|
-
id
|
|
77536
|
-
});
|
|
77712
|
+
style
|
|
77713
|
+
} = useSortableRow(id);
|
|
77537
77714
|
const [isEditing, setIsEditing] = d(false);
|
|
77538
77715
|
const [editName, setEditName] = d("");
|
|
77539
77716
|
const [editValue, setEditValue] = d("");
|
|
77540
77717
|
const containerRef = A$1(null);
|
|
77541
|
-
const style = {
|
|
77542
|
-
transform: CSS$1.Transform.toString(transform),
|
|
77543
|
-
transition,
|
|
77544
|
-
opacity: isDragging ? 0.5 : 1,
|
|
77545
|
-
display: "flex",
|
|
77546
|
-
alignItems: "center",
|
|
77547
|
-
width: "100%"
|
|
77548
|
-
};
|
|
77549
77718
|
|
|
77550
77719
|
// Handle click outside to cancel
|
|
77551
77720
|
h(() => {
|
|
@@ -77570,7 +77739,7 @@ const SortableComputedChip = ({
|
|
|
77570
77739
|
const handleSave = () => {
|
|
77571
77740
|
const trimmedName = editName.trim();
|
|
77572
77741
|
const trimmedValue = editValue.trim();
|
|
77573
|
-
if (!trimmedName
|
|
77742
|
+
if (!trimmedName) return;
|
|
77574
77743
|
if (isNameTaken(trimmedName, name)) return;
|
|
77575
77744
|
onUpdate(name, {
|
|
77576
77745
|
name: trimmedName,
|
|
@@ -77608,21 +77777,9 @@ const SortableComputedChip = ({
|
|
|
77608
77777
|
opacity: 1
|
|
77609
77778
|
}
|
|
77610
77779
|
}
|
|
77611
|
-
}, /*#__PURE__*/gn.createElement(
|
|
77612
|
-
|
|
77613
|
-
|
|
77614
|
-
alignItems: "center",
|
|
77615
|
-
cursor: "grab",
|
|
77616
|
-
"&:active": {
|
|
77617
|
-
cursor: "grabbing"
|
|
77618
|
-
}
|
|
77619
|
-
}
|
|
77620
|
-
}), /*#__PURE__*/gn.createElement(DragIndicatorIcon, {
|
|
77621
|
-
sx: {
|
|
77622
|
-
cursor: "grab",
|
|
77623
|
-
color: "rgba(110, 110, 110, 0.62)"
|
|
77624
|
-
}
|
|
77625
|
-
})), !isEditing ? /*#__PURE__*/gn.createElement(gn.Fragment, null, /*#__PURE__*/gn.createElement(Chip, {
|
|
77780
|
+
}, /*#__PURE__*/gn.createElement(DragHandle, {
|
|
77781
|
+
listeners: listeners
|
|
77782
|
+
}), !isEditing ? /*#__PURE__*/gn.createElement(gn.Fragment, null, /*#__PURE__*/gn.createElement(Chip, {
|
|
77626
77783
|
icon: /*#__PURE__*/gn.createElement(FunctionsIcon, {
|
|
77627
77784
|
sx: {
|
|
77628
77785
|
fontSize: "15px !important"
|
|
@@ -77686,29 +77843,12 @@ const SortableComputedChip = ({
|
|
|
77686
77843
|
sx: {
|
|
77687
77844
|
flex: 1
|
|
77688
77845
|
}
|
|
77689
|
-
}), /*#__PURE__*/gn.createElement(
|
|
77690
|
-
|
|
77691
|
-
|
|
77692
|
-
|
|
77693
|
-
|
|
77694
|
-
|
|
77695
|
-
transition: "opacity 0.2s"
|
|
77696
|
-
}
|
|
77697
|
-
}, /*#__PURE__*/gn.createElement(IconButton, {
|
|
77698
|
-
size: "small",
|
|
77699
|
-
onClick: onMoveUp,
|
|
77700
|
-
disabled: isFirst,
|
|
77701
|
-
"aria-label": "move up"
|
|
77702
|
-
}, /*#__PURE__*/gn.createElement(ArrowUpwardIcon, {
|
|
77703
|
-
fontSize: "small"
|
|
77704
|
-
})), /*#__PURE__*/gn.createElement(IconButton, {
|
|
77705
|
-
size: "small",
|
|
77706
|
-
onClick: onMoveDown,
|
|
77707
|
-
disabled: isLast,
|
|
77708
|
-
"aria-label": "move down"
|
|
77709
|
-
}, /*#__PURE__*/gn.createElement(ArrowDownwardIcon, {
|
|
77710
|
-
fontSize: "small"
|
|
77711
|
-
})))) : /*#__PURE__*/gn.createElement(gn.Fragment, null, /*#__PURE__*/gn.createElement(TextField, {
|
|
77846
|
+
}), /*#__PURE__*/gn.createElement(MoveButtons, {
|
|
77847
|
+
onMoveUp: onMoveUp,
|
|
77848
|
+
onMoveDown: onMoveDown,
|
|
77849
|
+
isFirst: isFirst,
|
|
77850
|
+
isLast: isLast
|
|
77851
|
+
})) : /*#__PURE__*/gn.createElement(gn.Fragment, null, /*#__PURE__*/gn.createElement(TextField, {
|
|
77712
77852
|
value: editName,
|
|
77713
77853
|
onChange: e => setEditName(e.target.value),
|
|
77714
77854
|
onKeyDown: handleKeyDown,
|
|
@@ -77749,7 +77889,7 @@ const SortableComputedChip = ({
|
|
|
77749
77889
|
onClick: handleSave,
|
|
77750
77890
|
color: "primary",
|
|
77751
77891
|
"aria-label": "save computed dimension",
|
|
77752
|
-
disabled: !editName.trim() ||
|
|
77892
|
+
disabled: !editName.trim() || nameError
|
|
77753
77893
|
}, /*#__PURE__*/gn.createElement(CheckIcon, {
|
|
77754
77894
|
fontSize: "small"
|
|
77755
77895
|
})))), /*#__PURE__*/gn.createElement(Tooltip, {
|
|
@@ -77793,55 +77933,39 @@ const Dimensions = ({
|
|
|
77793
77933
|
const [computedValue, setComputedValue] = d("");
|
|
77794
77934
|
|
|
77795
77935
|
// Setup drag and drop sensors
|
|
77796
|
-
const sensors =
|
|
77797
|
-
coordinateGetter: sortableKeyboardCoordinates
|
|
77798
|
-
}));
|
|
77936
|
+
const sensors = useSortableListSensors();
|
|
77799
77937
|
|
|
77800
77938
|
// Resolve the unified dimensionOrder into the actual dimension / computed
|
|
77801
77939
|
// dimension objects, so dimensions and computed dimensions can be shown
|
|
77802
77940
|
// (and dragged) as a single list while staying stored in their own arrays.
|
|
77803
|
-
|
|
77941
|
+
// Keyed by instance id rather than fullPath, so two instances of the same
|
|
77942
|
+
// dimension (added deliberately as a duplicate) stay distinct entries.
|
|
77943
|
+
const dimensionById = Object.fromEntries(savedDimensions.map(d => [d.id, d]));
|
|
77804
77944
|
const computedDimensionByName = Object.fromEntries(savedComputedDimensions.map(cd => [cd.name, cd]));
|
|
77805
77945
|
const orderedItems = dimensionOrder.map(entry => entry.type === "dimension" ? {
|
|
77806
77946
|
kind: "dimension",
|
|
77807
77947
|
key: entry.key,
|
|
77808
|
-
data:
|
|
77948
|
+
data: dimensionById[entry.key]
|
|
77809
77949
|
} : {
|
|
77810
77950
|
kind: "computed",
|
|
77811
77951
|
key: entry.key,
|
|
77812
77952
|
data: computedDimensionByName[entry.key]
|
|
77813
77953
|
}).filter(item => item.data);
|
|
77814
77954
|
|
|
77815
|
-
// Handle drag
|
|
77816
|
-
const
|
|
77817
|
-
|
|
77818
|
-
|
|
77819
|
-
|
|
77820
|
-
|
|
77821
|
-
if (over && active.id !== over.id) {
|
|
77822
|
-
const oldIndex = dimensionOrder.findIndex((_, i) => i === active.id);
|
|
77823
|
-
const newIndex = dimensionOrder.findIndex((_, i) => i === over.id);
|
|
77824
|
-
onReorderDimensionItems(arrayMove(dimensionOrder, oldIndex, newIndex));
|
|
77825
|
-
}
|
|
77826
|
-
};
|
|
77827
|
-
|
|
77828
|
-
// Handle move up
|
|
77829
|
-
const handleMoveUp = index => {
|
|
77830
|
-
if (index > 0) {
|
|
77831
|
-
onReorderDimensionItems(arrayMove(dimensionOrder, index, index - 1));
|
|
77832
|
-
}
|
|
77833
|
-
};
|
|
77834
|
-
|
|
77835
|
-
// Handle move down
|
|
77836
|
-
const handleMoveDown = index => {
|
|
77837
|
-
if (index < dimensionOrder.length - 1) {
|
|
77838
|
-
onReorderDimensionItems(arrayMove(dimensionOrder, index, index + 1));
|
|
77839
|
-
}
|
|
77840
|
-
};
|
|
77955
|
+
// Handle drag/move for the unified list
|
|
77956
|
+
const {
|
|
77957
|
+
handleDragEnd,
|
|
77958
|
+
handleMoveUp,
|
|
77959
|
+
handleMoveDown
|
|
77960
|
+
} = makeListReorderHandlers(dimensionOrder, onReorderDimensionItems);
|
|
77841
77961
|
|
|
77842
|
-
// Handle sort order change (dimensions only - computed dimensions have no
|
|
77843
|
-
|
|
77844
|
-
|
|
77962
|
+
// Handle sort order change (dimensions only - computed dimensions have no
|
|
77963
|
+
// order_by). Keyed by instance id but applied query-wide: SQL can only
|
|
77964
|
+
// sort by a given column once, so toggling sort on any duplicate instance
|
|
77965
|
+
// of the same field is propagated to all instances sharing its fullPath
|
|
77966
|
+
// (handled in ReportBuilder's onUpdateDimensionSortOrder).
|
|
77967
|
+
const handleSortOrderChange = (id, sortOrder) => {
|
|
77968
|
+
onUpdateDimensionSortOrder(id, sortOrder);
|
|
77845
77969
|
};
|
|
77846
77970
|
|
|
77847
77971
|
// Computed Dimensions: { name, value } pairs independent of any provider.
|
|
@@ -77862,7 +77986,7 @@ const Dimensions = ({
|
|
|
77862
77986
|
const handleSaveComputed = () => {
|
|
77863
77987
|
const trimmedName = computedName.trim();
|
|
77864
77988
|
const trimmedValue = computedValue.trim();
|
|
77865
|
-
if (!trimmedName
|
|
77989
|
+
if (!trimmedName) return;
|
|
77866
77990
|
if (isComputedNameTaken(trimmedName)) return;
|
|
77867
77991
|
onSaveComputedDimension({
|
|
77868
77992
|
name: trimmedName,
|
|
@@ -77904,7 +78028,10 @@ const Dimensions = ({
|
|
|
77904
78028
|
currentProviderKey = selection.targetKey;
|
|
77905
78029
|
});
|
|
77906
78030
|
|
|
77907
|
-
// Create a Set of already selected dimension fullPaths for quick lookup
|
|
78031
|
+
// Create a Set of already selected dimension fullPaths for quick lookup.
|
|
78032
|
+
// Already-selected items stay selectable (so the same dimension can be
|
|
78033
|
+
// added again on purpose) - they're just flagged so the dropdown can
|
|
78034
|
+
// highlight them instead of blocking the click.
|
|
77908
78035
|
const selectedFullPaths = new Set(savedDimensions.map(dim => dim.fullPath));
|
|
77909
78036
|
const items = [];
|
|
77910
78037
|
// Iterate through aliases (e.g., 'ba', 'pc')
|
|
@@ -77916,9 +78043,6 @@ const Dimensions = ({
|
|
|
77916
78043
|
|
|
77917
78044
|
// Construct the fullPath for this dimension
|
|
77918
78045
|
const fullPath = `${aliasPath.join("_")}.${dimKey}`;
|
|
77919
|
-
|
|
77920
|
-
// Check if this dimension is already selected
|
|
77921
|
-
const isAlreadySelected = selectedFullPaths.has(fullPath);
|
|
77922
78046
|
items.push({
|
|
77923
78047
|
// Include provider name to ensure uniqueness across different providers
|
|
77924
78048
|
key: `${currentProvider}_${alias}.${dimKey}`,
|
|
@@ -77926,7 +78050,7 @@ const Dimensions = ({
|
|
|
77926
78050
|
dimensionKey: dimKey,
|
|
77927
78051
|
alias: alias,
|
|
77928
78052
|
dimension: dimension,
|
|
77929
|
-
|
|
78053
|
+
alreadySelected: selectedFullPaths.has(fullPath)
|
|
77930
78054
|
});
|
|
77931
78055
|
});
|
|
77932
78056
|
});
|
|
@@ -77988,7 +78112,10 @@ const Dimensions = ({
|
|
|
77988
78112
|
dimensionKey: selectedItem.dimensionKey,
|
|
77989
78113
|
dimensionTitle: selectedItem.value,
|
|
77990
78114
|
// The constructed path for server
|
|
77991
|
-
fullPath: fullPath
|
|
78115
|
+
fullPath: fullPath,
|
|
78116
|
+
// Unique per-instance identity, so this field can be added more than
|
|
78117
|
+
// once and each occurrence stays independently orderable/removable.
|
|
78118
|
+
id: nextInstanceId(fullPath, savedDimensions)
|
|
77992
78119
|
};
|
|
77993
78120
|
onSaveDimension(dimensionData);
|
|
77994
78121
|
handleCancel();
|
|
@@ -77999,8 +78126,9 @@ const Dimensions = ({
|
|
|
77999
78126
|
const handleAddAll = () => {
|
|
78000
78127
|
const dimensionItems = getDimensionItems();
|
|
78001
78128
|
|
|
78002
|
-
// Filter out already selected dimensions
|
|
78003
|
-
|
|
78129
|
+
// Filter out already selected dimensions - "Add all" only fills in
|
|
78130
|
+
// missing ones; intentional duplicates are added one at a time above.
|
|
78131
|
+
const availableItems = dimensionItems.filter(item => !item.alreadySelected);
|
|
78004
78132
|
if (availableItems.length === 0) return;
|
|
78005
78133
|
|
|
78006
78134
|
// Build the complete relation objects array (same for all dimensions in this provider)
|
|
@@ -78045,7 +78173,10 @@ const Dimensions = ({
|
|
|
78045
78173
|
dimensionKey: item.dimensionKey,
|
|
78046
78174
|
dimensionTitle: item.value,
|
|
78047
78175
|
// The constructed path for server
|
|
78048
|
-
fullPath: fullPath
|
|
78176
|
+
fullPath: fullPath,
|
|
78177
|
+
// "Add all" only ever adds fields that aren't already selected, so
|
|
78178
|
+
// this is always the first (and only) occurrence of this fullPath.
|
|
78179
|
+
id: fullPath
|
|
78049
78180
|
};
|
|
78050
78181
|
onSaveDimension(dimensionData);
|
|
78051
78182
|
});
|
|
@@ -78179,7 +78310,7 @@ const Dimensions = ({
|
|
|
78179
78310
|
}, "Cancel"), /*#__PURE__*/gn.createElement(Button, {
|
|
78180
78311
|
variant: "contained",
|
|
78181
78312
|
onClick: handleSaveComputed,
|
|
78182
|
-
disabled: !computedName.trim() ||
|
|
78313
|
+
disabled: !computedName.trim() || computedNameError,
|
|
78183
78314
|
sx: {
|
|
78184
78315
|
height: "40px",
|
|
78185
78316
|
fontFamily: "system-ui",
|
|
@@ -78255,7 +78386,7 @@ const Dimensions = ({
|
|
|
78255
78386
|
placement: "top"
|
|
78256
78387
|
}, /*#__PURE__*/gn.createElement("span", null, /*#__PURE__*/gn.createElement(IconButton, {
|
|
78257
78388
|
onClick: handleAddAll,
|
|
78258
|
-
disabled: getDimensionItems().filter(item => !item.
|
|
78389
|
+
disabled: getDimensionItems().filter(item => !item.alreadySelected).length === 0,
|
|
78259
78390
|
"aria-label": "add all dimensions",
|
|
78260
78391
|
sx: {
|
|
78261
78392
|
color: "rgb(70, 134, 128)",
|
|
@@ -78330,19 +78461,10 @@ const Dimensions = ({
|
|
|
78330
78461
|
color: "#666",
|
|
78331
78462
|
marginBottom: 2
|
|
78332
78463
|
}
|
|
78333
|
-
}, "Drag to reorder or use arrows"), /*#__PURE__*/gn.createElement(
|
|
78464
|
+
}, "Drag to reorder or use arrows"), /*#__PURE__*/gn.createElement(SortableListContext, {
|
|
78334
78465
|
sensors: sensors,
|
|
78335
|
-
|
|
78466
|
+
itemIds: orderedItems.map((_, index) => index),
|
|
78336
78467
|
onDragEnd: handleDragEnd
|
|
78337
|
-
}, /*#__PURE__*/gn.createElement(SortableContext, {
|
|
78338
|
-
items: orderedItems.map((_, index) => index),
|
|
78339
|
-
strategy: verticalListSortingStrategy
|
|
78340
|
-
}, /*#__PURE__*/gn.createElement(Box, {
|
|
78341
|
-
sx: {
|
|
78342
|
-
display: "flex",
|
|
78343
|
-
flexDirection: "column",
|
|
78344
|
-
gap: 1
|
|
78345
|
-
}
|
|
78346
78468
|
}, orderedItems.map((item, index) => item.kind === "dimension" ? /*#__PURE__*/gn.createElement(SortableChip$1, {
|
|
78347
78469
|
key: `dim-${item.key}`,
|
|
78348
78470
|
id: index,
|
|
@@ -78355,9 +78477,9 @@ const Dimensions = ({
|
|
|
78355
78477
|
isLast: index === orderedItems.length - 1,
|
|
78356
78478
|
sortOrder: item.data.sortOrder || null,
|
|
78357
78479
|
onSortOrderChange: sortOrder => handleSortOrderChange(item.key, sortOrder),
|
|
78358
|
-
|
|
78480
|
+
instanceId: item.data.id,
|
|
78359
78481
|
defaultTitle: item.data.dimensionTitle,
|
|
78360
|
-
customTitle: titleOverrides[item.data.
|
|
78482
|
+
customTitle: titleOverrides[item.data.id],
|
|
78361
78483
|
onUpdateTitle: onUpdateTitle,
|
|
78362
78484
|
onResetTitle: onResetTitle
|
|
78363
78485
|
}) : /*#__PURE__*/gn.createElement(SortableComputedChip, {
|
|
@@ -78372,7 +78494,7 @@ const Dimensions = ({
|
|
|
78372
78494
|
onMoveDown: () => handleMoveDown(index),
|
|
78373
78495
|
isFirst: index === 0,
|
|
78374
78496
|
isLast: index === orderedItems.length - 1
|
|
78375
|
-
})))))
|
|
78497
|
+
})))));
|
|
78376
78498
|
};
|
|
78377
78499
|
|
|
78378
78500
|
var StorageIcon = createSvgIcon$1(/*#__PURE__*/u("path", {
|
|
@@ -78442,7 +78564,7 @@ const SortableChip = ({
|
|
|
78442
78564
|
onMoveDown,
|
|
78443
78565
|
isFirst,
|
|
78444
78566
|
isLast,
|
|
78445
|
-
|
|
78567
|
+
instanceId,
|
|
78446
78568
|
defaultTitle,
|
|
78447
78569
|
customTitle,
|
|
78448
78570
|
onUpdateTitle,
|
|
@@ -78459,20 +78581,8 @@ const SortableChip = ({
|
|
|
78459
78581
|
attributes,
|
|
78460
78582
|
listeners,
|
|
78461
78583
|
setNodeRef,
|
|
78462
|
-
|
|
78463
|
-
|
|
78464
|
-
isDragging
|
|
78465
|
-
} = useSortable({
|
|
78466
|
-
id
|
|
78467
|
-
});
|
|
78468
|
-
const style = {
|
|
78469
|
-
transform: CSS$1.Transform.toString(transform),
|
|
78470
|
-
transition,
|
|
78471
|
-
opacity: isDragging ? 0.5 : 1,
|
|
78472
|
-
display: 'flex',
|
|
78473
|
-
alignItems: 'center',
|
|
78474
|
-
width: '100%'
|
|
78475
|
-
};
|
|
78584
|
+
style
|
|
78585
|
+
} = useSortableRow(id);
|
|
78476
78586
|
|
|
78477
78587
|
// Focus input when entering edit mode
|
|
78478
78588
|
h(() => {
|
|
@@ -78507,7 +78617,7 @@ const SortableChip = ({
|
|
|
78507
78617
|
handleCancel();
|
|
78508
78618
|
return;
|
|
78509
78619
|
}
|
|
78510
|
-
onUpdateTitle(
|
|
78620
|
+
onUpdateTitle(instanceId, trimmedValue);
|
|
78511
78621
|
setIsEditing(false);
|
|
78512
78622
|
};
|
|
78513
78623
|
const handleCancel = () => {
|
|
@@ -78515,7 +78625,7 @@ const SortableChip = ({
|
|
|
78515
78625
|
setEditValue('');
|
|
78516
78626
|
};
|
|
78517
78627
|
const handleReset = () => {
|
|
78518
|
-
onResetTitle(
|
|
78628
|
+
onResetTitle(instanceId);
|
|
78519
78629
|
setIsEditing(false);
|
|
78520
78630
|
setEditValue('');
|
|
78521
78631
|
};
|
|
@@ -78550,21 +78660,9 @@ const SortableChip = ({
|
|
|
78550
78660
|
opacity: 1 // show icons on hover
|
|
78551
78661
|
}
|
|
78552
78662
|
}
|
|
78553
|
-
}, /*#__PURE__*/gn.createElement(
|
|
78554
|
-
|
|
78555
|
-
|
|
78556
|
-
alignItems: "center",
|
|
78557
|
-
cursor: "grab",
|
|
78558
|
-
"&:active": {
|
|
78559
|
-
cursor: "grabbing"
|
|
78560
|
-
}
|
|
78561
|
-
}
|
|
78562
|
-
}), /*#__PURE__*/gn.createElement(DragIndicatorIcon, {
|
|
78563
|
-
sx: {
|
|
78564
|
-
cursor: "grab",
|
|
78565
|
-
color: "rgba(110, 110, 110, 0.62)"
|
|
78566
|
-
}
|
|
78567
|
-
})), !isEditing ? /*#__PURE__*/gn.createElement(gn.Fragment, null, source && /*#__PURE__*/gn.createElement(Box, {
|
|
78663
|
+
}, /*#__PURE__*/gn.createElement(DragHandle, {
|
|
78664
|
+
listeners: listeners
|
|
78665
|
+
}), !isEditing ? /*#__PURE__*/gn.createElement(gn.Fragment, null, source && /*#__PURE__*/gn.createElement(Box, {
|
|
78568
78666
|
sx: {
|
|
78569
78667
|
display: 'flex',
|
|
78570
78668
|
alignItems: 'center',
|
|
@@ -78626,29 +78724,12 @@ const SortableChip = ({
|
|
|
78626
78724
|
sx: {
|
|
78627
78725
|
flex: 1
|
|
78628
78726
|
}
|
|
78629
|
-
}), /*#__PURE__*/gn.createElement(
|
|
78630
|
-
|
|
78631
|
-
|
|
78632
|
-
|
|
78633
|
-
|
|
78634
|
-
|
|
78635
|
-
transition: "opacity 0.2s"
|
|
78636
|
-
}
|
|
78637
|
-
}, /*#__PURE__*/gn.createElement(IconButton, {
|
|
78638
|
-
size: "small",
|
|
78639
|
-
onClick: onMoveUp,
|
|
78640
|
-
disabled: isFirst,
|
|
78641
|
-
"aria-label": "move up"
|
|
78642
|
-
}, /*#__PURE__*/gn.createElement(ArrowUpwardIcon, {
|
|
78643
|
-
fontSize: "small"
|
|
78644
|
-
})), /*#__PURE__*/gn.createElement(IconButton, {
|
|
78645
|
-
size: "small",
|
|
78646
|
-
onClick: onMoveDown,
|
|
78647
|
-
disabled: isLast,
|
|
78648
|
-
"aria-label": "move down"
|
|
78649
|
-
}, /*#__PURE__*/gn.createElement(ArrowDownwardIcon, {
|
|
78650
|
-
fontSize: "small"
|
|
78651
|
-
})))) : /*#__PURE__*/gn.createElement(gn.Fragment, null, /*#__PURE__*/gn.createElement(TextField, {
|
|
78727
|
+
}), /*#__PURE__*/gn.createElement(MoveButtons, {
|
|
78728
|
+
onMoveUp: onMoveUp,
|
|
78729
|
+
onMoveDown: onMoveDown,
|
|
78730
|
+
isFirst: isFirst,
|
|
78731
|
+
isLast: isLast
|
|
78732
|
+
})) : /*#__PURE__*/gn.createElement(gn.Fragment, null, /*#__PURE__*/gn.createElement(TextField, {
|
|
78652
78733
|
inputRef: inputRef,
|
|
78653
78734
|
value: editValue,
|
|
78654
78735
|
onChange: e => setEditValue(e.target.value),
|
|
@@ -78723,39 +78804,14 @@ const Metrics = ({
|
|
|
78723
78804
|
const [selectedMetric, setSelectedMetric] = d('');
|
|
78724
78805
|
|
|
78725
78806
|
// Setup drag and drop sensors
|
|
78726
|
-
const sensors =
|
|
78727
|
-
coordinateGetter: sortableKeyboardCoordinates
|
|
78728
|
-
}));
|
|
78729
|
-
|
|
78730
|
-
// Handle drag end
|
|
78731
|
-
const handleDragEnd = event => {
|
|
78732
|
-
const {
|
|
78733
|
-
active,
|
|
78734
|
-
over
|
|
78735
|
-
} = event;
|
|
78736
|
-
if (over && active.id !== over.id) {
|
|
78737
|
-
const oldIndex = savedMetrics.findIndex((_, i) => i === active.id);
|
|
78738
|
-
const newIndex = savedMetrics.findIndex((_, i) => i === over.id);
|
|
78739
|
-
const newOrder = arrayMove(savedMetrics, oldIndex, newIndex);
|
|
78740
|
-
onReorderMetrics(newOrder);
|
|
78741
|
-
}
|
|
78742
|
-
};
|
|
78807
|
+
const sensors = useSortableListSensors();
|
|
78743
78808
|
|
|
78744
|
-
// Handle move
|
|
78745
|
-
const
|
|
78746
|
-
|
|
78747
|
-
|
|
78748
|
-
|
|
78749
|
-
|
|
78750
|
-
};
|
|
78751
|
-
|
|
78752
|
-
// Handle move down
|
|
78753
|
-
const handleMoveDown = index => {
|
|
78754
|
-
if (index < savedMetrics.length - 1) {
|
|
78755
|
-
const newOrder = arrayMove(savedMetrics, index, index + 1);
|
|
78756
|
-
onReorderMetrics(newOrder);
|
|
78757
|
-
}
|
|
78758
|
-
};
|
|
78809
|
+
// Handle drag/move
|
|
78810
|
+
const {
|
|
78811
|
+
handleDragEnd,
|
|
78812
|
+
handleMoveUp,
|
|
78813
|
+
handleMoveDown
|
|
78814
|
+
} = makeListReorderHandlers(savedMetrics, onReorderMetrics);
|
|
78759
78815
|
|
|
78760
78816
|
// Get the current provider based on selection chain
|
|
78761
78817
|
const getCurrentProvider = () => {
|
|
@@ -78790,7 +78846,10 @@ const Metrics = ({
|
|
|
78790
78846
|
currentProviderKey = selection.targetKey;
|
|
78791
78847
|
});
|
|
78792
78848
|
|
|
78793
|
-
// Create a Set of already selected metric fullPaths for quick lookup
|
|
78849
|
+
// Create a Set of already selected metric fullPaths for quick lookup.
|
|
78850
|
+
// Already-selected items stay selectable (so the same metric can be
|
|
78851
|
+
// added again on purpose) - they're just flagged so the dropdown can
|
|
78852
|
+
// highlight them instead of blocking the click.
|
|
78794
78853
|
const selectedFullPaths = new Set(savedMetrics.map(metric => metric.fullPath));
|
|
78795
78854
|
|
|
78796
78855
|
// Metrics are a direct array, not nested by alias
|
|
@@ -78798,15 +78857,12 @@ const Metrics = ({
|
|
|
78798
78857
|
const items = provider.metrics.map((metric, index) => {
|
|
78799
78858
|
// Construct the fullPath for this metric
|
|
78800
78859
|
const fullPath = `${aliasPath.join('_')}.${metric.name}`;
|
|
78801
|
-
|
|
78802
|
-
// Check if this metric is already selected
|
|
78803
|
-
const isAlreadySelected = selectedFullPaths.has(fullPath);
|
|
78804
78860
|
return {
|
|
78805
78861
|
key: `${currentProvider}_${metric.name}_${index}`,
|
|
78806
78862
|
value: metric.title || metric.name,
|
|
78807
78863
|
metricName: metric.name,
|
|
78808
78864
|
metric: metric,
|
|
78809
|
-
|
|
78865
|
+
alreadySelected: selectedFullPaths.has(fullPath),
|
|
78810
78866
|
icon: metric.source ? /*#__PURE__*/gn.createElement(MetricSourceIcon, {
|
|
78811
78867
|
source: metric.source
|
|
78812
78868
|
}) : undefined
|
|
@@ -78869,7 +78925,11 @@ const Metrics = ({
|
|
|
78869
78925
|
metricName: selectedItem.metricName,
|
|
78870
78926
|
metricTitle: selectedItem.value,
|
|
78871
78927
|
// The constructed path for server
|
|
78872
|
-
fullPath: fullPath
|
|
78928
|
+
fullPath: fullPath,
|
|
78929
|
+
// Unique per-instance identity, so this field can be added more
|
|
78930
|
+
// than once and each occurrence stays independently
|
|
78931
|
+
// orderable/removable/titleable.
|
|
78932
|
+
id: nextInstanceId(fullPath, savedMetrics)
|
|
78873
78933
|
};
|
|
78874
78934
|
onSaveMetric(metricData);
|
|
78875
78935
|
handleCancel();
|
|
@@ -78880,8 +78940,9 @@ const Metrics = ({
|
|
|
78880
78940
|
const handleAddAll = () => {
|
|
78881
78941
|
const metricItems = getMetricItems();
|
|
78882
78942
|
|
|
78883
|
-
// Filter out already selected metrics
|
|
78884
|
-
|
|
78943
|
+
// Filter out already selected metrics - "Add all" only fills in
|
|
78944
|
+
// missing ones; intentional duplicates are added one at a time above.
|
|
78945
|
+
const availableItems = metricItems.filter(item => !item.alreadySelected);
|
|
78885
78946
|
if (availableItems.length === 0) return;
|
|
78886
78947
|
|
|
78887
78948
|
// Build the complete relation objects array (same for all metrics in this provider)
|
|
@@ -78926,7 +78987,10 @@ const Metrics = ({
|
|
|
78926
78987
|
metricName: item.metricName,
|
|
78927
78988
|
metricTitle: item.value,
|
|
78928
78989
|
// The constructed path for server
|
|
78929
|
-
fullPath: fullPath
|
|
78990
|
+
fullPath: fullPath,
|
|
78991
|
+
// "Add all" only ever adds fields that aren't already
|
|
78992
|
+
// selected, so this is always the first occurrence.
|
|
78993
|
+
id: fullPath
|
|
78930
78994
|
};
|
|
78931
78995
|
onSaveMetric(metricData);
|
|
78932
78996
|
});
|
|
@@ -79025,7 +79089,7 @@ const Metrics = ({
|
|
|
79025
79089
|
placement: "top"
|
|
79026
79090
|
}, /*#__PURE__*/gn.createElement("span", null, /*#__PURE__*/gn.createElement(IconButton, {
|
|
79027
79091
|
onClick: handleAddAll,
|
|
79028
|
-
disabled: getMetricItems().filter(item => !item.
|
|
79092
|
+
disabled: getMetricItems().filter(item => !item.alreadySelected).length === 0,
|
|
79029
79093
|
"aria-label": "add all metrics",
|
|
79030
79094
|
sx: {
|
|
79031
79095
|
color: "rgb(70, 134, 128)",
|
|
@@ -79094,36 +79158,27 @@ const Metrics = ({
|
|
|
79094
79158
|
marginBottom: 1,
|
|
79095
79159
|
color: "rgb(37, 37, 37)"
|
|
79096
79160
|
}
|
|
79097
|
-
}, "Saved Metrics"), /*#__PURE__*/gn.createElement(
|
|
79161
|
+
}, "Saved Metrics"), /*#__PURE__*/gn.createElement(SortableListContext, {
|
|
79098
79162
|
sensors: sensors,
|
|
79099
|
-
|
|
79163
|
+
itemIds: savedMetrics.map((_, index) => index),
|
|
79100
79164
|
onDragEnd: handleDragEnd
|
|
79101
|
-
}, /*#__PURE__*/gn.createElement(SortableContext, {
|
|
79102
|
-
items: savedMetrics.map((_, index) => index),
|
|
79103
|
-
strategy: verticalListSortingStrategy
|
|
79104
|
-
}, /*#__PURE__*/gn.createElement(Box, {
|
|
79105
|
-
sx: {
|
|
79106
|
-
display: 'flex',
|
|
79107
|
-
flexDirection: 'column',
|
|
79108
|
-
gap: 1
|
|
79109
|
-
}
|
|
79110
79165
|
}, savedMetrics.map((metric, index) => /*#__PURE__*/gn.createElement(SortableChip, {
|
|
79111
79166
|
key: index,
|
|
79112
79167
|
id: index,
|
|
79113
79168
|
label: metric.metricTitle,
|
|
79114
79169
|
fullLabel: `${formatProviderPath(metric)} → ${metric.metricTitle} (${metric.fullPath})`,
|
|
79115
|
-
onDelete: () => onRemoveMetric(
|
|
79170
|
+
onDelete: () => onRemoveMetric(metric.id),
|
|
79116
79171
|
onMoveUp: () => handleMoveUp(index),
|
|
79117
79172
|
onMoveDown: () => handleMoveDown(index),
|
|
79118
79173
|
isFirst: index === 0,
|
|
79119
79174
|
isLast: index === savedMetrics.length - 1,
|
|
79120
|
-
|
|
79175
|
+
instanceId: metric.id,
|
|
79121
79176
|
defaultTitle: metric.metricTitle,
|
|
79122
|
-
customTitle: titleOverrides[metric.
|
|
79177
|
+
customTitle: titleOverrides[metric.id],
|
|
79123
79178
|
onUpdateTitle: onUpdateTitle,
|
|
79124
79179
|
onResetTitle: onResetTitle,
|
|
79125
79180
|
source: metric.metric?.source
|
|
79126
|
-
})))))
|
|
79181
|
+
})))));
|
|
79127
79182
|
};
|
|
79128
79183
|
|
|
79129
79184
|
var FilterAltIcon = createSvgIcon$1(/*#__PURE__*/u("path", {
|
|
@@ -91292,7 +91347,12 @@ const Filters = ({
|
|
|
91292
91347
|
mb: 3
|
|
91293
91348
|
}
|
|
91294
91349
|
}, /*#__PURE__*/gn.createElement(SingleSelect, {
|
|
91295
|
-
items:
|
|
91350
|
+
items: Array.from(
|
|
91351
|
+
// Dimensions can now be added more than once (same fullPath,
|
|
91352
|
+
// different instance), but a filter targets the underlying
|
|
91353
|
+
// field regardless of how many columns display it - so this
|
|
91354
|
+
// list is deduped by fullPath to avoid showing it twice.
|
|
91355
|
+
new Map(existingDimensions.map(dim => [dim.fullPath, dim])).values()).map(dim => ({
|
|
91296
91356
|
key: dim.fullPath,
|
|
91297
91357
|
value: interpolateTitle(dimensionTitleOverrides[dim.fullPath] || dim.dimensionTitle, parameters)
|
|
91298
91358
|
})).sort((a, b) => a.value.localeCompare(b.value)),
|
|
@@ -91686,6 +91746,209 @@ const Filters = ({
|
|
|
91686
91746
|
}))));
|
|
91687
91747
|
};
|
|
91688
91748
|
|
|
91749
|
+
var CalculateIcon = createSvgIcon$1(/*#__PURE__*/u("path", {
|
|
91750
|
+
d: "M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2m-5.97 4.06L14.09 6l1.41 1.41L16.91 6l1.06 1.06-1.41 1.41 1.41 1.41-1.06 1.06-1.41-1.4-1.41 1.41-1.06-1.06 1.41-1.41zm-6.78.66h5v1.5h-5zM11.5 16h-2v2H8v-2H6v-1.5h2v-2h1.5v2h2zm6.5 1.25h-5v-1.5h5zm0-2.5h-5v-1.5h5z"
|
|
91751
|
+
}));
|
|
91752
|
+
|
|
91753
|
+
const KIND_LABEL = {
|
|
91754
|
+
dimension: "Dimension",
|
|
91755
|
+
computed: "Computed",
|
|
91756
|
+
metric: "Metric"
|
|
91757
|
+
};
|
|
91758
|
+
const KindChip = ({
|
|
91759
|
+
kind,
|
|
91760
|
+
source
|
|
91761
|
+
}) => {
|
|
91762
|
+
if (kind === "metric") {
|
|
91763
|
+
return /*#__PURE__*/gn.createElement(Box, {
|
|
91764
|
+
sx: {
|
|
91765
|
+
display: "flex",
|
|
91766
|
+
alignItems: "center",
|
|
91767
|
+
flexShrink: 0
|
|
91768
|
+
}
|
|
91769
|
+
}, /*#__PURE__*/gn.createElement(MetricSourceIcon, {
|
|
91770
|
+
source: source
|
|
91771
|
+
}));
|
|
91772
|
+
}
|
|
91773
|
+
return /*#__PURE__*/gn.createElement(Chip, {
|
|
91774
|
+
icon: kind === "computed" ? /*#__PURE__*/gn.createElement(CalculateIcon, {
|
|
91775
|
+
sx: {
|
|
91776
|
+
fontSize: "15px !important"
|
|
91777
|
+
}
|
|
91778
|
+
}) : undefined,
|
|
91779
|
+
label: KIND_LABEL[kind],
|
|
91780
|
+
size: "small",
|
|
91781
|
+
sx: {
|
|
91782
|
+
fontWeight: 500,
|
|
91783
|
+
backgroundColor: "rgba(70, 134, 128, 0.1)",
|
|
91784
|
+
color: "rgb(70, 134, 128)",
|
|
91785
|
+
flexShrink: 0
|
|
91786
|
+
}
|
|
91787
|
+
});
|
|
91788
|
+
};
|
|
91789
|
+
|
|
91790
|
+
// Read-only sortable row: this tab only reorders columns. Adding, removing,
|
|
91791
|
+
// and renaming stay on the Dimensions/Metrics tabs so there's one place each
|
|
91792
|
+
// field is managed.
|
|
91793
|
+
const SortableColumnRow = ({
|
|
91794
|
+
id,
|
|
91795
|
+
label,
|
|
91796
|
+
fullPath,
|
|
91797
|
+
kind,
|
|
91798
|
+
source,
|
|
91799
|
+
onMoveUp,
|
|
91800
|
+
onMoveDown,
|
|
91801
|
+
isFirst,
|
|
91802
|
+
isLast
|
|
91803
|
+
}) => {
|
|
91804
|
+
const reportingContext = useReportingContextOptional();
|
|
91805
|
+
const parameters = reportingContext?.parameters;
|
|
91806
|
+
const {
|
|
91807
|
+
attributes,
|
|
91808
|
+
listeners,
|
|
91809
|
+
setNodeRef,
|
|
91810
|
+
style
|
|
91811
|
+
} = useSortableRow(id);
|
|
91812
|
+
const displayLabel = interpolateTitle(label, parameters);
|
|
91813
|
+
return /*#__PURE__*/gn.createElement("div", _extends({
|
|
91814
|
+
ref: setNodeRef,
|
|
91815
|
+
style: style
|
|
91816
|
+
}, attributes), /*#__PURE__*/gn.createElement(Box, {
|
|
91817
|
+
sx: {
|
|
91818
|
+
display: "flex",
|
|
91819
|
+
alignItems: "center",
|
|
91820
|
+
width: "100%",
|
|
91821
|
+
gap: 1,
|
|
91822
|
+
backgroundColor: "white",
|
|
91823
|
+
border: "1px solid #e0e0e0",
|
|
91824
|
+
borderRadius: 2,
|
|
91825
|
+
padding: 1,
|
|
91826
|
+
transition: "transform 0.2s ease, box-shadow 0.2s ease",
|
|
91827
|
+
"&:hover .hover-icons": {
|
|
91828
|
+
opacity: 1
|
|
91829
|
+
}
|
|
91830
|
+
}
|
|
91831
|
+
}, /*#__PURE__*/gn.createElement(DragHandle, {
|
|
91832
|
+
listeners: listeners
|
|
91833
|
+
}), /*#__PURE__*/gn.createElement(KindChip, {
|
|
91834
|
+
kind: kind,
|
|
91835
|
+
source: source
|
|
91836
|
+
}), /*#__PURE__*/gn.createElement(Tooltip, {
|
|
91837
|
+
title: fullPath,
|
|
91838
|
+
arrow: true,
|
|
91839
|
+
placement: "top"
|
|
91840
|
+
}, /*#__PURE__*/gn.createElement(Typography, {
|
|
91841
|
+
variant: "h6",
|
|
91842
|
+
sx: {
|
|
91843
|
+
fontWeight: 500,
|
|
91844
|
+
color: "#1a1a1a",
|
|
91845
|
+
fontSize: "14px",
|
|
91846
|
+
whiteSpace: "nowrap",
|
|
91847
|
+
overflow: "hidden",
|
|
91848
|
+
textOverflow: "ellipsis"
|
|
91849
|
+
}
|
|
91850
|
+
}, displayLabel)), /*#__PURE__*/gn.createElement(Box, {
|
|
91851
|
+
sx: {
|
|
91852
|
+
flex: 1
|
|
91853
|
+
}
|
|
91854
|
+
}), /*#__PURE__*/gn.createElement(MoveButtons, {
|
|
91855
|
+
onMoveUp: onMoveUp,
|
|
91856
|
+
onMoveDown: onMoveDown,
|
|
91857
|
+
isFirst: isFirst,
|
|
91858
|
+
isLast: isLast
|
|
91859
|
+
})));
|
|
91860
|
+
};
|
|
91861
|
+
const ColumnOrder = ({
|
|
91862
|
+
orderedDimensionItems = [],
|
|
91863
|
+
metrics = [],
|
|
91864
|
+
columnOrder = [],
|
|
91865
|
+
onReorderColumnItems,
|
|
91866
|
+
titleOverrides = {
|
|
91867
|
+
dimensions: {},
|
|
91868
|
+
metrics: {}
|
|
91869
|
+
}
|
|
91870
|
+
}) => {
|
|
91871
|
+
const isCustomized = columnOrder.length > 0;
|
|
91872
|
+
const resolvedItems = resolveColumnOrder(columnOrder, orderedDimensionItems, metrics);
|
|
91873
|
+
const sensors = useSortableListSensors();
|
|
91874
|
+
const reorderResolved = newResolvedItems => {
|
|
91875
|
+
onReorderColumnItems(newResolvedItems.map(item => ({
|
|
91876
|
+
type: item.kind,
|
|
91877
|
+
key: item.key
|
|
91878
|
+
})));
|
|
91879
|
+
};
|
|
91880
|
+
const {
|
|
91881
|
+
handleDragEnd,
|
|
91882
|
+
handleMoveUp,
|
|
91883
|
+
handleMoveDown
|
|
91884
|
+
} = makeListReorderHandlers(resolvedItems, reorderResolved);
|
|
91885
|
+
const labelFor = item => {
|
|
91886
|
+
if (item.kind === "computed") return item.data.name;
|
|
91887
|
+
if (item.kind === "metric") {
|
|
91888
|
+
return titleOverrides.metrics[item.key] || item.data.metricTitle || item.key;
|
|
91889
|
+
}
|
|
91890
|
+
return titleOverrides.dimensions[item.key] || item.data.dimensionTitle || item.key;
|
|
91891
|
+
};
|
|
91892
|
+
return /*#__PURE__*/gn.createElement("div", null, /*#__PURE__*/gn.createElement(Box, {
|
|
91893
|
+
sx: {
|
|
91894
|
+
display: "flex",
|
|
91895
|
+
justifyContent: "space-between",
|
|
91896
|
+
alignItems: "flex-start",
|
|
91897
|
+
mb: 2,
|
|
91898
|
+
gap: 2
|
|
91899
|
+
}
|
|
91900
|
+
}, /*#__PURE__*/gn.createElement(Typography, {
|
|
91901
|
+
sx: {
|
|
91902
|
+
fontSize: "13px",
|
|
91903
|
+
color: "#666"
|
|
91904
|
+
}
|
|
91905
|
+
}, "Optional: define a single order for all columns (dimensions, computed dimensions and metrics together) in the results grid. Drag to reorder or use the arrows. Newly added dimensions/metrics are appended to the end automatically. Without a custom order, columns follow the Dimensions tab order, then the Metrics tab order."), isCustomized && /*#__PURE__*/gn.createElement(Tooltip, {
|
|
91906
|
+
title: "Discard the custom order and go back to the default (Dimensions, then Metrics)",
|
|
91907
|
+
arrow: true
|
|
91908
|
+
}, /*#__PURE__*/gn.createElement(Button, {
|
|
91909
|
+
variant: "outlined",
|
|
91910
|
+
size: "small",
|
|
91911
|
+
startIcon: /*#__PURE__*/gn.createElement(RestartAltIcon, null),
|
|
91912
|
+
onClick: () => onReorderColumnItems([]),
|
|
91913
|
+
sx: {
|
|
91914
|
+
height: "36px",
|
|
91915
|
+
flexShrink: 0,
|
|
91916
|
+
fontFamily: "system-ui",
|
|
91917
|
+
fontSize: "13px",
|
|
91918
|
+
fontWeight: 500,
|
|
91919
|
+
borderRadius: "8px",
|
|
91920
|
+
boxShadow: "none",
|
|
91921
|
+
textTransform: "none",
|
|
91922
|
+
borderColor: "#e0e0e0",
|
|
91923
|
+
color: "rgb(37, 37, 37)",
|
|
91924
|
+
"&:hover": {
|
|
91925
|
+
backgroundColor: "#f5f5f5",
|
|
91926
|
+
borderColor: "#d0d0d0"
|
|
91927
|
+
}
|
|
91928
|
+
}
|
|
91929
|
+
}, "Reset to Default"))), resolvedItems.length === 0 ? /*#__PURE__*/gn.createElement(Typography, {
|
|
91930
|
+
sx: {
|
|
91931
|
+
fontSize: "13px",
|
|
91932
|
+
color: "#999"
|
|
91933
|
+
}
|
|
91934
|
+
}, "Add dimensions or metrics in the other tabs first.") : /*#__PURE__*/gn.createElement(SortableListContext, {
|
|
91935
|
+
sensors: sensors,
|
|
91936
|
+
itemIds: resolvedItems.map((_, index) => index),
|
|
91937
|
+
onDragEnd: handleDragEnd
|
|
91938
|
+
}, resolvedItems.map((item, index) => /*#__PURE__*/gn.createElement(SortableColumnRow, {
|
|
91939
|
+
key: `${item.kind}-${item.key}`,
|
|
91940
|
+
id: index,
|
|
91941
|
+
label: labelFor(item),
|
|
91942
|
+
fullPath: item.kind === "computed" ? item.data.value : item.data.fullPath,
|
|
91943
|
+
kind: item.kind,
|
|
91944
|
+
source: item.kind === "metric" ? item.data.metric?.source : undefined,
|
|
91945
|
+
onMoveUp: () => handleMoveUp(index),
|
|
91946
|
+
onMoveDown: () => handleMoveDown(index),
|
|
91947
|
+
isFirst: index === 0,
|
|
91948
|
+
isLast: index === resolvedItems.length - 1
|
|
91949
|
+
}))));
|
|
91950
|
+
};
|
|
91951
|
+
|
|
91689
91952
|
// Default numeral.js formats applied when a dimension/metric definition has a
|
|
91690
91953
|
// known type but no explicit `format` set on the provider.
|
|
91691
91954
|
const DEFAULT_FORMATS_BY_TYPE = {
|
|
@@ -91700,10 +91963,122 @@ const formatValue = (value, def) => {
|
|
|
91700
91963
|
return def?.prefix ? `${def.prefix} ${formatted}` : formatted;
|
|
91701
91964
|
};
|
|
91702
91965
|
const isNumericType = type => type === 'integer' || type === 'currency';
|
|
91966
|
+
|
|
91967
|
+
// Builds a single DataGrid column definition for one resolved column item
|
|
91968
|
+
// (a dimension, computed dimension, or metric). The DataGrid `field` is the
|
|
91969
|
+
// item's instance id (so two duplicate instances of the same underlying
|
|
91970
|
+
// dimension/metric still get distinct columns); the actual cell value is
|
|
91971
|
+
// read via `valueGetter` from `dataFieldName`, the key the API response
|
|
91972
|
+
// actually uses - shared by every duplicate instance, since the backend
|
|
91973
|
+
// only ever returns one value per fullPath. dataFieldName rules:
|
|
91974
|
+
// - Dimensions from base provider: baseAlias.field -> baseAlias_field
|
|
91975
|
+
// - Dimensions from nested provider: baseAlias_rel1_rel2.field -> rel1_rel2_field (drops base alias)
|
|
91976
|
+
// - Computed dimensions have no provider/relations chain, so the API returns
|
|
91977
|
+
// each row keyed by the computed dimension's own `name` (and can't be
|
|
91978
|
+
// duplicated - names must be unique - so field/dataFieldName coincide).
|
|
91979
|
+
// - Metrics: API returns metric keys in different forms depending on path depth
|
|
91980
|
+
// (see comment inline below), normalised to match the row-key normalisation
|
|
91981
|
+
// done when building `rows`.
|
|
91982
|
+
const buildColumn = (item, titleOverrides, parameters) => {
|
|
91983
|
+
if (item.kind === 'computed') {
|
|
91984
|
+
const cd = item.data;
|
|
91985
|
+
return {
|
|
91986
|
+
field: cd.name,
|
|
91987
|
+
headerName: cd.name,
|
|
91988
|
+
flex: 1,
|
|
91989
|
+
minWidth: 150,
|
|
91990
|
+
type: 'string'
|
|
91991
|
+
};
|
|
91992
|
+
}
|
|
91993
|
+
if (item.kind === 'metric') {
|
|
91994
|
+
const metric = item.data;
|
|
91995
|
+
const metricDef = metric.metric;
|
|
91996
|
+
let dataFieldName;
|
|
91997
|
+
const dotCount = (metric.fullPath.match(/\./g) || []).length;
|
|
91998
|
+
if (dotCount >= 2) {
|
|
91999
|
+
// 3+-part namespaced path: API returns the full dotted path as the key.
|
|
92000
|
+
// Normalise dots to underscores to match the row key normalisation below.
|
|
92001
|
+
// Example: "mc.dkpi.activeAgreementsCount" -> "mc_dkpi_activeAgreementsCount"
|
|
92002
|
+
dataFieldName = metric.fullPath.replace(/\./g, '_');
|
|
92003
|
+
} else if (metric.relations && metric.relations.length > 0) {
|
|
92004
|
+
// 2-part path from a nested provider: API drops the base provider alias.
|
|
92005
|
+
// Example: "mc_fa.total_amount" -> "fa_total_amount"
|
|
92006
|
+
const parts = metric.fullPath.split('.');
|
|
92007
|
+
const pathWithoutField = parts[0]; // e.g., "mc_fa"
|
|
92008
|
+
const field = parts[1]; // e.g., "total_amount"
|
|
92009
|
+
|
|
92010
|
+
const pathParts = pathWithoutField.split('_');
|
|
92011
|
+
pathParts.shift(); // remove base provider alias
|
|
92012
|
+
const pathWithoutBase = pathParts.join('_');
|
|
92013
|
+
dataFieldName = pathWithoutBase ? `${pathWithoutBase}_${field}` : field;
|
|
92014
|
+
} else {
|
|
92015
|
+
// 2-part path from the base provider: API returns just the metric name.
|
|
92016
|
+
// Example: "mc.record_count" -> "record_count"
|
|
92017
|
+
dataFieldName = metric.metricName;
|
|
92018
|
+
}
|
|
92019
|
+
const headerName = interpolateTitle(titleOverrides.metrics[metric.id] || metric.metricTitle || dataFieldName, parameters);
|
|
92020
|
+
return {
|
|
92021
|
+
// `field` is the instance id (unique even for duplicate columns
|
|
92022
|
+
// pointing at the same underlying metric); the actual value is
|
|
92023
|
+
// read from the row via valueGetter using dataFieldName, which is
|
|
92024
|
+
// the key the API response actually uses (shared across duplicates).
|
|
92025
|
+
field: metric.id.replace(/[.#]/g, '_'),
|
|
92026
|
+
headerName: headerName,
|
|
92027
|
+
flex: 1,
|
|
92028
|
+
minWidth: 150,
|
|
92029
|
+
type: isNumericType(metricDef?.type) ? 'number' : 'string',
|
|
92030
|
+
valueGetter: (_value, row) => row[dataFieldName],
|
|
92031
|
+
renderHeader: () => /*#__PURE__*/gn.createElement(Box, {
|
|
92032
|
+
sx: {
|
|
92033
|
+
display: 'flex',
|
|
92034
|
+
alignItems: 'center',
|
|
92035
|
+
gap: 0.5
|
|
92036
|
+
}
|
|
92037
|
+
}, /*#__PURE__*/gn.createElement(MetricSourceIcon, {
|
|
92038
|
+
source: metricDef?.source
|
|
92039
|
+
}), /*#__PURE__*/gn.createElement("span", null, headerName)),
|
|
92040
|
+
valueFormatter: value => formatValue(value, metricDef)
|
|
92041
|
+
};
|
|
92042
|
+
}
|
|
92043
|
+
|
|
92044
|
+
// Dimension
|
|
92045
|
+
const dim = item.data;
|
|
92046
|
+
let dataFieldName;
|
|
92047
|
+
if (dim.relations && dim.relations.length > 0) {
|
|
92048
|
+
// From nested provider: drop the base provider alias
|
|
92049
|
+
// Example: ft_fa_db.currency -> fa_db_currency
|
|
92050
|
+
const parts = dim.fullPath.split('.');
|
|
92051
|
+
const pathWithoutField = parts[0]; // e.g., "ft_fa_db"
|
|
92052
|
+
const field = parts[1]; // e.g., "currency"
|
|
92053
|
+
|
|
92054
|
+
const pathParts = pathWithoutField.split('_');
|
|
92055
|
+
pathParts.shift(); // Remove base provider alias
|
|
92056
|
+
const pathWithoutBase = pathParts.join('_'); // e.g., "fa_db"
|
|
92057
|
+
|
|
92058
|
+
dataFieldName = pathWithoutBase ? `${pathWithoutBase}_${field}` : field;
|
|
92059
|
+
} else {
|
|
92060
|
+
// From base provider: keep the full path with underscore
|
|
92061
|
+
// Example: ba.created_at -> ba_created_at
|
|
92062
|
+
dataFieldName = dim.fullPath.replace('.', '_');
|
|
92063
|
+
}
|
|
92064
|
+
const headerName = interpolateTitle(titleOverrides.dimensions[dim.id] || dim.dimensionTitle || dataFieldName, parameters);
|
|
92065
|
+
const dimDef = dim.dimension;
|
|
92066
|
+
return {
|
|
92067
|
+
// Same rationale as the metric branch above: `field` must be unique
|
|
92068
|
+
// per instance, while the value always comes from the one real
|
|
92069
|
+
// underlying row key (dataFieldName), shared by every duplicate.
|
|
92070
|
+
field: dim.id.replace(/[.#]/g, '_'),
|
|
92071
|
+
headerName: headerName,
|
|
92072
|
+
flex: 1,
|
|
92073
|
+
minWidth: 150,
|
|
92074
|
+
type: isNumericType(dimDef?.type) ? 'number' : 'string',
|
|
92075
|
+
valueGetter: (_value, row) => row[dataFieldName],
|
|
92076
|
+
valueFormatter: value => formatValue(value, dimDef)
|
|
92077
|
+
};
|
|
92078
|
+
};
|
|
91703
92079
|
const ReportDataGrid = ({
|
|
91704
92080
|
reportData,
|
|
91705
|
-
|
|
91706
|
-
metrics,
|
|
92081
|
+
columnItems = [],
|
|
91707
92082
|
loading,
|
|
91708
92083
|
onPageChange,
|
|
91709
92084
|
totalRows = 0,
|
|
@@ -91719,127 +92094,10 @@ const ReportDataGrid = ({
|
|
|
91719
92094
|
pageSize: 50
|
|
91720
92095
|
});
|
|
91721
92096
|
|
|
91722
|
-
// Generate columns dynamically
|
|
91723
|
-
|
|
91724
|
-
|
|
91725
|
-
|
|
91726
|
-
// Add dimension + computed-dimension columns, in the single unified
|
|
91727
|
-
// order the user arranged them in on the Dimensions tab.
|
|
91728
|
-
// Field naming logic (dimensions):
|
|
91729
|
-
// - If from base provider: baseAlias.field -> baseAlias_field
|
|
91730
|
-
// - If from nested provider: baseAlias_rel1_rel2.field -> rel1_rel2_field (drops base alias)
|
|
91731
|
-
// Computed dimensions have no provider/relations chain, so the API
|
|
91732
|
-
// returns each row keyed by the computed dimension's own `name`.
|
|
91733
|
-
orderedDimensionItems.forEach(item => {
|
|
91734
|
-
if (item.kind === 'computed') {
|
|
91735
|
-
const cd = item.data;
|
|
91736
|
-
cols.push({
|
|
91737
|
-
field: cd.name,
|
|
91738
|
-
headerName: cd.name,
|
|
91739
|
-
flex: 1,
|
|
91740
|
-
minWidth: 150,
|
|
91741
|
-
type: 'string'
|
|
91742
|
-
});
|
|
91743
|
-
return;
|
|
91744
|
-
}
|
|
91745
|
-
const dim = item.data;
|
|
91746
|
-
let fieldName;
|
|
91747
|
-
let headerName;
|
|
91748
|
-
|
|
91749
|
-
// Check if there are relations (nested providers)
|
|
91750
|
-
if (dim.relations && dim.relations.length > 0) {
|
|
91751
|
-
// From nested provider: drop the base provider alias
|
|
91752
|
-
// Example: ft_fa_db.currency -> fa_db_currency
|
|
91753
|
-
const parts = dim.fullPath.split('.');
|
|
91754
|
-
const pathWithoutField = parts[0]; // e.g., "ft_fa_db"
|
|
91755
|
-
const field = parts[1]; // e.g., "currency"
|
|
91756
|
-
|
|
91757
|
-
// Remove the base provider alias (first part before first underscore)
|
|
91758
|
-
const pathParts = pathWithoutField.split('_');
|
|
91759
|
-
pathParts.shift(); // Remove base provider alias
|
|
91760
|
-
const pathWithoutBase = pathParts.join('_'); // e.g., "fa_db"
|
|
91761
|
-
|
|
91762
|
-
fieldName = pathWithoutBase ? `${pathWithoutBase}_${field}` : field;
|
|
91763
|
-
} else {
|
|
91764
|
-
// From base provider: keep the full path with underscore
|
|
91765
|
-
// Example: ba.created_at -> ba_created_at
|
|
91766
|
-
fieldName = dim.fullPath.replace('.', '_');
|
|
91767
|
-
}
|
|
91768
|
-
|
|
91769
|
-
// Check for title override, otherwise use the friendly dimension title from provider
|
|
91770
|
-
headerName = titleOverrides.dimensions[dim.fullPath] || dim.dimensionTitle || fieldName;
|
|
91771
|
-
headerName = interpolateTitle(headerName, parameters);
|
|
91772
|
-
const dimDef = dim.dimension;
|
|
91773
|
-
cols.push({
|
|
91774
|
-
field: fieldName,
|
|
91775
|
-
headerName: headerName,
|
|
91776
|
-
flex: 1,
|
|
91777
|
-
minWidth: 150,
|
|
91778
|
-
type: isNumericType(dimDef?.type) ? 'number' : 'string',
|
|
91779
|
-
valueFormatter: value => formatValue(value, dimDef)
|
|
91780
|
-
});
|
|
91781
|
-
});
|
|
91782
|
-
|
|
91783
|
-
// Add metric columns
|
|
91784
|
-
// The API returns metric keys in two different forms depending on the path depth:
|
|
91785
|
-
//
|
|
91786
|
-
// • 2-part path "mc.record_count" → API key: "record_count"
|
|
91787
|
-
// • 2-part path "mc_fa.total_amount" (nested) → API key: "fa_total_amount"
|
|
91788
|
-
// • 3+-part path "mc.dkpi.activeAgreementsCount"→ API key: "mc.dkpi.activeAgreementsCount" (dots)
|
|
91789
|
-
//
|
|
91790
|
-
// For 3+-part paths the API key contains dots. MUI DataGrid interprets dots
|
|
91791
|
-
// in field names as nested-object accessors, so the rows useMemo normalises
|
|
91792
|
-
// those keys (dots → underscores). The column field must match that normalised key.
|
|
91793
|
-
metrics.forEach(metric => {
|
|
91794
|
-
const metricDef = metric.metric;
|
|
91795
|
-
let fieldName;
|
|
91796
|
-
let headerName;
|
|
91797
|
-
const dotCount = (metric.fullPath.match(/\./g) || []).length;
|
|
91798
|
-
if (dotCount >= 2) {
|
|
91799
|
-
// 3+-part namespaced path: API returns the full dotted path as the key.
|
|
91800
|
-
// Normalise dots to underscores to match the row key normalisation below.
|
|
91801
|
-
// Example: "mc.dkpi.activeAgreementsCount" -> "mc_dkpi_activeAgreementsCount"
|
|
91802
|
-
fieldName = metric.fullPath.replace(/\./g, '_');
|
|
91803
|
-
} else if (metric.relations && metric.relations.length > 0) {
|
|
91804
|
-
// 2-part path from a nested provider: API drops the base provider alias.
|
|
91805
|
-
// Example: "mc_fa.total_amount" -> "fa_total_amount"
|
|
91806
|
-
const parts = metric.fullPath.split('.');
|
|
91807
|
-
const pathWithoutField = parts[0]; // e.g., "mc_fa"
|
|
91808
|
-
const field = parts[1]; // e.g., "total_amount"
|
|
91809
|
-
|
|
91810
|
-
const pathParts = pathWithoutField.split('_');
|
|
91811
|
-
pathParts.shift(); // remove base provider alias
|
|
91812
|
-
const pathWithoutBase = pathParts.join('_');
|
|
91813
|
-
fieldName = pathWithoutBase ? `${pathWithoutBase}_${field}` : field;
|
|
91814
|
-
} else {
|
|
91815
|
-
// 2-part path from the base provider: API returns just the metric name.
|
|
91816
|
-
// Example: "mc.record_count" -> "record_count"
|
|
91817
|
-
fieldName = metric.metricName;
|
|
91818
|
-
}
|
|
91819
|
-
|
|
91820
|
-
// Check for title override, otherwise use the friendly metric title from provider
|
|
91821
|
-
headerName = titleOverrides.metrics[metric.fullPath] || metric.metricTitle || fieldName;
|
|
91822
|
-
headerName = interpolateTitle(headerName, parameters);
|
|
91823
|
-
cols.push({
|
|
91824
|
-
field: fieldName,
|
|
91825
|
-
headerName: headerName,
|
|
91826
|
-
flex: 1,
|
|
91827
|
-
minWidth: 150,
|
|
91828
|
-
type: isNumericType(metricDef?.type) ? 'number' : 'string',
|
|
91829
|
-
renderHeader: () => /*#__PURE__*/gn.createElement(Box, {
|
|
91830
|
-
sx: {
|
|
91831
|
-
display: 'flex',
|
|
91832
|
-
alignItems: 'center',
|
|
91833
|
-
gap: 0.5
|
|
91834
|
-
}
|
|
91835
|
-
}, /*#__PURE__*/gn.createElement(MetricSourceIcon, {
|
|
91836
|
-
source: metricDef?.source
|
|
91837
|
-
}), /*#__PURE__*/gn.createElement("span", null, headerName)),
|
|
91838
|
-
valueFormatter: value => formatValue(value, metricDef)
|
|
91839
|
-
});
|
|
91840
|
-
});
|
|
91841
|
-
return cols;
|
|
91842
|
-
}, [orderedDimensionItems, metrics, titleOverrides, parameters]);
|
|
92097
|
+
// Generate columns dynamically, in the final resolved column order
|
|
92098
|
+
// (custom Column Order tab arrangement when defined, otherwise
|
|
92099
|
+
// dimensions/computed then metrics).
|
|
92100
|
+
const columns = gn.useMemo(() => columnItems.map(item => buildColumn(item, titleOverrides, parameters)), [columnItems, titleOverrides, parameters]);
|
|
91843
92101
|
|
|
91844
92102
|
// Transform report data to rows with unique IDs.
|
|
91845
92103
|
// Keys are normalised by replacing dots with underscores so that MUI DataGrid
|
|
@@ -91949,7 +92207,12 @@ const ReportBuilder = ({
|
|
|
91949
92207
|
computedDimensions: [],
|
|
91950
92208
|
// Unified display/execution order across dimensions + computedDimensions.
|
|
91951
92209
|
// Entries: { type: 'dimension', key: fullPath } | { type: 'computed', key: name }
|
|
91952
|
-
dimensionOrder: []
|
|
92210
|
+
dimensionOrder: [],
|
|
92211
|
+
// Optional unified display order across dimensions + computedDimensions +
|
|
92212
|
+
// metrics, set on the Column Order tab. Empty means "not defined" — the
|
|
92213
|
+
// results grid falls back to dimensionOrder followed by metrics order.
|
|
92214
|
+
// Entries: { type: 'dimension' | 'computed' | 'metric', key }
|
|
92215
|
+
columnOrder: []
|
|
91953
92216
|
});
|
|
91954
92217
|
const [titleOverrides, setTitleOverrides] = d({
|
|
91955
92218
|
dimensions: {},
|
|
@@ -92135,6 +92398,72 @@ const ReportBuilder = ({
|
|
|
92135
92398
|
}
|
|
92136
92399
|
};
|
|
92137
92400
|
|
|
92401
|
+
// Reconstructs saved dimensions as independent, individually-identified
|
|
92402
|
+
// instances so that deliberate duplicates (the same field added more than
|
|
92403
|
+
// once) survive a save/reload round trip. Prefers `ordered_dimensions`
|
|
92404
|
+
// (one ref per instance - the nth duplicate of a path is suffixed
|
|
92405
|
+
// `#n`) when present; falls back to one instance per entry in the
|
|
92406
|
+
// (necessarily duplicate-free) `dimensions` path list for reports saved
|
|
92407
|
+
// before this field existed.
|
|
92408
|
+
const reconstructDimensions = (reportDef, providersData) => {
|
|
92409
|
+
const orderByMap = {};
|
|
92410
|
+
if (reportDef.definition?.doc?.query?.order_by) {
|
|
92411
|
+
for (const orderItem of reportDef.definition.doc.query.order_by) {
|
|
92412
|
+
orderByMap[orderItem.name] = orderItem.desc === true ? "desc" : "asc";
|
|
92413
|
+
}
|
|
92414
|
+
}
|
|
92415
|
+
const reconstructed = [];
|
|
92416
|
+
const orderedRefs = (reportDef.definition?.doc?.query?.ordered_dimensions || []).filter(entry => entry.type === "dimension").map(entry => entry.ref);
|
|
92417
|
+
if (orderedRefs.length > 0) {
|
|
92418
|
+
orderedRefs.forEach(ref => {
|
|
92419
|
+
const fullPath = ref.split("#")[0];
|
|
92420
|
+
const dim = reconstructDimensionFromPath(fullPath, providersData, reportDef.provider);
|
|
92421
|
+
if (dim) {
|
|
92422
|
+
dim.id = ref;
|
|
92423
|
+
dim.sortOrder = orderByMap[fullPath] || null;
|
|
92424
|
+
reconstructed.push(dim);
|
|
92425
|
+
}
|
|
92426
|
+
});
|
|
92427
|
+
} else if (reportDef.definition?.doc?.query?.dimensions) {
|
|
92428
|
+
for (const dimPath of reportDef.definition.doc.query.dimensions) {
|
|
92429
|
+
const dim = reconstructDimensionFromPath(dimPath, providersData, reportDef.provider);
|
|
92430
|
+
if (dim) {
|
|
92431
|
+
dim.id = dimPath;
|
|
92432
|
+
dim.sortOrder = orderByMap[dimPath] || null;
|
|
92433
|
+
reconstructed.push(dim);
|
|
92434
|
+
}
|
|
92435
|
+
}
|
|
92436
|
+
}
|
|
92437
|
+
return reconstructed;
|
|
92438
|
+
};
|
|
92439
|
+
|
|
92440
|
+
// Mirrors reconstructDimensions for metrics, using the analogous
|
|
92441
|
+
// `ordered_metrics` field (metrics have no other unified-order concept to
|
|
92442
|
+
// fall back on, so this is always written whenever there are metrics).
|
|
92443
|
+
const reconstructMetrics = (reportDef, providersData) => {
|
|
92444
|
+
const reconstructed = [];
|
|
92445
|
+
const orderedRefs = reportDef.definition?.doc?.query?.ordered_metrics || [];
|
|
92446
|
+
if (orderedRefs.length > 0) {
|
|
92447
|
+
orderedRefs.forEach(ref => {
|
|
92448
|
+
const fullPath = ref.split("#")[0];
|
|
92449
|
+
const metric = reconstructMetricFromPath(fullPath, providersData, reportDef.provider);
|
|
92450
|
+
if (metric) {
|
|
92451
|
+
metric.id = ref;
|
|
92452
|
+
reconstructed.push(metric);
|
|
92453
|
+
}
|
|
92454
|
+
});
|
|
92455
|
+
} else if (reportDef.definition?.doc?.query?.metrics) {
|
|
92456
|
+
for (const metricPath of reportDef.definition.doc.query.metrics) {
|
|
92457
|
+
const metric = reconstructMetricFromPath(metricPath, providersData, reportDef.provider);
|
|
92458
|
+
if (metric) {
|
|
92459
|
+
metric.id = metricPath;
|
|
92460
|
+
reconstructed.push(metric);
|
|
92461
|
+
}
|
|
92462
|
+
}
|
|
92463
|
+
}
|
|
92464
|
+
return reconstructed;
|
|
92465
|
+
};
|
|
92466
|
+
|
|
92138
92467
|
// Build the unified display/execution order for dimensions + computed
|
|
92139
92468
|
// dimensions. Prefers the persisted `ordered_dimensions` field; falls back
|
|
92140
92469
|
// to "dimensions first, then computed dimensions" for report definitions
|
|
@@ -92145,7 +92474,7 @@ const ReportBuilder = ({
|
|
|
92145
92474
|
const seenComputedKeys = new Set();
|
|
92146
92475
|
if (Array.isArray(rawOrderedDimensions)) {
|
|
92147
92476
|
rawOrderedDimensions.forEach(entry => {
|
|
92148
|
-
if (entry.type === "dimension" && dimensions.some(d => d.
|
|
92477
|
+
if (entry.type === "dimension" && dimensions.some(d => d.id === entry.ref)) {
|
|
92149
92478
|
dimensionOrder.push({
|
|
92150
92479
|
type: "dimension",
|
|
92151
92480
|
key: entry.ref
|
|
@@ -92163,10 +92492,10 @@ const ReportBuilder = ({
|
|
|
92163
92492
|
|
|
92164
92493
|
// Append anything not covered by ordered_dimensions (older reports, or drift)
|
|
92165
92494
|
dimensions.forEach(d => {
|
|
92166
|
-
if (!seenDimensionKeys.has(d.
|
|
92495
|
+
if (!seenDimensionKeys.has(d.id)) {
|
|
92167
92496
|
dimensionOrder.push({
|
|
92168
92497
|
type: "dimension",
|
|
92169
|
-
key: d.
|
|
92498
|
+
key: d.id
|
|
92170
92499
|
});
|
|
92171
92500
|
}
|
|
92172
92501
|
});
|
|
@@ -92180,6 +92509,46 @@ const ReportBuilder = ({
|
|
|
92180
92509
|
});
|
|
92181
92510
|
return dimensionOrder;
|
|
92182
92511
|
};
|
|
92512
|
+
|
|
92513
|
+
// Build the optional unified column order (dimensions + computed
|
|
92514
|
+
// dimensions + metrics) from the persisted `ordered_columns` field.
|
|
92515
|
+
// Unlike buildDimensionOrder, entries that no longer resolve are simply
|
|
92516
|
+
// dropped rather than backfilled — an empty/partial result just means "no
|
|
92517
|
+
// custom order defined (yet)", which resolveColumnOrder treats as
|
|
92518
|
+
// "fall back to the default order".
|
|
92519
|
+
const buildColumnOrder = (rawOrderedColumns, dimensions, computedDimensions, metrics) => {
|
|
92520
|
+
if (!Array.isArray(rawOrderedColumns)) return [];
|
|
92521
|
+
const dimensionIds = new Set(dimensions.map(d => d.id));
|
|
92522
|
+
const computedNames = new Set(computedDimensions.map(cd => cd.name));
|
|
92523
|
+
const metricIds = new Set(metrics.map(m => m.id));
|
|
92524
|
+
const columnOrder = [];
|
|
92525
|
+
rawOrderedColumns.forEach(entry => {
|
|
92526
|
+
if (entry.type === "dimension" && dimensionIds.has(entry.ref)) {
|
|
92527
|
+
columnOrder.push({
|
|
92528
|
+
type: "dimension",
|
|
92529
|
+
key: entry.ref
|
|
92530
|
+
});
|
|
92531
|
+
} else if (entry.type === "computed" && computedNames.has(entry.ref)) {
|
|
92532
|
+
columnOrder.push({
|
|
92533
|
+
type: "computed",
|
|
92534
|
+
key: entry.ref
|
|
92535
|
+
});
|
|
92536
|
+
} else if (entry.type === "metric" && metricIds.has(entry.ref)) {
|
|
92537
|
+
columnOrder.push({
|
|
92538
|
+
type: "metric",
|
|
92539
|
+
key: entry.ref
|
|
92540
|
+
});
|
|
92541
|
+
}
|
|
92542
|
+
});
|
|
92543
|
+
return columnOrder;
|
|
92544
|
+
};
|
|
92545
|
+
|
|
92546
|
+
// Auto-append a newly added dimension/computed dimension/metric to the end
|
|
92547
|
+
// of columnOrder — but only once a custom order actually exists. While
|
|
92548
|
+
// columnOrder is empty (no custom order defined) it's left untouched, so
|
|
92549
|
+
// the results grid keeps falling back to the default dimensions-then-
|
|
92550
|
+
// metrics order until the user visits the Column Order tab.
|
|
92551
|
+
const appendToColumnOrder = (columnOrder, entry) => columnOrder.length > 0 ? [...columnOrder, entry] : columnOrder;
|
|
92183
92552
|
const loadReportDefinition = async id => {
|
|
92184
92553
|
try {
|
|
92185
92554
|
console.log("Loading report definition:", id);
|
|
@@ -92194,43 +92563,10 @@ const ReportBuilder = ({
|
|
|
92194
92563
|
// Set the title
|
|
92195
92564
|
setReportTitle(reportDef.title || "");
|
|
92196
92565
|
|
|
92197
|
-
// Reconstruct dimensions
|
|
92198
|
-
|
|
92199
|
-
const
|
|
92200
|
-
|
|
92201
|
-
// First, build a map of fullPath -> sortOrder from order_by
|
|
92202
|
-
if (reportDef.definition?.doc?.query?.order_by) {
|
|
92203
|
-
for (const orderItem of reportDef.definition.doc.query.order_by) {
|
|
92204
|
-
if (orderItem.desc === true) {
|
|
92205
|
-
orderByMap[orderItem.name] = "desc";
|
|
92206
|
-
} else {
|
|
92207
|
-
orderByMap[orderItem.name] = "asc";
|
|
92208
|
-
}
|
|
92209
|
-
}
|
|
92210
|
-
}
|
|
92211
|
-
|
|
92212
|
-
// Now reconstruct dimensions and add sortOrder from the map
|
|
92213
|
-
if (reportDef.definition?.doc?.query?.dimensions) {
|
|
92214
|
-
for (const dimPath of reportDef.definition.doc.query.dimensions) {
|
|
92215
|
-
const dim = reconstructDimensionFromPath(dimPath, providersData, reportDef.provider);
|
|
92216
|
-
if (dim) {
|
|
92217
|
-
// Add sortOrder from order_by if it exists
|
|
92218
|
-
dim.sortOrder = orderByMap[dimPath] || null;
|
|
92219
|
-
reconstructedDimensions.push(dim);
|
|
92220
|
-
}
|
|
92221
|
-
}
|
|
92222
|
-
}
|
|
92223
|
-
|
|
92224
|
-
// Reconstruct metrics
|
|
92225
|
-
const reconstructedMetrics = [];
|
|
92226
|
-
if (reportDef.definition?.doc?.query?.metrics) {
|
|
92227
|
-
for (const metricPath of reportDef.definition.doc.query.metrics) {
|
|
92228
|
-
const metric = reconstructMetricFromPath(metricPath, providersData, reportDef.provider);
|
|
92229
|
-
if (metric) {
|
|
92230
|
-
reconstructedMetrics.push(metric);
|
|
92231
|
-
}
|
|
92232
|
-
}
|
|
92233
|
-
}
|
|
92566
|
+
// Reconstruct dimensions and metrics as independent instances
|
|
92567
|
+
// (preserving deliberate duplicates - see reconstructDimensions/Metrics)
|
|
92568
|
+
const reconstructedDimensions = reconstructDimensions(reportDef, providersData);
|
|
92569
|
+
const reconstructedMetrics = reconstructMetrics(reportDef, providersData);
|
|
92234
92570
|
|
|
92235
92571
|
// Load title overrides if they exist
|
|
92236
92572
|
const loadedTitleOverrides = {
|
|
@@ -92276,6 +92612,7 @@ const ReportBuilder = ({
|
|
|
92276
92612
|
// provider/relation chain, so they're loaded as-is (no reconstruction needed)
|
|
92277
92613
|
const computedDimensions = reportDef.definition?.doc?.query?.computed_dimensions || [];
|
|
92278
92614
|
const dimensionOrder = buildDimensionOrder(reportDef.definition?.doc?.query?.ordered_dimensions, reconstructedDimensions, computedDimensions);
|
|
92615
|
+
const columnOrder = buildColumnOrder(reportDef.definition?.doc?.query?.ordered_columns, reconstructedDimensions, computedDimensions, reconstructedMetrics);
|
|
92279
92616
|
|
|
92280
92617
|
// Set the report state
|
|
92281
92618
|
setReport({
|
|
@@ -92283,7 +92620,8 @@ const ReportBuilder = ({
|
|
|
92283
92620
|
metrics: reconstructedMetrics,
|
|
92284
92621
|
filters: loadedFilters,
|
|
92285
92622
|
computedDimensions,
|
|
92286
|
-
dimensionOrder
|
|
92623
|
+
dimensionOrder,
|
|
92624
|
+
columnOrder
|
|
92287
92625
|
});
|
|
92288
92626
|
|
|
92289
92627
|
// Set title overrides
|
|
@@ -92312,43 +92650,10 @@ const ReportBuilder = ({
|
|
|
92312
92650
|
// Set the title (already modified with " (Copy)" suffix)
|
|
92313
92651
|
setReportTitle(reportDef.title || "");
|
|
92314
92652
|
|
|
92315
|
-
// Reconstruct dimensions
|
|
92316
|
-
|
|
92317
|
-
const
|
|
92318
|
-
|
|
92319
|
-
// First, build a map of fullPath -> sortOrder from order_by
|
|
92320
|
-
if (reportDef.definition?.doc?.query?.order_by) {
|
|
92321
|
-
for (const orderItem of reportDef.definition.doc.query.order_by) {
|
|
92322
|
-
if (orderItem.desc === true) {
|
|
92323
|
-
orderByMap[orderItem.name] = "desc";
|
|
92324
|
-
} else {
|
|
92325
|
-
orderByMap[orderItem.name] = "asc";
|
|
92326
|
-
}
|
|
92327
|
-
}
|
|
92328
|
-
}
|
|
92329
|
-
|
|
92330
|
-
// Now reconstruct dimensions and add sortOrder from the map
|
|
92331
|
-
if (reportDef.definition?.doc?.query?.dimensions) {
|
|
92332
|
-
for (const dimPath of reportDef.definition.doc.query.dimensions) {
|
|
92333
|
-
const dim = reconstructDimensionFromPath(dimPath, providersData, reportDef.provider);
|
|
92334
|
-
if (dim) {
|
|
92335
|
-
// Add sortOrder from order_by if it exists
|
|
92336
|
-
dim.sortOrder = orderByMap[dimPath] || null;
|
|
92337
|
-
reconstructedDimensions.push(dim);
|
|
92338
|
-
}
|
|
92339
|
-
}
|
|
92340
|
-
}
|
|
92341
|
-
|
|
92342
|
-
// Reconstruct metrics
|
|
92343
|
-
const reconstructedMetrics = [];
|
|
92344
|
-
if (reportDef.definition?.doc?.query?.metrics) {
|
|
92345
|
-
for (const metricPath of reportDef.definition.doc.query.metrics) {
|
|
92346
|
-
const metric = reconstructMetricFromPath(metricPath, providersData, reportDef.provider);
|
|
92347
|
-
if (metric) {
|
|
92348
|
-
reconstructedMetrics.push(metric);
|
|
92349
|
-
}
|
|
92350
|
-
}
|
|
92351
|
-
}
|
|
92653
|
+
// Reconstruct dimensions and metrics as independent instances
|
|
92654
|
+
// (preserving deliberate duplicates - see reconstructDimensions/Metrics)
|
|
92655
|
+
const reconstructedDimensions = reconstructDimensions(reportDef, providersData);
|
|
92656
|
+
const reconstructedMetrics = reconstructMetrics(reportDef, providersData);
|
|
92352
92657
|
|
|
92353
92658
|
// Load title overrides if they exist
|
|
92354
92659
|
const loadedTitleOverrides = {
|
|
@@ -92393,6 +92698,7 @@ const ReportBuilder = ({
|
|
|
92393
92698
|
// provider/relation chain, so they're loaded as-is (no reconstruction needed)
|
|
92394
92699
|
const computedDimensions = reportDef.definition?.doc?.query?.computed_dimensions || [];
|
|
92395
92700
|
const dimensionOrder = buildDimensionOrder(reportDef.definition?.doc?.query?.ordered_dimensions, reconstructedDimensions, computedDimensions);
|
|
92701
|
+
const columnOrder = buildColumnOrder(reportDef.definition?.doc?.query?.ordered_columns, reconstructedDimensions, computedDimensions, reconstructedMetrics);
|
|
92396
92702
|
|
|
92397
92703
|
// Set the report state
|
|
92398
92704
|
setReport({
|
|
@@ -92400,7 +92706,8 @@ const ReportBuilder = ({
|
|
|
92400
92706
|
metrics: reconstructedMetrics,
|
|
92401
92707
|
filters: loadedFilters,
|
|
92402
92708
|
computedDimensions,
|
|
92403
|
-
dimensionOrder
|
|
92709
|
+
dimensionOrder,
|
|
92710
|
+
columnOrder
|
|
92404
92711
|
});
|
|
92405
92712
|
|
|
92406
92713
|
// Set title overrides
|
|
@@ -92434,7 +92741,8 @@ const ReportBuilder = ({
|
|
|
92434
92741
|
metrics: [],
|
|
92435
92742
|
filters: {},
|
|
92436
92743
|
computedDimensions: [],
|
|
92437
|
-
dimensionOrder: []
|
|
92744
|
+
dimensionOrder: [],
|
|
92745
|
+
columnOrder: []
|
|
92438
92746
|
});
|
|
92439
92747
|
// Reset title overrides
|
|
92440
92748
|
setTitleOverrides({
|
|
@@ -92444,42 +92752,45 @@ const ReportBuilder = ({
|
|
|
92444
92752
|
});
|
|
92445
92753
|
console.log("Selected root provider:", event.target.value);
|
|
92446
92754
|
};
|
|
92447
|
-
|
|
92755
|
+
|
|
92756
|
+
// Keyed by instance id, so two instances of the same dimension can carry
|
|
92757
|
+
// independent custom titles.
|
|
92758
|
+
const handleUpdateDimensionTitle = (id, customTitle) => {
|
|
92448
92759
|
setTitleOverrides(prev => ({
|
|
92449
92760
|
...prev,
|
|
92450
92761
|
dimensions: {
|
|
92451
92762
|
...prev.dimensions,
|
|
92452
|
-
[
|
|
92763
|
+
[id]: customTitle
|
|
92453
92764
|
}
|
|
92454
92765
|
}));
|
|
92455
92766
|
};
|
|
92456
|
-
const handleResetDimensionTitle =
|
|
92767
|
+
const handleResetDimensionTitle = id => {
|
|
92457
92768
|
setTitleOverrides(prev => {
|
|
92458
92769
|
const newDimensions = {
|
|
92459
92770
|
...prev.dimensions
|
|
92460
92771
|
};
|
|
92461
|
-
delete newDimensions[
|
|
92772
|
+
delete newDimensions[id];
|
|
92462
92773
|
return {
|
|
92463
92774
|
...prev,
|
|
92464
92775
|
dimensions: newDimensions
|
|
92465
92776
|
};
|
|
92466
92777
|
});
|
|
92467
92778
|
};
|
|
92468
|
-
const handleUpdateMetricTitle = (
|
|
92779
|
+
const handleUpdateMetricTitle = (id, customTitle) => {
|
|
92469
92780
|
setTitleOverrides(prev => ({
|
|
92470
92781
|
...prev,
|
|
92471
92782
|
metrics: {
|
|
92472
92783
|
...prev.metrics,
|
|
92473
|
-
[
|
|
92784
|
+
[id]: customTitle
|
|
92474
92785
|
}
|
|
92475
92786
|
}));
|
|
92476
92787
|
};
|
|
92477
|
-
const handleResetMetricTitle =
|
|
92788
|
+
const handleResetMetricTitle = id => {
|
|
92478
92789
|
setTitleOverrides(prev => {
|
|
92479
92790
|
const newMetrics = {
|
|
92480
92791
|
...prev.metrics
|
|
92481
92792
|
};
|
|
92482
|
-
delete newMetrics[
|
|
92793
|
+
delete newMetrics[id];
|
|
92483
92794
|
return {
|
|
92484
92795
|
...prev,
|
|
92485
92796
|
metrics: newMetrics
|
|
@@ -92514,29 +92825,42 @@ const ReportBuilder = ({
|
|
|
92514
92825
|
dimensions: [...prev.dimensions, dimensionData],
|
|
92515
92826
|
dimensionOrder: [...prev.dimensionOrder, {
|
|
92516
92827
|
type: "dimension",
|
|
92517
|
-
key: dimensionData.
|
|
92518
|
-
}]
|
|
92828
|
+
key: dimensionData.id
|
|
92829
|
+
}],
|
|
92830
|
+
columnOrder: appendToColumnOrder(prev.columnOrder, {
|
|
92831
|
+
type: "dimension",
|
|
92832
|
+
key: dimensionData.id
|
|
92833
|
+
})
|
|
92519
92834
|
};
|
|
92520
92835
|
console.log("Dimension saved:", dimensionData);
|
|
92521
92836
|
console.log("Complete report:", newReport);
|
|
92522
92837
|
return newReport;
|
|
92523
92838
|
});
|
|
92524
92839
|
};
|
|
92525
|
-
const handleRemoveDimension =
|
|
92840
|
+
const handleRemoveDimension = id => {
|
|
92526
92841
|
setReport(prev => ({
|
|
92527
92842
|
...prev,
|
|
92528
|
-
dimensions: prev.dimensions.filter(d => d.
|
|
92529
|
-
dimensionOrder: prev.dimensionOrder.filter(entry => !(entry.type === "dimension" && entry.key ===
|
|
92843
|
+
dimensions: prev.dimensions.filter(d => d.id !== id),
|
|
92844
|
+
dimensionOrder: prev.dimensionOrder.filter(entry => !(entry.type === "dimension" && entry.key === id)),
|
|
92845
|
+
columnOrder: prev.columnOrder.filter(entry => !(entry.type === "dimension" && entry.key === id))
|
|
92530
92846
|
}));
|
|
92531
92847
|
};
|
|
92532
|
-
|
|
92533
|
-
|
|
92534
|
-
|
|
92535
|
-
|
|
92536
|
-
|
|
92537
|
-
|
|
92538
|
-
|
|
92539
|
-
|
|
92848
|
+
|
|
92849
|
+
// sortOrder is fundamentally a per-field (query-level) setting - SQL can
|
|
92850
|
+
// only sort by a given column once - so toggling it on any instance of a
|
|
92851
|
+
// duplicated dimension applies to every instance sharing that fullPath.
|
|
92852
|
+
const handleUpdateDimensionSortOrder = (id, sortOrder) => {
|
|
92853
|
+
setReport(prev => {
|
|
92854
|
+
const target = prev.dimensions.find(d => d.id === id);
|
|
92855
|
+
if (!target) return prev;
|
|
92856
|
+
return {
|
|
92857
|
+
...prev,
|
|
92858
|
+
dimensions: prev.dimensions.map(d => d.fullPath === target.fullPath ? {
|
|
92859
|
+
...d,
|
|
92860
|
+
sortOrder
|
|
92861
|
+
} : d)
|
|
92862
|
+
};
|
|
92863
|
+
});
|
|
92540
92864
|
};
|
|
92541
92865
|
|
|
92542
92866
|
// Reorders the single unified dimensions + computed-dimensions list;
|
|
@@ -92555,7 +92879,11 @@ const ReportBuilder = ({
|
|
|
92555
92879
|
dimensionOrder: [...prev.dimensionOrder, {
|
|
92556
92880
|
type: "computed",
|
|
92557
92881
|
key: computedDimensionData.name
|
|
92558
|
-
}]
|
|
92882
|
+
}],
|
|
92883
|
+
columnOrder: appendToColumnOrder(prev.columnOrder, {
|
|
92884
|
+
type: "computed",
|
|
92885
|
+
key: computedDimensionData.name
|
|
92886
|
+
})
|
|
92559
92887
|
}));
|
|
92560
92888
|
};
|
|
92561
92889
|
const handleUpdateComputedDimension = (name, computedDimensionData) => {
|
|
@@ -92565,6 +92893,10 @@ const ReportBuilder = ({
|
|
|
92565
92893
|
dimensionOrder: prev.dimensionOrder.map(entry => entry.type === "computed" && entry.key === name ? {
|
|
92566
92894
|
...entry,
|
|
92567
92895
|
key: computedDimensionData.name
|
|
92896
|
+
} : entry),
|
|
92897
|
+
columnOrder: prev.columnOrder.map(entry => entry.type === "computed" && entry.key === name ? {
|
|
92898
|
+
...entry,
|
|
92899
|
+
key: computedDimensionData.name
|
|
92568
92900
|
} : entry)
|
|
92569
92901
|
}));
|
|
92570
92902
|
};
|
|
@@ -92572,24 +92904,30 @@ const ReportBuilder = ({
|
|
|
92572
92904
|
setReport(prev => ({
|
|
92573
92905
|
...prev,
|
|
92574
92906
|
computedDimensions: prev.computedDimensions.filter(cd => cd.name !== name),
|
|
92575
|
-
dimensionOrder: prev.dimensionOrder.filter(entry => !(entry.type === "computed" && entry.key === name))
|
|
92907
|
+
dimensionOrder: prev.dimensionOrder.filter(entry => !(entry.type === "computed" && entry.key === name)),
|
|
92908
|
+
columnOrder: prev.columnOrder.filter(entry => !(entry.type === "computed" && entry.key === name))
|
|
92576
92909
|
}));
|
|
92577
92910
|
};
|
|
92578
92911
|
const handleSaveMetric = metricData => {
|
|
92579
92912
|
setReport(prev => {
|
|
92580
92913
|
const newReport = {
|
|
92581
92914
|
...prev,
|
|
92582
|
-
metrics: [...prev.metrics, metricData]
|
|
92915
|
+
metrics: [...prev.metrics, metricData],
|
|
92916
|
+
columnOrder: appendToColumnOrder(prev.columnOrder, {
|
|
92917
|
+
type: "metric",
|
|
92918
|
+
key: metricData.id
|
|
92919
|
+
})
|
|
92583
92920
|
};
|
|
92584
92921
|
console.log("Metric saved:", metricData);
|
|
92585
92922
|
console.log("Complete report:", newReport);
|
|
92586
92923
|
return newReport;
|
|
92587
92924
|
});
|
|
92588
92925
|
};
|
|
92589
|
-
const handleRemoveMetric =
|
|
92926
|
+
const handleRemoveMetric = id => {
|
|
92590
92927
|
setReport(prev => ({
|
|
92591
92928
|
...prev,
|
|
92592
|
-
metrics: prev.metrics.filter(
|
|
92929
|
+
metrics: prev.metrics.filter(m => m.id !== id),
|
|
92930
|
+
columnOrder: prev.columnOrder.filter(entry => !(entry.type === "metric" && entry.key === id))
|
|
92593
92931
|
}));
|
|
92594
92932
|
};
|
|
92595
92933
|
const handleReorderMetrics = newOrder => {
|
|
@@ -92598,6 +92936,16 @@ const ReportBuilder = ({
|
|
|
92598
92936
|
metrics: newOrder
|
|
92599
92937
|
}));
|
|
92600
92938
|
};
|
|
92939
|
+
|
|
92940
|
+
// Sets the unified column order shown on the Column Order tab. An empty
|
|
92941
|
+
// array (e.g. via "Reset to Default") clears the customization, and the
|
|
92942
|
+
// results grid falls back to dimensionOrder followed by metrics order.
|
|
92943
|
+
const handleReorderColumnItems = newColumnOrder => {
|
|
92944
|
+
setReport(prev => ({
|
|
92945
|
+
...prev,
|
|
92946
|
+
columnOrder: newColumnOrder
|
|
92947
|
+
}));
|
|
92948
|
+
};
|
|
92601
92949
|
const handleSaveFilter = (fullPath, filterData) => {
|
|
92602
92950
|
setReport(prev => {
|
|
92603
92951
|
const newReport = {
|
|
@@ -92635,13 +92983,30 @@ const ReportBuilder = ({
|
|
|
92635
92983
|
// persisted `ordered_dimensions` field all stay consistent with whatever
|
|
92636
92984
|
// order the user arranged on screen.
|
|
92637
92985
|
const buildQueryObject = () => {
|
|
92638
|
-
const
|
|
92986
|
+
const dimensionById = Object.fromEntries(report.dimensions.map(d => [d.id, d]));
|
|
92639
92987
|
const computedByName = Object.fromEntries(report.computedDimensions.map(cd => [cd.name, cd]));
|
|
92640
|
-
|
|
92988
|
+
|
|
92989
|
+
// May contain more than one instance of the same fullPath (deliberate
|
|
92990
|
+
// duplicates), kept in dimensionOrder's display order.
|
|
92991
|
+
const orderedDimensionsOnly = report.dimensionOrder.filter(entry => entry.type === "dimension").map(entry => dimensionById[entry.key]).filter(Boolean);
|
|
92641
92992
|
const orderedComputedOnly = report.dimensionOrder.filter(entry => entry.type === "computed").map(entry => computedByName[entry.key]).filter(Boolean);
|
|
92642
92993
|
|
|
92994
|
+
// The API keys each result row by fullPath, so duplicate instances of
|
|
92995
|
+
// the same field can only ever carry one value/one sort direction at
|
|
92996
|
+
// the query level - dedupe by fullPath (keeping first-occurrence order)
|
|
92997
|
+
// before sending. The builder still shows/orders/titles each instance
|
|
92998
|
+
// independently; they just read from the same underlying query column.
|
|
92999
|
+
const uniqueDimensionsByPath = [];
|
|
93000
|
+
const seenDimensionPaths = new Set();
|
|
93001
|
+
orderedDimensionsOnly.forEach(dim => {
|
|
93002
|
+
if (!seenDimensionPaths.has(dim.fullPath)) {
|
|
93003
|
+
seenDimensionPaths.add(dim.fullPath);
|
|
93004
|
+
uniqueDimensionsByPath.push(dim);
|
|
93005
|
+
}
|
|
93006
|
+
});
|
|
93007
|
+
|
|
92643
93008
|
// Build order_by array - include all dimensions, add desc property only when needed
|
|
92644
|
-
const orderBy =
|
|
93009
|
+
const orderBy = uniqueDimensionsByPath.map(dim => {
|
|
92645
93010
|
const orderItem = {
|
|
92646
93011
|
name: dim.fullPath
|
|
92647
93012
|
};
|
|
@@ -92696,9 +93061,20 @@ const ReportBuilder = ({
|
|
|
92696
93061
|
if (Object.keys(titleOverrides.filters).length > 0) {
|
|
92697
93062
|
titles.filters = titleOverrides.filters;
|
|
92698
93063
|
}
|
|
93064
|
+
|
|
93065
|
+
// Same dedupe rationale as dimensions above: the query only needs each
|
|
93066
|
+
// metric fullPath once, however many display instances reference it.
|
|
93067
|
+
const uniqueMetricPaths = [];
|
|
93068
|
+
const seenMetricPaths = new Set();
|
|
93069
|
+
report.metrics.forEach(metric => {
|
|
93070
|
+
if (!seenMetricPaths.has(metric.fullPath)) {
|
|
93071
|
+
seenMetricPaths.add(metric.fullPath);
|
|
93072
|
+
uniqueMetricPaths.push(metric.fullPath);
|
|
93073
|
+
}
|
|
93074
|
+
});
|
|
92699
93075
|
const queryObj = {
|
|
92700
|
-
dimensions:
|
|
92701
|
-
metrics:
|
|
93076
|
+
dimensions: uniqueDimensionsByPath.map(dim => dim.fullPath),
|
|
93077
|
+
metrics: uniqueMetricPaths,
|
|
92702
93078
|
order_by: orderBy
|
|
92703
93079
|
};
|
|
92704
93080
|
|
|
@@ -92712,7 +93088,9 @@ const ReportBuilder = ({
|
|
|
92712
93088
|
|
|
92713
93089
|
// Persist the unified dimension + computed-dimension order so it can be
|
|
92714
93090
|
// reconstructed on reload and so executed reports render columns in the
|
|
92715
|
-
// same order they were arranged in the builder.
|
|
93091
|
+
// same order they were arranged in the builder. Entries use each
|
|
93092
|
+
// dimension's instance id as `ref`, so deliberate duplicates (the nth
|
|
93093
|
+
// occurrence of a fullPath is suffixed `#n`) round-trip correctly.
|
|
92716
93094
|
if (report.dimensionOrder.length > 0) {
|
|
92717
93095
|
queryObj.ordered_dimensions = report.dimensionOrder.map(entry => ({
|
|
92718
93096
|
type: entry.type,
|
|
@@ -92720,6 +93098,23 @@ const ReportBuilder = ({
|
|
|
92720
93098
|
}));
|
|
92721
93099
|
}
|
|
92722
93100
|
|
|
93101
|
+
// Persist metric instance order/identity the same way (metrics have no
|
|
93102
|
+
// other unified-order field to piggyback on).
|
|
93103
|
+
if (report.metrics.length > 0) {
|
|
93104
|
+
queryObj.ordered_metrics = report.metrics.map(metric => metric.id);
|
|
93105
|
+
}
|
|
93106
|
+
|
|
93107
|
+
// Persist the optional unified column order (Column Order tab) so the
|
|
93108
|
+
// results grid can reproduce it on reload. Absent when no custom order
|
|
93109
|
+
// has been defined, so old/untouched reports keep their current
|
|
93110
|
+
// dimensions-then-metrics column order unchanged.
|
|
93111
|
+
if (report.columnOrder.length > 0) {
|
|
93112
|
+
queryObj.ordered_columns = report.columnOrder.map(entry => ({
|
|
93113
|
+
type: entry.type,
|
|
93114
|
+
ref: entry.key
|
|
93115
|
+
}));
|
|
93116
|
+
}
|
|
93117
|
+
|
|
92723
93118
|
// Only add titles if there are any overrides
|
|
92724
93119
|
if (Object.keys(titles).length > 0) {
|
|
92725
93120
|
queryObj.titles = titles;
|
|
@@ -92779,8 +93174,8 @@ const ReportBuilder = ({
|
|
|
92779
93174
|
const handleRunReport = async () => {
|
|
92780
93175
|
setCurrentPage(0);
|
|
92781
93176
|
await runReportWithPagination(0, pageSize);
|
|
92782
|
-
// Switch to Results tab after running report (
|
|
92783
|
-
setActiveTab(
|
|
93177
|
+
// Switch to Results tab after running report (index 4: Dimensions, Metrics, Filters, Column Order, Results)
|
|
93178
|
+
setActiveTab(4);
|
|
92784
93179
|
};
|
|
92785
93180
|
const handleDownloadReport = async () => {
|
|
92786
93181
|
try {
|
|
@@ -92878,15 +93273,20 @@ const ReportBuilder = ({
|
|
|
92878
93273
|
|
|
92879
93274
|
// Resolve the unified dimensionOrder into actual dimension/computed-dimension
|
|
92880
93275
|
// objects so the results grid can render columns in that same order.
|
|
92881
|
-
const
|
|
93276
|
+
const dimensionById = Object.fromEntries(report.dimensions.map(d => [d.id, d]));
|
|
92882
93277
|
const computedDimensionByName = Object.fromEntries(report.computedDimensions.map(cd => [cd.name, cd]));
|
|
92883
93278
|
const orderedDimensionItems = report.dimensionOrder.map(entry => entry.type === "dimension" ? {
|
|
92884
93279
|
kind: "dimension",
|
|
92885
|
-
data:
|
|
93280
|
+
data: dimensionById[entry.key]
|
|
92886
93281
|
} : {
|
|
92887
93282
|
kind: "computed",
|
|
92888
93283
|
data: computedDimensionByName[entry.key]
|
|
92889
93284
|
}).filter(item => item.data);
|
|
93285
|
+
|
|
93286
|
+
// Final column list for the results grid: the explicit Column Order tab
|
|
93287
|
+
// arrangement when defined, otherwise dimensions/computed (in their own
|
|
93288
|
+
// order) followed by metrics (in their own order) — today's behavior.
|
|
93289
|
+
const columnItems = resolveColumnOrder(report.columnOrder, orderedDimensionItems, report.metrics);
|
|
92890
93290
|
return /*#__PURE__*/gn.createElement(Box, {
|
|
92891
93291
|
sx: {
|
|
92892
93292
|
p: 3,
|
|
@@ -93141,9 +93541,36 @@ const ReportBuilder = ({
|
|
|
93141
93541
|
}
|
|
93142
93542
|
}
|
|
93143
93543
|
}), /*#__PURE__*/gn.createElement(Tab, {
|
|
93144
|
-
label:
|
|
93544
|
+
label: /*#__PURE__*/gn.createElement(Badge, {
|
|
93545
|
+
variant: "dot",
|
|
93546
|
+
invisible: report.columnOrder.length === 0,
|
|
93547
|
+
sx: {
|
|
93548
|
+
"& .MuiBadge-dot": {
|
|
93549
|
+
backgroundColor: "rgb(70, 134, 128)"
|
|
93550
|
+
}
|
|
93551
|
+
}
|
|
93552
|
+
}, /*#__PURE__*/gn.createElement("span", {
|
|
93553
|
+
style: {
|
|
93554
|
+
marginRight: report.columnOrder.length > 0 ? "8px" : "0"
|
|
93555
|
+
}
|
|
93556
|
+
}, "Column Order")),
|
|
93145
93557
|
id: "report-tab-3",
|
|
93146
93558
|
"aria-controls": "report-tabpanel-3",
|
|
93559
|
+
sx: {
|
|
93560
|
+
height: "41px",
|
|
93561
|
+
fontFamily: "system-ui",
|
|
93562
|
+
borderRadius: "0.5rem",
|
|
93563
|
+
boxShadow: "none",
|
|
93564
|
+
textTransform: "none",
|
|
93565
|
+
color: "rgb(37, 37, 37)",
|
|
93566
|
+
"&.Mui-selected": {
|
|
93567
|
+
color: "rgb(70, 134, 128)"
|
|
93568
|
+
}
|
|
93569
|
+
}
|
|
93570
|
+
}), /*#__PURE__*/gn.createElement(Tab, {
|
|
93571
|
+
label: reportData ? "Results" : "Results (Run report first)",
|
|
93572
|
+
id: "report-tab-4",
|
|
93573
|
+
"aria-controls": "report-tabpanel-4",
|
|
93147
93574
|
disabled: !reportData,
|
|
93148
93575
|
sx: {
|
|
93149
93576
|
height: "41px",
|
|
@@ -93210,10 +93637,18 @@ const ReportBuilder = ({
|
|
|
93210
93637
|
})), /*#__PURE__*/gn.createElement(TabPanel, {
|
|
93211
93638
|
value: activeTab,
|
|
93212
93639
|
index: 3
|
|
93213
|
-
},
|
|
93214
|
-
reportData: reportData,
|
|
93640
|
+
}, /*#__PURE__*/gn.createElement(ColumnOrder, {
|
|
93215
93641
|
orderedDimensionItems: orderedDimensionItems,
|
|
93216
93642
|
metrics: report.metrics,
|
|
93643
|
+
columnOrder: report.columnOrder,
|
|
93644
|
+
onReorderColumnItems: handleReorderColumnItems,
|
|
93645
|
+
titleOverrides: titleOverrides
|
|
93646
|
+
})), /*#__PURE__*/gn.createElement(TabPanel, {
|
|
93647
|
+
value: activeTab,
|
|
93648
|
+
index: 4
|
|
93649
|
+
}, reportData && /*#__PURE__*/gn.createElement(ReportDataGrid, {
|
|
93650
|
+
reportData: reportData,
|
|
93651
|
+
columnItems: columnItems,
|
|
93217
93652
|
loading: loading,
|
|
93218
93653
|
onPageChange: handlePageChange,
|
|
93219
93654
|
totalRows: totalRows,
|
|
@@ -93301,6 +93736,12 @@ const CurrencySelector = () => {
|
|
|
93301
93736
|
parameters,
|
|
93302
93737
|
setParameters
|
|
93303
93738
|
} = useReportingContext();
|
|
93739
|
+
|
|
93740
|
+
// Hidden by default; only shown when a `cur` query parameter is present in the top-level URL.
|
|
93741
|
+
const showCurrencySelector = new URLSearchParams(window.location.search).has("cur");
|
|
93742
|
+
if (!showCurrencySelector) {
|
|
93743
|
+
return null;
|
|
93744
|
+
}
|
|
93304
93745
|
return /*#__PURE__*/gn.createElement(SingleSelect, {
|
|
93305
93746
|
items: CURRENCY_OPTIONS,
|
|
93306
93747
|
value: parameters.base_currency,
|