@bygd/nc-report-ui 0.1.39 → 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 +561 -15
- 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';
|
|
@@ -2809,6 +2810,264 @@ const SortableChip$1 = ({
|
|
|
2809
2810
|
}
|
|
2810
2811
|
}))));
|
|
2811
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
|
+
};
|
|
2812
3071
|
const Dimensions = ({
|
|
2813
3072
|
providersData,
|
|
2814
3073
|
rootProvider,
|
|
@@ -2820,13 +3079,21 @@ const Dimensions = ({
|
|
|
2820
3079
|
onUpdateTitle,
|
|
2821
3080
|
onResetTitle,
|
|
2822
3081
|
existingMetrics = [],
|
|
2823
|
-
existingFilters = {}
|
|
3082
|
+
existingFilters = {},
|
|
3083
|
+
savedComputedDimensions = [],
|
|
3084
|
+
onSaveComputedDimension,
|
|
3085
|
+
onUpdateComputedDimension,
|
|
3086
|
+
onRemoveComputedDimension,
|
|
3087
|
+
onReorderComputedDimensions
|
|
2824
3088
|
}) => {
|
|
2825
3089
|
const reportingContext = useReportingContextOptional();
|
|
2826
3090
|
const parameters = reportingContext?.parameters;
|
|
2827
3091
|
const [isAdding, setIsAdding] = useState(false);
|
|
2828
3092
|
const [dimensionSelectionChain, setDimensionSelectionChain] = useState([]);
|
|
2829
3093
|
const [selectedDimension, setSelectedDimension] = useState("");
|
|
3094
|
+
const [isAddingComputed, setIsAddingComputed] = useState(false);
|
|
3095
|
+
const [computedName, setComputedName] = useState("");
|
|
3096
|
+
const [computedValue, setComputedValue] = useState("");
|
|
2830
3097
|
|
|
2831
3098
|
// Setup drag and drop sensors
|
|
2832
3099
|
const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, {
|
|
@@ -2873,6 +3140,54 @@ const Dimensions = ({
|
|
|
2873
3140
|
onReorderDimensions(newDimensions);
|
|
2874
3141
|
};
|
|
2875
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
|
+
|
|
2876
3191
|
// Get the current provider based on selection chain
|
|
2877
3192
|
const getCurrentProvider = () => {
|
|
2878
3193
|
if (!rootProvider) return null;
|
|
@@ -2936,6 +3251,7 @@ const Dimensions = ({
|
|
|
2936
3251
|
};
|
|
2937
3252
|
const handleAddClick = () => {
|
|
2938
3253
|
setIsAdding(true);
|
|
3254
|
+
setIsAddingComputed(false);
|
|
2939
3255
|
setDimensionSelectionChain([]);
|
|
2940
3256
|
setSelectedDimension("");
|
|
2941
3257
|
};
|
|
@@ -3064,9 +3380,10 @@ const Dimensions = ({
|
|
|
3064
3380
|
sx: {
|
|
3065
3381
|
display: "flex",
|
|
3066
3382
|
justifyContent: "flex-start",
|
|
3383
|
+
gap: 2,
|
|
3067
3384
|
mb: 2
|
|
3068
3385
|
}
|
|
3069
|
-
}, !isAdding && /*#__PURE__*/React__default.createElement(Button, {
|
|
3386
|
+
}, !isAdding && !isAddingComputed && /*#__PURE__*/React__default.createElement(Button, {
|
|
3070
3387
|
variant: "contained",
|
|
3071
3388
|
startIcon: /*#__PURE__*/React__default.createElement(GridAddIcon, null),
|
|
3072
3389
|
onClick: handleAddClick,
|
|
@@ -3084,7 +3401,117 @@ const Dimensions = ({
|
|
|
3084
3401
|
boxShadow: "none"
|
|
3085
3402
|
}
|
|
3086
3403
|
}
|
|
3087
|
-
}, "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, {
|
|
3088
3515
|
elevation: 0,
|
|
3089
3516
|
sx: {
|
|
3090
3517
|
p: 3,
|
|
@@ -3250,6 +3677,50 @@ const Dimensions = ({
|
|
|
3250
3677
|
customTitle: titleOverrides[dim.fullPath],
|
|
3251
3678
|
onUpdateTitle: onUpdateTitle,
|
|
3252
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
|
|
3253
3724
|
})))))));
|
|
3254
3725
|
};
|
|
3255
3726
|
|
|
@@ -5234,6 +5705,7 @@ const ReportDataGrid = ({
|
|
|
5234
5705
|
reportData,
|
|
5235
5706
|
dimensions,
|
|
5236
5707
|
metrics,
|
|
5708
|
+
computedDimensions = [],
|
|
5237
5709
|
loading,
|
|
5238
5710
|
onPageChange,
|
|
5239
5711
|
totalRows = 0,
|
|
@@ -5295,6 +5767,18 @@ const ReportDataGrid = ({
|
|
|
5295
5767
|
});
|
|
5296
5768
|
});
|
|
5297
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
|
+
|
|
5298
5782
|
// Add metric columns
|
|
5299
5783
|
// The API returns metric keys in two different forms depending on the path depth:
|
|
5300
5784
|
//
|
|
@@ -5354,7 +5838,7 @@ const ReportDataGrid = ({
|
|
|
5354
5838
|
});
|
|
5355
5839
|
});
|
|
5356
5840
|
return cols;
|
|
5357
|
-
}, [dimensions, metrics, titleOverrides, parameters]);
|
|
5841
|
+
}, [dimensions, metrics, computedDimensions, titleOverrides, parameters]);
|
|
5358
5842
|
|
|
5359
5843
|
// Transform report data to rows with unique IDs.
|
|
5360
5844
|
// Keys are normalised by replacing dots with underscores so that MUI DataGrid
|
|
@@ -5460,7 +5944,8 @@ const ReportBuilder = ({
|
|
|
5460
5944
|
const [report, setReport] = useState({
|
|
5461
5945
|
dimensions: [],
|
|
5462
5946
|
metrics: [],
|
|
5463
|
-
filters: {}
|
|
5947
|
+
filters: {},
|
|
5948
|
+
computedDimensions: []
|
|
5464
5949
|
});
|
|
5465
5950
|
const [titleOverrides, setTitleOverrides] = useState({
|
|
5466
5951
|
dimensions: {},
|
|
@@ -5501,7 +5986,7 @@ const ReportBuilder = ({
|
|
|
5501
5986
|
|
|
5502
5987
|
// Auto-run report when autoRun is true and report is loaded
|
|
5503
5988
|
useEffect(() => {
|
|
5504
|
-
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)) {
|
|
5505
5990
|
handleRunReport();
|
|
5506
5991
|
}
|
|
5507
5992
|
}, [autoRun, reportLoaded]);
|
|
@@ -5727,11 +6212,16 @@ const ReportBuilder = ({
|
|
|
5727
6212
|
});
|
|
5728
6213
|
}
|
|
5729
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
|
+
|
|
5730
6219
|
// Set the report state
|
|
5731
6220
|
setReport({
|
|
5732
6221
|
dimensions: reconstructedDimensions,
|
|
5733
6222
|
metrics: reconstructedMetrics,
|
|
5734
|
-
filters: loadedFilters
|
|
6223
|
+
filters: loadedFilters,
|
|
6224
|
+
computedDimensions
|
|
5735
6225
|
});
|
|
5736
6226
|
|
|
5737
6227
|
// Set title overrides
|
|
@@ -5837,11 +6327,16 @@ const ReportBuilder = ({
|
|
|
5837
6327
|
});
|
|
5838
6328
|
}
|
|
5839
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
|
+
|
|
5840
6334
|
// Set the report state
|
|
5841
6335
|
setReport({
|
|
5842
6336
|
dimensions: reconstructedDimensions,
|
|
5843
6337
|
metrics: reconstructedMetrics,
|
|
5844
|
-
filters: loadedFilters
|
|
6338
|
+
filters: loadedFilters,
|
|
6339
|
+
computedDimensions
|
|
5845
6340
|
});
|
|
5846
6341
|
|
|
5847
6342
|
// Set title overrides
|
|
@@ -5873,7 +6368,8 @@ const ReportBuilder = ({
|
|
|
5873
6368
|
setReport({
|
|
5874
6369
|
dimensions: [],
|
|
5875
6370
|
metrics: [],
|
|
5876
|
-
filters: {}
|
|
6371
|
+
filters: {},
|
|
6372
|
+
computedDimensions: []
|
|
5877
6373
|
});
|
|
5878
6374
|
// Reset title overrides
|
|
5879
6375
|
setTitleOverrides({
|
|
@@ -5969,6 +6465,34 @@ const ReportBuilder = ({
|
|
|
5969
6465
|
dimensions: newOrder
|
|
5970
6466
|
}));
|
|
5971
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
|
+
};
|
|
5972
6496
|
const handleSaveMetric = metricData => {
|
|
5973
6497
|
setReport(prev => {
|
|
5974
6498
|
const newReport = {
|
|
@@ -6088,6 +6612,14 @@ const ReportBuilder = ({
|
|
|
6088
6612
|
offset: page * size
|
|
6089
6613
|
};
|
|
6090
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
|
+
|
|
6091
6623
|
// Only add titles if there are any overrides
|
|
6092
6624
|
if (Object.keys(titles).length > 0) {
|
|
6093
6625
|
queryObj.titles = titles;
|
|
@@ -6184,8 +6716,8 @@ const ReportBuilder = ({
|
|
|
6184
6716
|
notify.warning("Please enter a report title");
|
|
6185
6717
|
return;
|
|
6186
6718
|
}
|
|
6187
|
-
if (report.dimensions.length === 0 && report.metrics.length === 0) {
|
|
6188
|
-
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");
|
|
6189
6721
|
return;
|
|
6190
6722
|
}
|
|
6191
6723
|
try {
|
|
@@ -6220,6 +6752,14 @@ const ReportBuilder = ({
|
|
|
6220
6752
|
order_by: orderBy
|
|
6221
6753
|
};
|
|
6222
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
|
+
|
|
6223
6763
|
// Only add titles if there are any overrides
|
|
6224
6764
|
if (Object.keys(titles).length > 0) {
|
|
6225
6765
|
queryObj.titles = titles;
|
|
@@ -6296,7 +6836,7 @@ const ReportBuilder = ({
|
|
|
6296
6836
|
setSaving(false);
|
|
6297
6837
|
}
|
|
6298
6838
|
};
|
|
6299
|
-
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);
|
|
6300
6840
|
return /*#__PURE__*/React__default.createElement(Box$1, {
|
|
6301
6841
|
sx: {
|
|
6302
6842
|
p: 3,
|
|
@@ -6469,7 +7009,7 @@ const ReportBuilder = ({
|
|
|
6469
7009
|
}
|
|
6470
7010
|
}, /*#__PURE__*/React__default.createElement(Tab, {
|
|
6471
7011
|
label: /*#__PURE__*/React__default.createElement(Badge, {
|
|
6472
|
-
badgeContent: report.dimensions.length,
|
|
7012
|
+
badgeContent: report.dimensions.length + report.computedDimensions.length,
|
|
6473
7013
|
sx: {
|
|
6474
7014
|
"& .MuiBadge-badge": {
|
|
6475
7015
|
backgroundColor: "rgb(70, 134, 128)",
|
|
@@ -6479,7 +7019,7 @@ const ReportBuilder = ({
|
|
|
6479
7019
|
}
|
|
6480
7020
|
}, /*#__PURE__*/React__default.createElement("span", {
|
|
6481
7021
|
style: {
|
|
6482
|
-
marginRight: report.dimensions.length > 0 ? "12px" : "0"
|
|
7022
|
+
marginRight: report.dimensions.length + report.computedDimensions.length > 0 ? "12px" : "0"
|
|
6483
7023
|
}
|
|
6484
7024
|
}, "Dimensions")),
|
|
6485
7025
|
id: "report-tab-0",
|
|
@@ -6580,7 +7120,12 @@ const ReportBuilder = ({
|
|
|
6580
7120
|
onUpdateTitle: handleUpdateDimensionTitle,
|
|
6581
7121
|
onResetTitle: handleResetDimensionTitle,
|
|
6582
7122
|
existingMetrics: report.metrics,
|
|
6583
|
-
existingFilters: report.filters
|
|
7123
|
+
existingFilters: report.filters,
|
|
7124
|
+
savedComputedDimensions: report.computedDimensions,
|
|
7125
|
+
onSaveComputedDimension: handleSaveComputedDimension,
|
|
7126
|
+
onUpdateComputedDimension: handleUpdateComputedDimension,
|
|
7127
|
+
onRemoveComputedDimension: handleRemoveComputedDimension,
|
|
7128
|
+
onReorderComputedDimensions: handleReorderComputedDimensions
|
|
6584
7129
|
})), /*#__PURE__*/React__default.createElement(TabPanel, {
|
|
6585
7130
|
value: activeTab,
|
|
6586
7131
|
index: 1
|
|
@@ -6618,6 +7163,7 @@ const ReportBuilder = ({
|
|
|
6618
7163
|
reportData: reportData,
|
|
6619
7164
|
dimensions: report.dimensions,
|
|
6620
7165
|
metrics: report.metrics,
|
|
7166
|
+
computedDimensions: report.computedDimensions,
|
|
6621
7167
|
loading: loading,
|
|
6622
7168
|
onPageChange: handlePageChange,
|
|
6623
7169
|
totalRows: totalRows,
|