@bygd/nc-report-ui 0.1.38 → 0.1.40
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.html +21 -0
- package/dist/app/esm/index.js +93411 -0
- package/dist/default/cjs/index.cjs +7386 -1
- package/dist/default/esm/index.js +611 -26
- package/package.json +1 -1
|
@@ -54,6 +54,7 @@ import CheckIcon from '@mui/icons-material/Check';
|
|
|
54
54
|
import CloseIcon from '@mui/icons-material/Close';
|
|
55
55
|
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
|
56
56
|
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
|
57
|
+
import FunctionsIcon from '@mui/icons-material/Functions';
|
|
57
58
|
import StorageIcon from '@mui/icons-material/Storage';
|
|
58
59
|
import TrendingUpIcon from '@mui/icons-material/TrendingUp';
|
|
59
60
|
import BoltIcon from '@mui/icons-material/Bolt';
|
|
@@ -2494,6 +2495,17 @@ const ProviderSelection = ({
|
|
|
2494
2495
|
}, renderSelectionChain()));
|
|
2495
2496
|
};
|
|
2496
2497
|
|
|
2498
|
+
// Replace {{key}} placeholders in a title with values from the report
|
|
2499
|
+
// parameters (e.g. "Balance ({{base_currency}})" -> "Balance (EUR)").
|
|
2500
|
+
// Unknown keys are left as-is so missing parameters remain visible.
|
|
2501
|
+
const interpolateTitle = (template, params) => {
|
|
2502
|
+
if (typeof template !== 'string' || !template.includes('{{')) return template;
|
|
2503
|
+
return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (match, key) => {
|
|
2504
|
+
const value = params?.[key];
|
|
2505
|
+
return value == null ? match : String(value);
|
|
2506
|
+
});
|
|
2507
|
+
};
|
|
2508
|
+
|
|
2497
2509
|
// Sortable Chip Component
|
|
2498
2510
|
const SortableChip$1 = ({
|
|
2499
2511
|
id,
|
|
@@ -2512,6 +2524,8 @@ const SortableChip$1 = ({
|
|
|
2512
2524
|
onUpdateTitle,
|
|
2513
2525
|
onResetTitle
|
|
2514
2526
|
}) => {
|
|
2527
|
+
const reportingContext = useReportingContextOptional();
|
|
2528
|
+
const parameters = reportingContext?.parameters;
|
|
2515
2529
|
const [isEditing, setIsEditing] = useState(false);
|
|
2516
2530
|
const [editValue, setEditValue] = useState("");
|
|
2517
2531
|
const inputRef = useRef(null);
|
|
@@ -2618,7 +2632,9 @@ const SortableChip$1 = ({
|
|
|
2618
2632
|
handleCancel();
|
|
2619
2633
|
}
|
|
2620
2634
|
};
|
|
2621
|
-
const
|
|
2635
|
+
const rawDisplayLabel = customTitle || label;
|
|
2636
|
+
const displayLabel = interpolateTitle(rawDisplayLabel, parameters);
|
|
2637
|
+
const interpolatedFullLabel = interpolateTitle(fullLabel, parameters);
|
|
2622
2638
|
const hasCustomTitle = !!customTitle;
|
|
2623
2639
|
return /*#__PURE__*/React__default.createElement("div", _extends({
|
|
2624
2640
|
ref: setNodeRef,
|
|
@@ -2659,7 +2675,7 @@ const SortableChip$1 = ({
|
|
|
2659
2675
|
minWidth: 0
|
|
2660
2676
|
}
|
|
2661
2677
|
}, /*#__PURE__*/React__default.createElement(Tooltip$1, {
|
|
2662
|
-
title:
|
|
2678
|
+
title: interpolatedFullLabel || displayLabel,
|
|
2663
2679
|
arrow: true,
|
|
2664
2680
|
placement: "top"
|
|
2665
2681
|
}, /*#__PURE__*/React__default.createElement(Typography$1, {
|
|
@@ -2794,6 +2810,264 @@ const SortableChip$1 = ({
|
|
|
2794
2810
|
}
|
|
2795
2811
|
}))));
|
|
2796
2812
|
};
|
|
2813
|
+
|
|
2814
|
+
// Sortable chip for a Computed Dimension: a { name, value } pair independent
|
|
2815
|
+
// of any provider, so unlike SortableChip there's no title override or sort
|
|
2816
|
+
// toggle. Editing happens inline (right where the item is) rather than via
|
|
2817
|
+
// the top-of-page Add form, so it stays visible regardless of scroll position.
|
|
2818
|
+
const SortableComputedChip = ({
|
|
2819
|
+
id,
|
|
2820
|
+
index,
|
|
2821
|
+
name,
|
|
2822
|
+
value,
|
|
2823
|
+
onDelete,
|
|
2824
|
+
onMoveUp,
|
|
2825
|
+
onMoveDown,
|
|
2826
|
+
isFirst,
|
|
2827
|
+
isLast,
|
|
2828
|
+
onUpdate,
|
|
2829
|
+
isNameTaken
|
|
2830
|
+
}) => {
|
|
2831
|
+
const {
|
|
2832
|
+
attributes,
|
|
2833
|
+
listeners,
|
|
2834
|
+
setNodeRef,
|
|
2835
|
+
transform,
|
|
2836
|
+
transition,
|
|
2837
|
+
isDragging
|
|
2838
|
+
} = useSortable({
|
|
2839
|
+
id
|
|
2840
|
+
});
|
|
2841
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
2842
|
+
const [editName, setEditName] = useState("");
|
|
2843
|
+
const [editValue, setEditValue] = useState("");
|
|
2844
|
+
const containerRef = useRef(null);
|
|
2845
|
+
const style = {
|
|
2846
|
+
transform: CSS.Transform.toString(transform),
|
|
2847
|
+
transition,
|
|
2848
|
+
opacity: isDragging ? 0.5 : 1,
|
|
2849
|
+
display: "flex",
|
|
2850
|
+
alignItems: "center",
|
|
2851
|
+
width: "100%"
|
|
2852
|
+
};
|
|
2853
|
+
|
|
2854
|
+
// Handle click outside to cancel
|
|
2855
|
+
useEffect(() => {
|
|
2856
|
+
const handleClickOutside = event => {
|
|
2857
|
+
if (isEditing && containerRef.current && !containerRef.current.contains(event.target)) {
|
|
2858
|
+
handleCancel();
|
|
2859
|
+
}
|
|
2860
|
+
};
|
|
2861
|
+
if (isEditing) {
|
|
2862
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
2863
|
+
return () => {
|
|
2864
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
}, [isEditing]);
|
|
2868
|
+
const handleEditClick = () => {
|
|
2869
|
+
setEditName(name);
|
|
2870
|
+
setEditValue(value);
|
|
2871
|
+
setIsEditing(true);
|
|
2872
|
+
};
|
|
2873
|
+
const nameError = editName.trim() !== "" && isNameTaken(editName.trim(), index);
|
|
2874
|
+
const handleSave = () => {
|
|
2875
|
+
const trimmedName = editName.trim();
|
|
2876
|
+
const trimmedValue = editValue.trim();
|
|
2877
|
+
if (!trimmedName || !trimmedValue) return;
|
|
2878
|
+
if (isNameTaken(trimmedName, index)) return;
|
|
2879
|
+
onUpdate(index, {
|
|
2880
|
+
name: trimmedName,
|
|
2881
|
+
value: trimmedValue
|
|
2882
|
+
});
|
|
2883
|
+
setIsEditing(false);
|
|
2884
|
+
};
|
|
2885
|
+
const handleCancel = () => {
|
|
2886
|
+
setIsEditing(false);
|
|
2887
|
+
setEditName("");
|
|
2888
|
+
setEditValue("");
|
|
2889
|
+
};
|
|
2890
|
+
const handleKeyDown = e => {
|
|
2891
|
+
if (e.key === "Enter") {
|
|
2892
|
+
handleSave();
|
|
2893
|
+
} else if (e.key === "Escape") {
|
|
2894
|
+
handleCancel();
|
|
2895
|
+
}
|
|
2896
|
+
};
|
|
2897
|
+
return /*#__PURE__*/React__default.createElement("div", _extends({
|
|
2898
|
+
ref: setNodeRef,
|
|
2899
|
+
style: style
|
|
2900
|
+
}, attributes), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
2901
|
+
ref: containerRef,
|
|
2902
|
+
sx: {
|
|
2903
|
+
display: "flex",
|
|
2904
|
+
alignItems: "center",
|
|
2905
|
+
width: "100%",
|
|
2906
|
+
gap: 1,
|
|
2907
|
+
backgroundColor: "white",
|
|
2908
|
+
border: "1px solid #e0e0e0",
|
|
2909
|
+
borderRadius: 2,
|
|
2910
|
+
padding: 1,
|
|
2911
|
+
"&:hover .hover-icons": {
|
|
2912
|
+
opacity: 1
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
}, /*#__PURE__*/React__default.createElement(Box$1, _extends({}, listeners, {
|
|
2916
|
+
sx: {
|
|
2917
|
+
display: "flex",
|
|
2918
|
+
alignItems: "center",
|
|
2919
|
+
cursor: "grab",
|
|
2920
|
+
"&:active": {
|
|
2921
|
+
cursor: "grabbing"
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
}), /*#__PURE__*/React__default.createElement(DragIndicatorIcon, {
|
|
2925
|
+
sx: {
|
|
2926
|
+
cursor: "grab",
|
|
2927
|
+
color: "rgba(110, 110, 110, 0.62)"
|
|
2928
|
+
}
|
|
2929
|
+
})), !isEditing ? /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(Chip, {
|
|
2930
|
+
icon: /*#__PURE__*/React__default.createElement(FunctionsIcon, {
|
|
2931
|
+
sx: {
|
|
2932
|
+
fontSize: "15px !important"
|
|
2933
|
+
}
|
|
2934
|
+
}),
|
|
2935
|
+
label: name,
|
|
2936
|
+
size: "small",
|
|
2937
|
+
sx: {
|
|
2938
|
+
fontWeight: 500,
|
|
2939
|
+
backgroundColor: "rgba(70, 134, 128, 0.1)",
|
|
2940
|
+
color: "rgb(70, 134, 128)",
|
|
2941
|
+
flexShrink: 0
|
|
2942
|
+
}
|
|
2943
|
+
}), /*#__PURE__*/React__default.createElement(Tooltip$1, {
|
|
2944
|
+
title: value,
|
|
2945
|
+
arrow: true,
|
|
2946
|
+
placement: "top"
|
|
2947
|
+
}, /*#__PURE__*/React__default.createElement(Typography$1, {
|
|
2948
|
+
sx: {
|
|
2949
|
+
fontSize: "13px",
|
|
2950
|
+
color: "#666",
|
|
2951
|
+
fontFamily: "monospace",
|
|
2952
|
+
whiteSpace: "nowrap",
|
|
2953
|
+
overflow: "hidden",
|
|
2954
|
+
textOverflow: "ellipsis",
|
|
2955
|
+
minWidth: 0,
|
|
2956
|
+
flex: 1
|
|
2957
|
+
}
|
|
2958
|
+
}, value)), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
2959
|
+
className: "hover-icons",
|
|
2960
|
+
sx: {
|
|
2961
|
+
display: "flex",
|
|
2962
|
+
gap: 0.5,
|
|
2963
|
+
opacity: 0,
|
|
2964
|
+
transition: "opacity 0.2s"
|
|
2965
|
+
}
|
|
2966
|
+
}, /*#__PURE__*/React__default.createElement(Tooltip$1, {
|
|
2967
|
+
title: "Edit",
|
|
2968
|
+
arrow: true,
|
|
2969
|
+
placement: "top"
|
|
2970
|
+
}, /*#__PURE__*/React__default.createElement(IconButton$1, {
|
|
2971
|
+
size: "small",
|
|
2972
|
+
onClick: handleEditClick,
|
|
2973
|
+
"aria-label": "edit computed dimension"
|
|
2974
|
+
}, /*#__PURE__*/React__default.createElement(EditIcon, {
|
|
2975
|
+
fontSize: "small"
|
|
2976
|
+
}))), /*#__PURE__*/React__default.createElement(Tooltip$1, {
|
|
2977
|
+
title: "Delete",
|
|
2978
|
+
arrow: true,
|
|
2979
|
+
placement: "top"
|
|
2980
|
+
}, /*#__PURE__*/React__default.createElement(IconButton$1, {
|
|
2981
|
+
size: "small",
|
|
2982
|
+
onClick: onDelete,
|
|
2983
|
+
"aria-label": "delete",
|
|
2984
|
+
sx: {
|
|
2985
|
+
color: "#ef5350"
|
|
2986
|
+
}
|
|
2987
|
+
}, /*#__PURE__*/React__default.createElement(GridDeleteIcon, {
|
|
2988
|
+
fontSize: "small"
|
|
2989
|
+
})))), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
2990
|
+
sx: {
|
|
2991
|
+
flex: 1
|
|
2992
|
+
}
|
|
2993
|
+
}), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
2994
|
+
className: "hover-icons",
|
|
2995
|
+
sx: {
|
|
2996
|
+
display: "flex",
|
|
2997
|
+
gap: 0.5,
|
|
2998
|
+
opacity: 0,
|
|
2999
|
+
transition: "opacity 0.2s"
|
|
3000
|
+
}
|
|
3001
|
+
}, /*#__PURE__*/React__default.createElement(IconButton$1, {
|
|
3002
|
+
size: "small",
|
|
3003
|
+
onClick: onMoveUp,
|
|
3004
|
+
disabled: isFirst,
|
|
3005
|
+
"aria-label": "move up"
|
|
3006
|
+
}, /*#__PURE__*/React__default.createElement(ArrowUpwardIcon, {
|
|
3007
|
+
fontSize: "small"
|
|
3008
|
+
})), /*#__PURE__*/React__default.createElement(IconButton$1, {
|
|
3009
|
+
size: "small",
|
|
3010
|
+
onClick: onMoveDown,
|
|
3011
|
+
disabled: isLast,
|
|
3012
|
+
"aria-label": "move down"
|
|
3013
|
+
}, /*#__PURE__*/React__default.createElement(ArrowDownwardIcon, {
|
|
3014
|
+
fontSize: "small"
|
|
3015
|
+
})))) : /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(TextField, {
|
|
3016
|
+
value: editName,
|
|
3017
|
+
onChange: e => setEditName(e.target.value),
|
|
3018
|
+
onKeyDown: handleKeyDown,
|
|
3019
|
+
size: "small",
|
|
3020
|
+
label: "Name",
|
|
3021
|
+
error: nameError,
|
|
3022
|
+
helperText: nameError ? "Name already in use" : " ",
|
|
3023
|
+
sx: {
|
|
3024
|
+
width: "200px"
|
|
3025
|
+
}
|
|
3026
|
+
}), /*#__PURE__*/React__default.createElement(TextField, {
|
|
3027
|
+
value: editValue,
|
|
3028
|
+
onChange: e => setEditValue(e.target.value),
|
|
3029
|
+
onKeyDown: handleKeyDown,
|
|
3030
|
+
size: "small",
|
|
3031
|
+
label: "Value",
|
|
3032
|
+
helperText: " ",
|
|
3033
|
+
sx: {
|
|
3034
|
+
flex: 1,
|
|
3035
|
+
minWidth: "200px",
|
|
3036
|
+
"& .MuiInputBase-input": {
|
|
3037
|
+
fontFamily: "monospace"
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
}), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
3041
|
+
sx: {
|
|
3042
|
+
display: "flex",
|
|
3043
|
+
gap: 0.5,
|
|
3044
|
+
alignSelf: "flex-start",
|
|
3045
|
+
mt: "4px"
|
|
3046
|
+
}
|
|
3047
|
+
}, /*#__PURE__*/React__default.createElement(Tooltip$1, {
|
|
3048
|
+
title: "Save",
|
|
3049
|
+
arrow: true,
|
|
3050
|
+
placement: "top"
|
|
3051
|
+
}, /*#__PURE__*/React__default.createElement("span", null, /*#__PURE__*/React__default.createElement(IconButton$1, {
|
|
3052
|
+
size: "small",
|
|
3053
|
+
onClick: handleSave,
|
|
3054
|
+
color: "primary",
|
|
3055
|
+
"aria-label": "save computed dimension",
|
|
3056
|
+
disabled: !editName.trim() || !editValue.trim() || nameError
|
|
3057
|
+
}, /*#__PURE__*/React__default.createElement(CheckIcon, {
|
|
3058
|
+
fontSize: "small"
|
|
3059
|
+
})))), /*#__PURE__*/React__default.createElement(Tooltip$1, {
|
|
3060
|
+
title: "Cancel",
|
|
3061
|
+
arrow: true,
|
|
3062
|
+
placement: "top"
|
|
3063
|
+
}, /*#__PURE__*/React__default.createElement(IconButton$1, {
|
|
3064
|
+
size: "small",
|
|
3065
|
+
onClick: handleCancel,
|
|
3066
|
+
"aria-label": "cancel edit"
|
|
3067
|
+
}, /*#__PURE__*/React__default.createElement(CloseIcon, {
|
|
3068
|
+
fontSize: "small"
|
|
3069
|
+
})))))));
|
|
3070
|
+
};
|
|
2797
3071
|
const Dimensions = ({
|
|
2798
3072
|
providersData,
|
|
2799
3073
|
rootProvider,
|
|
@@ -2805,11 +3079,21 @@ const Dimensions = ({
|
|
|
2805
3079
|
onUpdateTitle,
|
|
2806
3080
|
onResetTitle,
|
|
2807
3081
|
existingMetrics = [],
|
|
2808
|
-
existingFilters = {}
|
|
3082
|
+
existingFilters = {},
|
|
3083
|
+
savedComputedDimensions = [],
|
|
3084
|
+
onSaveComputedDimension,
|
|
3085
|
+
onUpdateComputedDimension,
|
|
3086
|
+
onRemoveComputedDimension,
|
|
3087
|
+
onReorderComputedDimensions
|
|
2809
3088
|
}) => {
|
|
3089
|
+
const reportingContext = useReportingContextOptional();
|
|
3090
|
+
const parameters = reportingContext?.parameters;
|
|
2810
3091
|
const [isAdding, setIsAdding] = useState(false);
|
|
2811
3092
|
const [dimensionSelectionChain, setDimensionSelectionChain] = useState([]);
|
|
2812
3093
|
const [selectedDimension, setSelectedDimension] = useState("");
|
|
3094
|
+
const [isAddingComputed, setIsAddingComputed] = useState(false);
|
|
3095
|
+
const [computedName, setComputedName] = useState("");
|
|
3096
|
+
const [computedValue, setComputedValue] = useState("");
|
|
2813
3097
|
|
|
2814
3098
|
// Setup drag and drop sensors
|
|
2815
3099
|
const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, {
|
|
@@ -2856,6 +3140,54 @@ const Dimensions = ({
|
|
|
2856
3140
|
onReorderDimensions(newDimensions);
|
|
2857
3141
|
};
|
|
2858
3142
|
|
|
3143
|
+
// Computed Dimensions: { name, value } pairs independent of any provider.
|
|
3144
|
+
// Their `value` is a raw SQL literal/expression sent to the API as-is.
|
|
3145
|
+
const isComputedNameTaken = (name, excludeIndex = null) => savedComputedDimensions.some((cd, i) => cd.name === name && i !== excludeIndex);
|
|
3146
|
+
const computedNameError = computedName.trim() !== "" && isComputedNameTaken(computedName.trim());
|
|
3147
|
+
const handleAddComputedClick = () => {
|
|
3148
|
+
setIsAddingComputed(true);
|
|
3149
|
+
setIsAdding(false);
|
|
3150
|
+
setComputedName("");
|
|
3151
|
+
setComputedValue("");
|
|
3152
|
+
};
|
|
3153
|
+
const handleCancelComputed = () => {
|
|
3154
|
+
setIsAddingComputed(false);
|
|
3155
|
+
setComputedName("");
|
|
3156
|
+
setComputedValue("");
|
|
3157
|
+
};
|
|
3158
|
+
const handleSaveComputed = () => {
|
|
3159
|
+
const trimmedName = computedName.trim();
|
|
3160
|
+
const trimmedValue = computedValue.trim();
|
|
3161
|
+
if (!trimmedName || !trimmedValue) return;
|
|
3162
|
+
if (isComputedNameTaken(trimmedName)) return;
|
|
3163
|
+
onSaveComputedDimension({
|
|
3164
|
+
name: trimmedName,
|
|
3165
|
+
value: trimmedValue
|
|
3166
|
+
});
|
|
3167
|
+
handleCancelComputed();
|
|
3168
|
+
};
|
|
3169
|
+
const handleComputedDragEnd = event => {
|
|
3170
|
+
const {
|
|
3171
|
+
active,
|
|
3172
|
+
over
|
|
3173
|
+
} = event;
|
|
3174
|
+
if (over && active.id !== over.id) {
|
|
3175
|
+
const oldIndex = savedComputedDimensions.findIndex((_, i) => i === active.id);
|
|
3176
|
+
const newIndex = savedComputedDimensions.findIndex((_, i) => i === over.id);
|
|
3177
|
+
onReorderComputedDimensions(arrayMove(savedComputedDimensions, oldIndex, newIndex));
|
|
3178
|
+
}
|
|
3179
|
+
};
|
|
3180
|
+
const handleMoveComputedUp = index => {
|
|
3181
|
+
if (index > 0) {
|
|
3182
|
+
onReorderComputedDimensions(arrayMove(savedComputedDimensions, index, index - 1));
|
|
3183
|
+
}
|
|
3184
|
+
};
|
|
3185
|
+
const handleMoveComputedDown = index => {
|
|
3186
|
+
if (index < savedComputedDimensions.length - 1) {
|
|
3187
|
+
onReorderComputedDimensions(arrayMove(savedComputedDimensions, index, index + 1));
|
|
3188
|
+
}
|
|
3189
|
+
};
|
|
3190
|
+
|
|
2859
3191
|
// Get the current provider based on selection chain
|
|
2860
3192
|
const getCurrentProvider = () => {
|
|
2861
3193
|
if (!rootProvider) return null;
|
|
@@ -2919,6 +3251,7 @@ const Dimensions = ({
|
|
|
2919
3251
|
};
|
|
2920
3252
|
const handleAddClick = () => {
|
|
2921
3253
|
setIsAdding(true);
|
|
3254
|
+
setIsAddingComputed(false);
|
|
2922
3255
|
setDimensionSelectionChain([]);
|
|
2923
3256
|
setSelectedDimension("");
|
|
2924
3257
|
};
|
|
@@ -3047,9 +3380,10 @@ const Dimensions = ({
|
|
|
3047
3380
|
sx: {
|
|
3048
3381
|
display: "flex",
|
|
3049
3382
|
justifyContent: "flex-start",
|
|
3383
|
+
gap: 2,
|
|
3050
3384
|
mb: 2
|
|
3051
3385
|
}
|
|
3052
|
-
}, !isAdding && /*#__PURE__*/React__default.createElement(Button, {
|
|
3386
|
+
}, !isAdding && !isAddingComputed && /*#__PURE__*/React__default.createElement(Button, {
|
|
3053
3387
|
variant: "contained",
|
|
3054
3388
|
startIcon: /*#__PURE__*/React__default.createElement(GridAddIcon, null),
|
|
3055
3389
|
onClick: handleAddClick,
|
|
@@ -3067,7 +3401,117 @@ const Dimensions = ({
|
|
|
3067
3401
|
boxShadow: "none"
|
|
3068
3402
|
}
|
|
3069
3403
|
}
|
|
3070
|
-
}, "Add Dimension")
|
|
3404
|
+
}, "Add Dimension"), !isAdding && !isAddingComputed && /*#__PURE__*/React__default.createElement(Button, {
|
|
3405
|
+
variant: "outlined",
|
|
3406
|
+
startIcon: /*#__PURE__*/React__default.createElement(FunctionsIcon, null),
|
|
3407
|
+
onClick: handleAddComputedClick,
|
|
3408
|
+
sx: {
|
|
3409
|
+
height: "40px",
|
|
3410
|
+
fontFamily: "system-ui",
|
|
3411
|
+
fontSize: "14px",
|
|
3412
|
+
fontWeight: 500,
|
|
3413
|
+
borderRadius: "8px",
|
|
3414
|
+
textTransform: "none",
|
|
3415
|
+
borderColor: "rgb(70, 134, 128)",
|
|
3416
|
+
color: "rgb(70, 134, 128)",
|
|
3417
|
+
boxShadow: "none",
|
|
3418
|
+
"&:hover": {
|
|
3419
|
+
backgroundColor: "rgba(70, 134, 128, 0.04)",
|
|
3420
|
+
borderColor: "rgb(50, 114, 108)",
|
|
3421
|
+
boxShadow: "none"
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
}, "Add Computed Dimension")), isAddingComputed && /*#__PURE__*/React__default.createElement(Paper$1, {
|
|
3425
|
+
elevation: 0,
|
|
3426
|
+
sx: {
|
|
3427
|
+
p: 3,
|
|
3428
|
+
mb: 3,
|
|
3429
|
+
border: "1px solid #e0e0e0",
|
|
3430
|
+
borderRadius: "12px",
|
|
3431
|
+
backgroundColor: "white",
|
|
3432
|
+
boxShadow: "0px 2px 4px rgba(0,0,0,0.02)",
|
|
3433
|
+
fontFamily: "system-ui"
|
|
3434
|
+
}
|
|
3435
|
+
}, /*#__PURE__*/React__default.createElement(Typography$1, {
|
|
3436
|
+
sx: {
|
|
3437
|
+
fontSize: "14px",
|
|
3438
|
+
fontWeight: 600,
|
|
3439
|
+
mb: 0.5,
|
|
3440
|
+
color: "rgb(37, 37, 37)"
|
|
3441
|
+
}
|
|
3442
|
+
}, "Define Computed Dimension"), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
3443
|
+
sx: {
|
|
3444
|
+
display: "flex",
|
|
3445
|
+
gap: 2,
|
|
3446
|
+
flexWrap: "wrap",
|
|
3447
|
+
mt: 2
|
|
3448
|
+
}
|
|
3449
|
+
}, /*#__PURE__*/React__default.createElement(TextField, {
|
|
3450
|
+
label: "Name",
|
|
3451
|
+
value: computedName,
|
|
3452
|
+
onChange: e => setComputedName(e.target.value),
|
|
3453
|
+
size: "small",
|
|
3454
|
+
sx: {
|
|
3455
|
+
width: "260px"
|
|
3456
|
+
},
|
|
3457
|
+
error: computedNameError,
|
|
3458
|
+
helperText: computedNameError ? "A computed dimension with this name already exists" : " "
|
|
3459
|
+
}), /*#__PURE__*/React__default.createElement(TextField, {
|
|
3460
|
+
label: "Value",
|
|
3461
|
+
value: computedValue,
|
|
3462
|
+
onChange: e => setComputedValue(e.target.value),
|
|
3463
|
+
size: "small",
|
|
3464
|
+
sx: {
|
|
3465
|
+
width: "400px",
|
|
3466
|
+
"& .MuiInputBase-input": {
|
|
3467
|
+
fontFamily: "monospace"
|
|
3468
|
+
}
|
|
3469
|
+
},
|
|
3470
|
+
helperText: " "
|
|
3471
|
+
})), /*#__PURE__*/React__default.createElement(Box$1, {
|
|
3472
|
+
sx: {
|
|
3473
|
+
display: "flex",
|
|
3474
|
+
justifyContent: "flex-end",
|
|
3475
|
+
gap: 2,
|
|
3476
|
+
mt: 1
|
|
3477
|
+
}
|
|
3478
|
+
}, /*#__PURE__*/React__default.createElement(Button, {
|
|
3479
|
+
variant: "outlined",
|
|
3480
|
+
onClick: handleCancelComputed,
|
|
3481
|
+
sx: {
|
|
3482
|
+
height: "40px",
|
|
3483
|
+
fontFamily: "system-ui",
|
|
3484
|
+
fontSize: "14px",
|
|
3485
|
+
fontWeight: 500,
|
|
3486
|
+
borderRadius: "8px",
|
|
3487
|
+
boxShadow: "none",
|
|
3488
|
+
borderColor: "#e0e0e0",
|
|
3489
|
+
color: "rgb(37, 37, 37)",
|
|
3490
|
+
textTransform: "none",
|
|
3491
|
+
"&:hover": {
|
|
3492
|
+
backgroundColor: "#f5f5f5",
|
|
3493
|
+
borderColor: "#d0d0d0"
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
}, "Cancel"), /*#__PURE__*/React__default.createElement(Button, {
|
|
3497
|
+
variant: "contained",
|
|
3498
|
+
onClick: handleSaveComputed,
|
|
3499
|
+
disabled: !computedName.trim() || !computedValue.trim() || computedNameError,
|
|
3500
|
+
sx: {
|
|
3501
|
+
height: "40px",
|
|
3502
|
+
fontFamily: "system-ui",
|
|
3503
|
+
fontSize: "14px",
|
|
3504
|
+
fontWeight: 500,
|
|
3505
|
+
borderRadius: "8px",
|
|
3506
|
+
boxShadow: "none",
|
|
3507
|
+
textTransform: "none",
|
|
3508
|
+
backgroundColor: "rgb(70, 134, 128)",
|
|
3509
|
+
"&:hover": {
|
|
3510
|
+
backgroundColor: "rgb(50, 114, 108)",
|
|
3511
|
+
boxShadow: "none"
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
}, "Save Computed Dimension"))), isAdding && /*#__PURE__*/React__default.createElement(Paper$1, {
|
|
3071
3515
|
elevation: 0,
|
|
3072
3516
|
sx: {
|
|
3073
3517
|
p: 3,
|
|
@@ -3111,7 +3555,10 @@ const Dimensions = ({
|
|
|
3111
3555
|
mb: 3
|
|
3112
3556
|
}
|
|
3113
3557
|
}, /*#__PURE__*/React__default.createElement(SingleSelect, {
|
|
3114
|
-
items: getDimensionItems()
|
|
3558
|
+
items: getDimensionItems().map(item => ({
|
|
3559
|
+
...item,
|
|
3560
|
+
value: interpolateTitle(item.value, parameters)
|
|
3561
|
+
})),
|
|
3115
3562
|
value: selectedDimension,
|
|
3116
3563
|
label: "Choose Dimension",
|
|
3117
3564
|
onChange: handleDimensionChange,
|
|
@@ -3230,6 +3677,50 @@ const Dimensions = ({
|
|
|
3230
3677
|
customTitle: titleOverrides[dim.fullPath],
|
|
3231
3678
|
onUpdateTitle: onUpdateTitle,
|
|
3232
3679
|
onResetTitle: onResetTitle
|
|
3680
|
+
})))))), savedComputedDimensions.length > 0 && /*#__PURE__*/React__default.createElement(Box$1, {
|
|
3681
|
+
sx: {
|
|
3682
|
+
marginTop: 2
|
|
3683
|
+
}
|
|
3684
|
+
}, /*#__PURE__*/React__default.createElement(Typography$1, {
|
|
3685
|
+
variant: "h6",
|
|
3686
|
+
sx: {
|
|
3687
|
+
fontSize: "16px",
|
|
3688
|
+
fontWeight: 600,
|
|
3689
|
+
marginTop: 2,
|
|
3690
|
+
color: "rgb(37, 37, 37)"
|
|
3691
|
+
}
|
|
3692
|
+
}, "Saved Computed Dimensions"), /*#__PURE__*/React__default.createElement(Typography$1, {
|
|
3693
|
+
sx: {
|
|
3694
|
+
fontSize: "13px",
|
|
3695
|
+
color: "#666",
|
|
3696
|
+
marginBottom: 2
|
|
3697
|
+
}
|
|
3698
|
+
}, "Drag to reorder or use arrows"), /*#__PURE__*/React__default.createElement(DndContext, {
|
|
3699
|
+
sensors: sensors,
|
|
3700
|
+
collisionDetection: closestCenter,
|
|
3701
|
+
onDragEnd: handleComputedDragEnd
|
|
3702
|
+
}, /*#__PURE__*/React__default.createElement(SortableContext, {
|
|
3703
|
+
items: savedComputedDimensions.map((_, index) => index),
|
|
3704
|
+
strategy: verticalListSortingStrategy
|
|
3705
|
+
}, /*#__PURE__*/React__default.createElement(Box$1, {
|
|
3706
|
+
sx: {
|
|
3707
|
+
display: "flex",
|
|
3708
|
+
flexDirection: "column",
|
|
3709
|
+
gap: 1
|
|
3710
|
+
}
|
|
3711
|
+
}, savedComputedDimensions.map((cd, index) => /*#__PURE__*/React__default.createElement(SortableComputedChip, {
|
|
3712
|
+
key: index,
|
|
3713
|
+
id: index,
|
|
3714
|
+
index: index,
|
|
3715
|
+
name: cd.name,
|
|
3716
|
+
value: cd.value,
|
|
3717
|
+
onUpdate: onUpdateComputedDimension,
|
|
3718
|
+
isNameTaken: isComputedNameTaken,
|
|
3719
|
+
onDelete: () => onRemoveComputedDimension(index),
|
|
3720
|
+
onMoveUp: () => handleMoveComputedUp(index),
|
|
3721
|
+
onMoveDown: () => handleMoveComputedDown(index),
|
|
3722
|
+
isFirst: index === 0,
|
|
3723
|
+
isLast: index === savedComputedDimensions.length - 1
|
|
3233
3724
|
})))))));
|
|
3234
3725
|
};
|
|
3235
3726
|
|
|
@@ -3295,6 +3786,8 @@ const SortableChip = ({
|
|
|
3295
3786
|
onResetTitle,
|
|
3296
3787
|
source
|
|
3297
3788
|
}) => {
|
|
3789
|
+
const reportingContext = useReportingContextOptional();
|
|
3790
|
+
const parameters = reportingContext?.parameters;
|
|
3298
3791
|
const [isEditing, setIsEditing] = useState(false);
|
|
3299
3792
|
const [editValue, setEditValue] = useState('');
|
|
3300
3793
|
const inputRef = useRef(null);
|
|
@@ -3370,7 +3863,9 @@ const SortableChip = ({
|
|
|
3370
3863
|
handleCancel();
|
|
3371
3864
|
}
|
|
3372
3865
|
};
|
|
3373
|
-
const
|
|
3866
|
+
const rawDisplayLabel = customTitle || label;
|
|
3867
|
+
const displayLabel = interpolateTitle(rawDisplayLabel, parameters);
|
|
3868
|
+
const interpolatedFullLabel = interpolateTitle(fullLabel, parameters);
|
|
3374
3869
|
const hasCustomTitle = !!customTitle;
|
|
3375
3870
|
return /*#__PURE__*/React__default.createElement("div", _extends({
|
|
3376
3871
|
ref: setNodeRef,
|
|
@@ -3419,7 +3914,7 @@ const SortableChip = ({
|
|
|
3419
3914
|
minWidth: 0
|
|
3420
3915
|
}
|
|
3421
3916
|
}, /*#__PURE__*/React__default.createElement(Tooltip$1, {
|
|
3422
|
-
title:
|
|
3917
|
+
title: interpolatedFullLabel || displayLabel,
|
|
3423
3918
|
arrow: true,
|
|
3424
3919
|
placement: "top"
|
|
3425
3920
|
}, /*#__PURE__*/React__default.createElement(Typography$1, {
|
|
@@ -3558,6 +4053,8 @@ const Metrics = ({
|
|
|
3558
4053
|
existingDimensions = [],
|
|
3559
4054
|
existingFilters = {}
|
|
3560
4055
|
}) => {
|
|
4056
|
+
const reportingContext = useReportingContextOptional();
|
|
4057
|
+
const parameters = reportingContext?.parameters;
|
|
3561
4058
|
const [isAdding, setIsAdding] = useState(false);
|
|
3562
4059
|
const [metricSelectionChain, setMetricSelectionChain] = useState([]);
|
|
3563
4060
|
const [selectedMetric, setSelectedMetric] = useState('');
|
|
@@ -3848,7 +4345,10 @@ const Metrics = ({
|
|
|
3848
4345
|
mb: 3
|
|
3849
4346
|
}
|
|
3850
4347
|
}, /*#__PURE__*/React__default.createElement(SingleSelect, {
|
|
3851
|
-
items: getMetricItems()
|
|
4348
|
+
items: getMetricItems().map(item => ({
|
|
4349
|
+
...item,
|
|
4350
|
+
value: interpolateTitle(item.value, parameters)
|
|
4351
|
+
})),
|
|
3852
4352
|
value: selectedMetric,
|
|
3853
4353
|
label: "Choose Metric",
|
|
3854
4354
|
onChange: handleMetricChange,
|
|
@@ -3977,6 +4477,7 @@ const Filters = ({
|
|
|
3977
4477
|
onResetTitle
|
|
3978
4478
|
}) => {
|
|
3979
4479
|
const reportingContext = useReportingContextOptional();
|
|
4480
|
+
const parameters = reportingContext?.parameters;
|
|
3980
4481
|
const [isAdding, setIsAdding] = useState(false);
|
|
3981
4482
|
const [isAddingFromDimension, setIsAddingFromDimension] = useState(false); // New state for dimension filter mode
|
|
3982
4483
|
const [dimensionSelectionChain, setDimensionSelectionChain] = useState([]);
|
|
@@ -4645,7 +5146,10 @@ const Filters = ({
|
|
|
4645
5146
|
mb: 3
|
|
4646
5147
|
}
|
|
4647
5148
|
}, /*#__PURE__*/React__default.createElement(SingleSelect, {
|
|
4648
|
-
items: getDimensionItems()
|
|
5149
|
+
items: getDimensionItems().map(item => ({
|
|
5150
|
+
...item,
|
|
5151
|
+
value: interpolateTitle(item.value, parameters)
|
|
5152
|
+
})),
|
|
4649
5153
|
value: selectedDimension,
|
|
4650
5154
|
label: "Choose Dimension",
|
|
4651
5155
|
onChange: handleDimensionChange,
|
|
@@ -4791,7 +5295,7 @@ const Filters = ({
|
|
|
4791
5295
|
}, /*#__PURE__*/React__default.createElement(SingleSelect, {
|
|
4792
5296
|
items: existingDimensions.map(dim => ({
|
|
4793
5297
|
key: dim.fullPath,
|
|
4794
|
-
value: dimensionTitleOverrides[dim.fullPath] || dim.dimensionTitle
|
|
5298
|
+
value: interpolateTitle(dimensionTitleOverrides[dim.fullPath] || dim.dimensionTitle, parameters)
|
|
4795
5299
|
})).sort((a, b) => a.value.localeCompare(b.value)),
|
|
4796
5300
|
value: selectedExistingDimension,
|
|
4797
5301
|
label: "Choose Dimension",
|
|
@@ -4954,7 +5458,9 @@ const Filters = ({
|
|
|
4954
5458
|
}
|
|
4955
5459
|
|
|
4956
5460
|
// Use filter title override if exists, otherwise use dimension title override, otherwise use default dimension title
|
|
4957
|
-
const
|
|
5461
|
+
const rawDisplayLabel = titleOverrides[fullPath] || dimensionTitleOverrides[fullPath] || filter.dimensionTitle;
|
|
5462
|
+
const displayLabel = interpolateTitle(rawDisplayLabel, parameters);
|
|
5463
|
+
const tooltipPathLabel = interpolateTitle(dimensionTitleOverrides[fullPath] || filter.dimensionTitle, parameters);
|
|
4958
5464
|
const hasCustomTitle = !!titleOverrides[fullPath];
|
|
4959
5465
|
return /*#__PURE__*/React__default.createElement(Box$1, {
|
|
4960
5466
|
key: fullPath
|
|
@@ -5028,7 +5534,7 @@ const Filters = ({
|
|
|
5028
5534
|
}, /*#__PURE__*/React__default.createElement(GridDeleteIcon, {
|
|
5029
5535
|
fontSize: "small"
|
|
5030
5536
|
}))), /*#__PURE__*/React__default.createElement(Tooltip$1, {
|
|
5031
|
-
title: /*#__PURE__*/React__default.createElement("div", null, /*#__PURE__*/React__default.createElement("div", null, /*#__PURE__*/React__default.createElement("strong", null, "Path:"), " ", formatProviderPath(filter), " \u2192 ",
|
|
5537
|
+
title: /*#__PURE__*/React__default.createElement("div", null, /*#__PURE__*/React__default.createElement("div", null, /*#__PURE__*/React__default.createElement("strong", null, "Path:"), " ", formatProviderPath(filter), " \u2192 ", tooltipPathLabel), /*#__PURE__*/React__default.createElement("div", null, /*#__PURE__*/React__default.createElement("strong", null, "Full Path:"), " ", fullPath), /*#__PURE__*/React__default.createElement("div", null, /*#__PURE__*/React__default.createElement("strong", null, isDate ? 'Date Range' : `Values (${valueCount})`, ":"), " ", valuesList)),
|
|
5032
5538
|
arrow: true,
|
|
5033
5539
|
placement: "right"
|
|
5034
5540
|
}, /*#__PURE__*/React__default.createElement(IconButton$1, {
|
|
@@ -5096,7 +5602,7 @@ const Filters = ({
|
|
|
5096
5602
|
marginBottom: 1.5,
|
|
5097
5603
|
fontWeight: 600
|
|
5098
5604
|
}
|
|
5099
|
-
}, "Edit ", isDate ? 'Date Range' : 'Values', " for: ",
|
|
5605
|
+
}, "Edit ", isDate ? 'Date Range' : 'Values', " for: ", tooltipPathLabel), isDate ? /*#__PURE__*/React__default.createElement(LocalizationProvider, {
|
|
5100
5606
|
dateAdapter: AdapterDayjs
|
|
5101
5607
|
}, /*#__PURE__*/React__default.createElement(Box$1, {
|
|
5102
5608
|
sx: {
|
|
@@ -5199,6 +5705,7 @@ const ReportDataGrid = ({
|
|
|
5199
5705
|
reportData,
|
|
5200
5706
|
dimensions,
|
|
5201
5707
|
metrics,
|
|
5708
|
+
computedDimensions = [],
|
|
5202
5709
|
loading,
|
|
5203
5710
|
onPageChange,
|
|
5204
5711
|
totalRows = 0,
|
|
@@ -5207,6 +5714,8 @@ const ReportDataGrid = ({
|
|
|
5207
5714
|
metrics: {}
|
|
5208
5715
|
}
|
|
5209
5716
|
}) => {
|
|
5717
|
+
const reportingContext = useReportingContextOptional();
|
|
5718
|
+
const parameters = reportingContext?.parameters;
|
|
5210
5719
|
const [paginationModel, setPaginationModel] = useState({
|
|
5211
5720
|
page: 0,
|
|
5212
5721
|
pageSize: 50
|
|
@@ -5246,6 +5755,7 @@ const ReportDataGrid = ({
|
|
|
5246
5755
|
|
|
5247
5756
|
// Check for title override, otherwise use the friendly dimension title from provider
|
|
5248
5757
|
headerName = titleOverrides.dimensions[dim.fullPath] || dim.dimensionTitle || fieldName;
|
|
5758
|
+
headerName = interpolateTitle(headerName, parameters);
|
|
5249
5759
|
const dimDef = dim.dimension;
|
|
5250
5760
|
cols.push({
|
|
5251
5761
|
field: fieldName,
|
|
@@ -5257,6 +5767,18 @@ const ReportDataGrid = ({
|
|
|
5257
5767
|
});
|
|
5258
5768
|
});
|
|
5259
5769
|
|
|
5770
|
+
// Add computed dimension columns. These have no provider/relations chain,
|
|
5771
|
+
// so the API returns each row keyed by the computed dimension's own `name`.
|
|
5772
|
+
computedDimensions.forEach(cd => {
|
|
5773
|
+
cols.push({
|
|
5774
|
+
field: cd.name,
|
|
5775
|
+
headerName: cd.name,
|
|
5776
|
+
flex: 1,
|
|
5777
|
+
minWidth: 150,
|
|
5778
|
+
type: 'string'
|
|
5779
|
+
});
|
|
5780
|
+
});
|
|
5781
|
+
|
|
5260
5782
|
// Add metric columns
|
|
5261
5783
|
// The API returns metric keys in two different forms depending on the path depth:
|
|
5262
5784
|
//
|
|
@@ -5296,6 +5818,7 @@ const ReportDataGrid = ({
|
|
|
5296
5818
|
|
|
5297
5819
|
// Check for title override, otherwise use the friendly metric title from provider
|
|
5298
5820
|
headerName = titleOverrides.metrics[metric.fullPath] || metric.metricTitle || fieldName;
|
|
5821
|
+
headerName = interpolateTitle(headerName, parameters);
|
|
5299
5822
|
cols.push({
|
|
5300
5823
|
field: fieldName,
|
|
5301
5824
|
headerName: headerName,
|
|
@@ -5315,7 +5838,7 @@ const ReportDataGrid = ({
|
|
|
5315
5838
|
});
|
|
5316
5839
|
});
|
|
5317
5840
|
return cols;
|
|
5318
|
-
}, [dimensions, metrics, titleOverrides]);
|
|
5841
|
+
}, [dimensions, metrics, computedDimensions, titleOverrides, parameters]);
|
|
5319
5842
|
|
|
5320
5843
|
// Transform report data to rows with unique IDs.
|
|
5321
5844
|
// Keys are normalised by replacing dots with underscores so that MUI DataGrid
|
|
@@ -5421,7 +5944,8 @@ const ReportBuilder = ({
|
|
|
5421
5944
|
const [report, setReport] = useState({
|
|
5422
5945
|
dimensions: [],
|
|
5423
5946
|
metrics: [],
|
|
5424
|
-
filters: {}
|
|
5947
|
+
filters: {},
|
|
5948
|
+
computedDimensions: []
|
|
5425
5949
|
});
|
|
5426
5950
|
const [titleOverrides, setTitleOverrides] = useState({
|
|
5427
5951
|
dimensions: {},
|
|
@@ -5462,7 +5986,7 @@ const ReportBuilder = ({
|
|
|
5462
5986
|
|
|
5463
5987
|
// Auto-run report when autoRun is true and report is loaded
|
|
5464
5988
|
useEffect(() => {
|
|
5465
|
-
if (autoRun && reportLoaded && selectedProvider && report.dimensions.length > 0) {
|
|
5989
|
+
if (autoRun && reportLoaded && selectedProvider && (report.dimensions.length > 0 || report.metrics.length > 0 || report.computedDimensions.length > 0)) {
|
|
5466
5990
|
handleRunReport();
|
|
5467
5991
|
}
|
|
5468
5992
|
}, [autoRun, reportLoaded]);
|
|
@@ -5688,11 +6212,16 @@ const ReportBuilder = ({
|
|
|
5688
6212
|
});
|
|
5689
6213
|
}
|
|
5690
6214
|
|
|
6215
|
+
// Computed dimensions are self-contained { name, value } pairs with no
|
|
6216
|
+
// provider/relation chain, so they're loaded as-is (no reconstruction needed)
|
|
6217
|
+
const computedDimensions = reportDef.definition?.doc?.query?.computed_dimensions || [];
|
|
6218
|
+
|
|
5691
6219
|
// Set the report state
|
|
5692
6220
|
setReport({
|
|
5693
6221
|
dimensions: reconstructedDimensions,
|
|
5694
6222
|
metrics: reconstructedMetrics,
|
|
5695
|
-
filters: loadedFilters
|
|
6223
|
+
filters: loadedFilters,
|
|
6224
|
+
computedDimensions
|
|
5696
6225
|
});
|
|
5697
6226
|
|
|
5698
6227
|
// Set title overrides
|
|
@@ -5798,11 +6327,16 @@ const ReportBuilder = ({
|
|
|
5798
6327
|
});
|
|
5799
6328
|
}
|
|
5800
6329
|
|
|
6330
|
+
// Computed dimensions are self-contained { name, value } pairs with no
|
|
6331
|
+
// provider/relation chain, so they're loaded as-is (no reconstruction needed)
|
|
6332
|
+
const computedDimensions = reportDef.definition?.doc?.query?.computed_dimensions || [];
|
|
6333
|
+
|
|
5801
6334
|
// Set the report state
|
|
5802
6335
|
setReport({
|
|
5803
6336
|
dimensions: reconstructedDimensions,
|
|
5804
6337
|
metrics: reconstructedMetrics,
|
|
5805
|
-
filters: loadedFilters
|
|
6338
|
+
filters: loadedFilters,
|
|
6339
|
+
computedDimensions
|
|
5806
6340
|
});
|
|
5807
6341
|
|
|
5808
6342
|
// Set title overrides
|
|
@@ -5834,7 +6368,8 @@ const ReportBuilder = ({
|
|
|
5834
6368
|
setReport({
|
|
5835
6369
|
dimensions: [],
|
|
5836
6370
|
metrics: [],
|
|
5837
|
-
filters: {}
|
|
6371
|
+
filters: {},
|
|
6372
|
+
computedDimensions: []
|
|
5838
6373
|
});
|
|
5839
6374
|
// Reset title overrides
|
|
5840
6375
|
setTitleOverrides({
|
|
@@ -5930,6 +6465,34 @@ const ReportBuilder = ({
|
|
|
5930
6465
|
dimensions: newOrder
|
|
5931
6466
|
}));
|
|
5932
6467
|
};
|
|
6468
|
+
const handleSaveComputedDimension = computedDimensionData => {
|
|
6469
|
+
setReport(prev => ({
|
|
6470
|
+
...prev,
|
|
6471
|
+
computedDimensions: [...prev.computedDimensions, computedDimensionData]
|
|
6472
|
+
}));
|
|
6473
|
+
};
|
|
6474
|
+
const handleUpdateComputedDimension = (index, computedDimensionData) => {
|
|
6475
|
+
setReport(prev => {
|
|
6476
|
+
const newComputedDimensions = [...prev.computedDimensions];
|
|
6477
|
+
newComputedDimensions[index] = computedDimensionData;
|
|
6478
|
+
return {
|
|
6479
|
+
...prev,
|
|
6480
|
+
computedDimensions: newComputedDimensions
|
|
6481
|
+
};
|
|
6482
|
+
});
|
|
6483
|
+
};
|
|
6484
|
+
const handleRemoveComputedDimension = index => {
|
|
6485
|
+
setReport(prev => ({
|
|
6486
|
+
...prev,
|
|
6487
|
+
computedDimensions: prev.computedDimensions.filter((_, i) => i !== index)
|
|
6488
|
+
}));
|
|
6489
|
+
};
|
|
6490
|
+
const handleReorderComputedDimensions = newOrder => {
|
|
6491
|
+
setReport(prev => ({
|
|
6492
|
+
...prev,
|
|
6493
|
+
computedDimensions: newOrder
|
|
6494
|
+
}));
|
|
6495
|
+
};
|
|
5933
6496
|
const handleSaveMetric = metricData => {
|
|
5934
6497
|
setReport(prev => {
|
|
5935
6498
|
const newReport = {
|
|
@@ -6049,6 +6612,14 @@ const ReportBuilder = ({
|
|
|
6049
6612
|
offset: page * size
|
|
6050
6613
|
};
|
|
6051
6614
|
|
|
6615
|
+
// Only add computed_dimensions if there are any
|
|
6616
|
+
if (report.computedDimensions && report.computedDimensions.length > 0) {
|
|
6617
|
+
queryObj.computed_dimensions = report.computedDimensions.map(cd => ({
|
|
6618
|
+
name: cd.name,
|
|
6619
|
+
value: cd.value
|
|
6620
|
+
}));
|
|
6621
|
+
}
|
|
6622
|
+
|
|
6052
6623
|
// Only add titles if there are any overrides
|
|
6053
6624
|
if (Object.keys(titles).length > 0) {
|
|
6054
6625
|
queryObj.titles = titles;
|
|
@@ -6145,8 +6716,8 @@ const ReportBuilder = ({
|
|
|
6145
6716
|
notify.warning("Please enter a report title");
|
|
6146
6717
|
return;
|
|
6147
6718
|
}
|
|
6148
|
-
if (report.dimensions.length === 0 && report.metrics.length === 0) {
|
|
6149
|
-
notify.warning("Please add at least one dimension or
|
|
6719
|
+
if (report.dimensions.length === 0 && report.metrics.length === 0 && report.computedDimensions.length === 0) {
|
|
6720
|
+
notify.warning("Please add at least one dimension, metric, or computed dimension");
|
|
6150
6721
|
return;
|
|
6151
6722
|
}
|
|
6152
6723
|
try {
|
|
@@ -6181,6 +6752,14 @@ const ReportBuilder = ({
|
|
|
6181
6752
|
order_by: orderBy
|
|
6182
6753
|
};
|
|
6183
6754
|
|
|
6755
|
+
// Only add computed_dimensions if there are any
|
|
6756
|
+
if (report.computedDimensions && report.computedDimensions.length > 0) {
|
|
6757
|
+
queryObj.computed_dimensions = report.computedDimensions.map(cd => ({
|
|
6758
|
+
name: cd.name,
|
|
6759
|
+
value: cd.value
|
|
6760
|
+
}));
|
|
6761
|
+
}
|
|
6762
|
+
|
|
6184
6763
|
// Only add titles if there are any overrides
|
|
6185
6764
|
if (Object.keys(titles).length > 0) {
|
|
6186
6765
|
queryObj.titles = titles;
|
|
@@ -6257,7 +6836,7 @@ const ReportBuilder = ({
|
|
|
6257
6836
|
setSaving(false);
|
|
6258
6837
|
}
|
|
6259
6838
|
};
|
|
6260
|
-
const canSaveReport = selectedProvider && reportTitle.trim() && (report.dimensions.length > 0 || report.metrics.length > 0);
|
|
6839
|
+
const canSaveReport = selectedProvider && reportTitle.trim() && (report.dimensions.length > 0 || report.metrics.length > 0 || report.computedDimensions.length > 0);
|
|
6261
6840
|
return /*#__PURE__*/React__default.createElement(Box$1, {
|
|
6262
6841
|
sx: {
|
|
6263
6842
|
p: 3,
|
|
@@ -6430,7 +7009,7 @@ const ReportBuilder = ({
|
|
|
6430
7009
|
}
|
|
6431
7010
|
}, /*#__PURE__*/React__default.createElement(Tab, {
|
|
6432
7011
|
label: /*#__PURE__*/React__default.createElement(Badge, {
|
|
6433
|
-
badgeContent: report.dimensions.length,
|
|
7012
|
+
badgeContent: report.dimensions.length + report.computedDimensions.length,
|
|
6434
7013
|
sx: {
|
|
6435
7014
|
"& .MuiBadge-badge": {
|
|
6436
7015
|
backgroundColor: "rgb(70, 134, 128)",
|
|
@@ -6440,7 +7019,7 @@ const ReportBuilder = ({
|
|
|
6440
7019
|
}
|
|
6441
7020
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
6442
7021
|
style: {
|
|
6443
|
-
marginRight: report.dimensions.length > 0 ? "12px" : "0"
|
|
7022
|
+
marginRight: report.dimensions.length + report.computedDimensions.length > 0 ? "12px" : "0"
|
|
6444
7023
|
}
|
|
6445
7024
|
}, "Dimensions")),
|
|
6446
7025
|
id: "report-tab-0",
|
|
@@ -6541,7 +7120,12 @@ const ReportBuilder = ({
|
|
|
6541
7120
|
onUpdateTitle: handleUpdateDimensionTitle,
|
|
6542
7121
|
onResetTitle: handleResetDimensionTitle,
|
|
6543
7122
|
existingMetrics: report.metrics,
|
|
6544
|
-
existingFilters: report.filters
|
|
7123
|
+
existingFilters: report.filters,
|
|
7124
|
+
savedComputedDimensions: report.computedDimensions,
|
|
7125
|
+
onSaveComputedDimension: handleSaveComputedDimension,
|
|
7126
|
+
onUpdateComputedDimension: handleUpdateComputedDimension,
|
|
7127
|
+
onRemoveComputedDimension: handleRemoveComputedDimension,
|
|
7128
|
+
onReorderComputedDimensions: handleReorderComputedDimensions
|
|
6545
7129
|
})), /*#__PURE__*/React__default.createElement(TabPanel, {
|
|
6546
7130
|
value: activeTab,
|
|
6547
7131
|
index: 1
|
|
@@ -6579,6 +7163,7 @@ const ReportBuilder = ({
|
|
|
6579
7163
|
reportData: reportData,
|
|
6580
7164
|
dimensions: report.dimensions,
|
|
6581
7165
|
metrics: report.metrics,
|
|
7166
|
+
computedDimensions: report.computedDimensions,
|
|
6582
7167
|
loading: loading,
|
|
6583
7168
|
onPageChange: handlePageChange,
|
|
6584
7169
|
totalRows: totalRows,
|