@perses-dev/dashboards 0.15.0 → 0.16.0

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.
Files changed (71) hide show
  1. package/dist/cjs/components/DashboardToolbar/DashboardToolbar.js +2 -0
  2. package/dist/cjs/components/DownloadButton/DownloadButton.js +109 -0
  3. package/dist/cjs/{utils/component-ids.js → components/DownloadButton/index.js} +11 -14
  4. package/dist/cjs/components/GridLayout/GridTitle.js +8 -12
  5. package/dist/cjs/components/Panel/Panel.js +4 -2
  6. package/dist/cjs/components/Panel/PanelHeader.js +54 -50
  7. package/dist/cjs/components/PanelDrawer/PanelDrawer.test.js +23 -0
  8. package/dist/cjs/components/TimeRangeControls/TimeRangeControls.test.js +3 -1
  9. package/dist/cjs/components/Variables/Variable.js +5 -0
  10. package/dist/cjs/components/Variables/VariableEditor.js +20 -1
  11. package/dist/cjs/components/Variables/VariableEditorForm/VariableEditorForm.js +61 -5
  12. package/dist/cjs/components/Variables/VariableList.js +5 -0
  13. package/dist/cjs/components/index.js +1 -0
  14. package/dist/cjs/context/DashboardProvider/panel-editor-slice.js +40 -15
  15. package/dist/cjs/context/DashboardProvider/panel-group-editor-slice.js +5 -9
  16. package/dist/cjs/context/DashboardProvider/panel-group-slice.js +16 -1
  17. package/dist/cjs/views/ViewDashboard/tests/panelGroups.test.js +15 -21
  18. package/dist/components/DashboardToolbar/DashboardToolbar.d.ts.map +1 -1
  19. package/dist/components/DashboardToolbar/DashboardToolbar.js +2 -0
  20. package/dist/components/DashboardToolbar/DashboardToolbar.js.map +1 -1
  21. package/dist/components/DownloadButton/DownloadButton.d.ts +3 -0
  22. package/dist/components/DownloadButton/DownloadButton.d.ts.map +1 -0
  23. package/dist/components/DownloadButton/DownloadButton.js +60 -0
  24. package/dist/components/DownloadButton/DownloadButton.js.map +1 -0
  25. package/dist/components/DownloadButton/index.d.ts +2 -0
  26. package/dist/components/DownloadButton/index.d.ts.map +1 -0
  27. package/dist/{utils/component-ids.js → components/DownloadButton/index.js} +2 -14
  28. package/dist/components/DownloadButton/index.js.map +1 -0
  29. package/dist/components/GridLayout/GridTitle.d.ts.map +1 -1
  30. package/dist/components/GridLayout/GridTitle.js +8 -12
  31. package/dist/components/GridLayout/GridTitle.js.map +1 -1
  32. package/dist/components/Panel/Panel.d.ts.map +1 -1
  33. package/dist/components/Panel/Panel.js +4 -2
  34. package/dist/components/Panel/Panel.js.map +1 -1
  35. package/dist/components/Panel/PanelHeader.d.ts.map +1 -1
  36. package/dist/components/Panel/PanelHeader.js +54 -50
  37. package/dist/components/Panel/PanelHeader.js.map +1 -1
  38. package/dist/components/PanelDrawer/PanelDrawer.test.js +23 -0
  39. package/dist/components/PanelDrawer/PanelDrawer.test.js.map +1 -1
  40. package/dist/components/TimeRangeControls/TimeRangeControls.test.js +4 -2
  41. package/dist/components/TimeRangeControls/TimeRangeControls.test.js.map +1 -1
  42. package/dist/components/Variables/Variable.js +5 -0
  43. package/dist/components/Variables/Variable.js.map +1 -1
  44. package/dist/components/Variables/VariableEditor.d.ts.map +1 -1
  45. package/dist/components/Variables/VariableEditor.js +22 -3
  46. package/dist/components/Variables/VariableEditor.js.map +1 -1
  47. package/dist/components/Variables/VariableEditorForm/VariableEditorForm.d.ts.map +1 -1
  48. package/dist/components/Variables/VariableEditorForm/VariableEditorForm.js +23 -1
  49. package/dist/components/Variables/VariableEditorForm/VariableEditorForm.js.map +1 -1
  50. package/dist/components/Variables/VariableList.js +5 -0
  51. package/dist/components/Variables/VariableList.js.map +1 -1
  52. package/dist/components/index.d.ts +1 -0
  53. package/dist/components/index.d.ts.map +1 -1
  54. package/dist/components/index.js +1 -0
  55. package/dist/components/index.js.map +1 -1
  56. package/dist/context/DashboardProvider/panel-editor-slice.d.ts.map +1 -1
  57. package/dist/context/DashboardProvider/panel-editor-slice.js +40 -15
  58. package/dist/context/DashboardProvider/panel-editor-slice.js.map +1 -1
  59. package/dist/context/DashboardProvider/panel-group-editor-slice.d.ts.map +1 -1
  60. package/dist/context/DashboardProvider/panel-group-editor-slice.js +5 -9
  61. package/dist/context/DashboardProvider/panel-group-editor-slice.js.map +1 -1
  62. package/dist/context/DashboardProvider/panel-group-slice.d.ts +9 -0
  63. package/dist/context/DashboardProvider/panel-group-slice.d.ts.map +1 -1
  64. package/dist/context/DashboardProvider/panel-group-slice.js +17 -0
  65. package/dist/context/DashboardProvider/panel-group-slice.js.map +1 -1
  66. package/dist/views/ViewDashboard/tests/panelGroups.test.js +15 -21
  67. package/dist/views/ViewDashboard/tests/panelGroups.test.js.map +1 -1
  68. package/package.json +4 -4
  69. package/dist/utils/component-ids.d.ts +0 -8
  70. package/dist/utils/component-ids.d.ts.map +0 -1
  71. package/dist/utils/component-ids.js.map +0 -1
@@ -27,6 +27,7 @@ const _components = require("@perses-dev/components");
27
27
  const _context = require("../../context");
28
28
  const _variables = require("../Variables");
29
29
  const _timeRangeControls = require("../TimeRangeControls");
30
+ const _downloadButton = require("../DownloadButton");
30
31
  function _interopRequireDefault(obj) {
31
32
  return obj && obj.__esModule ? obj : {
32
33
  default: obj
@@ -135,6 +136,7 @@ const DashboardToolbar = (props)=>{
135
136
  },
136
137
  children: [
137
138
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_timeRangeControls.TimeRangeControls, {}),
139
+ /*#__PURE__*/ (0, _jsxRuntime.jsx)(_downloadButton.DownloadButton, {}),
138
140
  isLaptopSize && /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.Button, {
139
141
  variant: "outlined",
140
142
  startIcon: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_pencilOutline.default, {}),
@@ -0,0 +1,109 @@
1
+ // Copyright 2022 The Perses Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+ "use strict";
14
+ Object.defineProperty(exports, "__esModule", {
15
+ value: true
16
+ });
17
+ Object.defineProperty(exports, "DownloadButton", {
18
+ enumerable: true,
19
+ get: ()=>DownloadButton
20
+ });
21
+ const _jsxRuntime = require("react/jsx-runtime");
22
+ const _react = /*#__PURE__*/ _interopRequireWildcard(require("react"));
23
+ const _downloadOutline = /*#__PURE__*/ _interopRequireDefault(require("mdi-material-ui/DownloadOutline"));
24
+ const _material = require("@mui/material");
25
+ const _context = require("../../context");
26
+ function _interopRequireDefault(obj) {
27
+ return obj && obj.__esModule ? obj : {
28
+ default: obj
29
+ };
30
+ }
31
+ function _getRequireWildcardCache(nodeInterop) {
32
+ if (typeof WeakMap !== "function") return null;
33
+ var cacheBabelInterop = new WeakMap();
34
+ var cacheNodeInterop = new WeakMap();
35
+ return (_getRequireWildcardCache = function(nodeInterop) {
36
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
37
+ })(nodeInterop);
38
+ }
39
+ function _interopRequireWildcard(obj, nodeInterop) {
40
+ if (!nodeInterop && obj && obj.__esModule) {
41
+ return obj;
42
+ }
43
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
44
+ return {
45
+ default: obj
46
+ };
47
+ }
48
+ var cache = _getRequireWildcardCache(nodeInterop);
49
+ if (cache && cache.has(obj)) {
50
+ return cache.get(obj);
51
+ }
52
+ var newObj = {};
53
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
54
+ for(var key in obj){
55
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
56
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
57
+ if (desc && (desc.get || desc.set)) {
58
+ Object.defineProperty(newObj, key, desc);
59
+ } else {
60
+ newObj[key] = obj[key];
61
+ }
62
+ }
63
+ }
64
+ newObj.default = obj;
65
+ if (cache) {
66
+ cache.set(obj, newObj);
67
+ }
68
+ return newObj;
69
+ }
70
+ function DownloadButton() {
71
+ const { dashboard } = (0, _context.useDashboard)();
72
+ const hiddenLinkRef = (0, _react.useRef)(null);
73
+ const onDownloadButtonClick = ()=>{
74
+ if (!hiddenLinkRef || !hiddenLinkRef.current) return;
75
+ // Create blob URL
76
+ const hiddenLinkUrl = URL.createObjectURL(new Blob([
77
+ JSON.stringify(dashboard)
78
+ ], {
79
+ type: 'application/json'
80
+ }));
81
+ // Simulate click
82
+ hiddenLinkRef.current.href = hiddenLinkUrl;
83
+ hiddenLinkRef.current.click();
84
+ // Remove blob URL (for memory management)
85
+ URL.revokeObjectURL(hiddenLinkUrl);
86
+ };
87
+ return /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
88
+ children: [
89
+ /*#__PURE__*/ (0, _jsxRuntime.jsx)(DownloadIconButton, {
90
+ title: "Download JSON",
91
+ onClick: onDownloadButtonClick,
92
+ children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_downloadOutline.default, {})
93
+ }),
94
+ /*#__PURE__*/ (0, _jsxRuntime.jsx)("a", {
95
+ ref: hiddenLinkRef,
96
+ style: {
97
+ display: 'none'
98
+ },
99
+ download: `${dashboard.metadata.name}.json`
100
+ })
101
+ ]
102
+ });
103
+ }
104
+ const DownloadIconButton = (0, _material.styled)(_material.IconButton)(({ theme })=>({
105
+ border: `1px solid ${theme.palette.grey[300]}`,
106
+ borderRadius: theme.shape.borderRadius,
107
+ padding: '4px',
108
+ color: theme.palette.grey[900]
109
+ }));
@@ -14,18 +14,15 @@
14
14
  Object.defineProperty(exports, "__esModule", {
15
15
  value: true
16
16
  });
17
- Object.defineProperty(exports, "useId", {
18
- enumerable: true,
19
- get: ()=>useId
20
- });
21
- const _react = require("react");
22
- function useId(prefix) {
23
- if (globalThis.useIdValue === undefined) {
24
- globalThis.useIdValue = 0;
25
- }
26
- const id = (0, _react.useRef)(undefined);
27
- if (id.current === undefined) {
28
- id.current = `${prefix}-${globalThis.useIdValue++}`;
29
- }
30
- return id.current;
17
+ _exportStar(require("./DownloadButton"), exports);
18
+ function _exportStar(from, to) {
19
+ Object.keys(from).forEach(function(k) {
20
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) Object.defineProperty(to, k, {
21
+ enumerable: true,
22
+ get: function() {
23
+ return from[k];
24
+ }
25
+ });
26
+ });
27
+ return from;
31
28
  }
@@ -19,11 +19,10 @@ Object.defineProperty(exports, "GridTitle", {
19
19
  get: ()=>GridTitle
20
20
  });
21
21
  const _jsxRuntime = require("react/jsx-runtime");
22
- const _react = require("react");
23
22
  const _material = require("@mui/material");
24
23
  const _chevronUp = /*#__PURE__*/ _interopRequireDefault(require("mdi-material-ui/ChevronUp"));
25
24
  const _chevronDown = /*#__PURE__*/ _interopRequireDefault(require("mdi-material-ui/ChevronDown"));
26
- const _plus = /*#__PURE__*/ _interopRequireDefault(require("mdi-material-ui/Plus"));
25
+ const _chartBoxPlusOutline = /*#__PURE__*/ _interopRequireDefault(require("mdi-material-ui/ChartBoxPlusOutline"));
27
26
  const _pencilOutline = /*#__PURE__*/ _interopRequireDefault(require("mdi-material-ui/PencilOutline"));
28
27
  const _arrowUp = /*#__PURE__*/ _interopRequireDefault(require("mdi-material-ui/ArrowUp"));
29
28
  const _arrowDown = /*#__PURE__*/ _interopRequireDefault(require("mdi-material-ui/ArrowDown"));
@@ -36,7 +35,6 @@ function _interopRequireDefault(obj) {
36
35
  }
37
36
  function GridTitle(props) {
38
37
  const { panelGroupId , title , collapse } = props;
39
- const [isHovered, setIsHovered] = (0, _react.useState)(false);
40
38
  const { openAddPanel , openEditPanelGroup , moveUp , moveDown } = (0, _context.usePanelGroupActions)(panelGroupId);
41
39
  const { openDeletePanelGroupDialog } = (0, _context.useDeletePanelGroupDialog)();
42
40
  const { isEditMode } = (0, _context.useEditMode)();
@@ -55,8 +53,6 @@ function GridTitle(props) {
55
53
  padding: (theme)=>theme.spacing(1),
56
54
  backgroundColor: (theme)=>theme.palette.background.default
57
55
  },
58
- onMouseEnter: ()=>setIsHovered(true),
59
- onMouseLeave: ()=>setIsHovered(false),
60
56
  children: collapse ? /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
61
57
  children: [
62
58
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.IconButton, {
@@ -64,35 +60,35 @@ function GridTitle(props) {
64
60
  children: collapse.isOpen ? /*#__PURE__*/ (0, _jsxRuntime.jsx)(_chevronUp.default, {}) : /*#__PURE__*/ (0, _jsxRuntime.jsx)(_chevronDown.default, {})
65
61
  }),
66
62
  text,
67
- isEditMode && isHovered && /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_material.Stack, {
63
+ isEditMode && /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_material.Stack, {
68
64
  direction: "row",
69
65
  sx: {
70
66
  marginLeft: 'auto'
71
67
  },
72
68
  children: [
73
69
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.IconButton, {
74
- "aria-label": "add panel to group",
70
+ "aria-label": `add panel to group ${title}`,
75
71
  onClick: openAddPanel,
76
- children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_plus.default, {})
72
+ children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_chartBoxPlusOutline.default, {})
77
73
  }),
78
74
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.IconButton, {
79
- "aria-label": "edit group",
75
+ "aria-label": `edit group ${title}`,
80
76
  onClick: openEditPanelGroup,
81
77
  children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_pencilOutline.default, {})
82
78
  }),
83
79
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.IconButton, {
84
- "aria-label": "delete group",
80
+ "aria-label": `delete group ${title}`,
85
81
  onClick: ()=>openDeletePanelGroupDialog(panelGroupId),
86
82
  children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_deleteOutline.default, {})
87
83
  }),
88
84
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.IconButton, {
89
- "aria-label": "move group down",
85
+ "aria-label": `move group ${title} down`,
90
86
  disabled: moveDown === undefined,
91
87
  onClick: moveDown,
92
88
  children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_arrowDown.default, {})
93
89
  }),
94
90
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.IconButton, {
95
- "aria-label": "move group up",
91
+ "aria-label": `move group ${title} up`,
96
92
  disabled: moveUp === undefined,
97
93
  onClick: moveUp,
98
94
  children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_arrowUp.default, {})
@@ -24,7 +24,6 @@ const _useResizeObserver = /*#__PURE__*/ _interopRequireDefault(require("use-res
24
24
  const _reactIntersectionObserver = require("react-intersection-observer");
25
25
  const _components = require("@perses-dev/components");
26
26
  const _material = require("@mui/material");
27
- const _componentIds = require("../../utils/component-ids");
28
27
  const _panelHeader = require("./PanelHeader");
29
28
  const _panelContent = require("./PanelContent");
30
29
  function _interopRequireDefault(obj) {
@@ -35,7 +34,7 @@ function _interopRequireDefault(obj) {
35
34
  function Panel(props) {
36
35
  const { definition , editHandlers , onMouseEnter , onMouseLeave , sx , ...others } = props;
37
36
  // Make sure we have an ID we can use for aria attributes
38
- const generatedPanelId = (0, _componentIds.useId)('Panel');
37
+ const generatedPanelId = (0, _components.useId)('Panel');
39
38
  const headerId = `${generatedPanelId}-header`;
40
39
  const [contentElement, setContentElement] = (0, _react.useState)(null);
41
40
  const [isHovered, setIsHovered] = (0, _react.useState)(false);
@@ -109,6 +108,9 @@ function Panel(props) {
109
108
  ref: setContentElement,
110
109
  children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_components.ErrorBoundary, {
111
110
  FallbackComponent: _components.ErrorAlert,
111
+ resetKeys: [
112
+ definition.spec.plugin.spec
113
+ ],
112
114
  children: inView === true && /*#__PURE__*/ (0, _jsxRuntime.jsx)(_panelContent.PanelContent, {
113
115
  panelPluginKind: definition.spec.plugin.kind,
114
116
  spec: definition.spec.plugin.spec,
@@ -33,58 +33,55 @@ function _interopRequireDefault(obj) {
33
33
  function PanelHeader({ id , title , description , editHandlers , isHovered , sx , ...rest }) {
34
34
  const titleElementId = `${id}-title`;
35
35
  const descriptionTooltipId = `${id}-description`;
36
- // Don't show any actions unless panel is hovered
37
36
  let action = undefined;
38
- if (isHovered) {
39
- if (editHandlers !== undefined) {
40
- // If there are edit handlers, always just show the edit buttons
41
- action = /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_material.Stack, {
42
- direction: "row",
43
- alignItems: "center",
44
- spacing: 0.5,
45
- children: [
46
- /*#__PURE__*/ (0, _jsxRuntime.jsx)(HeaderIconButton, {
47
- "aria-label": "edit panel",
48
- size: "small",
49
- onClick: editHandlers.onEditPanelClick,
50
- children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_pencilOutline.default, {})
51
- }),
52
- /*#__PURE__*/ (0, _jsxRuntime.jsx)(HeaderIconButton, {
53
- "aria-label": "delete panel",
54
- size: "small",
55
- onClick: editHandlers.onDeletePanelClick,
56
- children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_deleteOutline.default, {})
57
- }),
58
- /*#__PURE__*/ (0, _jsxRuntime.jsx)(HeaderIconButton, {
59
- "aria-label": "drag handle",
60
- size: "small",
61
- children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_dragVertical.default, {
62
- className: "drag-handle",
63
- sx: {
64
- cursor: 'grab'
65
- }
66
- })
67
- })
68
- ]
69
- });
70
- } else if (description !== undefined) {
71
- // If there aren't edit handlers and we have a description, show a button with a tooltip for the panel description
72
- action = /*#__PURE__*/ (0, _jsxRuntime.jsx)(_components.InfoTooltip, {
73
- id: descriptionTooltipId,
74
- description: description,
75
- placement: _components.TooltipPlacement.Bottom,
76
- children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(HeaderIconButton, {
77
- "aria-label": "Panel Description",
78
- children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_informationOutline.default, {
79
- "aria-describedby": "info-tooltip",
80
- "aria-hidden": false,
37
+ if (editHandlers !== undefined) {
38
+ // If there are edit handlers, always just show the edit buttons
39
+ action = /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_material.Stack, {
40
+ direction: "row",
41
+ alignItems: "center",
42
+ spacing: 0.5,
43
+ children: [
44
+ /*#__PURE__*/ (0, _jsxRuntime.jsx)(HeaderIconButton, {
45
+ "aria-label": `edit panel ${title}`,
46
+ size: "small",
47
+ onClick: editHandlers.onEditPanelClick,
48
+ children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_pencilOutline.default, {})
49
+ }),
50
+ /*#__PURE__*/ (0, _jsxRuntime.jsx)(HeaderIconButton, {
51
+ "aria-label": `delete panel ${title}`,
52
+ size: "small",
53
+ onClick: editHandlers.onDeletePanelClick,
54
+ children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_deleteOutline.default, {})
55
+ }),
56
+ /*#__PURE__*/ (0, _jsxRuntime.jsx)(HeaderIconButton, {
57
+ "aria-label": `move panel ${title}`,
58
+ size: "small",
59
+ children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_dragVertical.default, {
60
+ className: "drag-handle",
81
61
  sx: {
82
- color: (theme)=>theme.palette.grey[700]
62
+ cursor: 'grab'
83
63
  }
84
64
  })
85
65
  })
86
- });
87
- }
66
+ ]
67
+ });
68
+ } else if (description !== undefined && isHovered) {
69
+ // If there aren't edit handlers and we have a description, show a button with a tooltip for the panel description
70
+ action = /*#__PURE__*/ (0, _jsxRuntime.jsx)(_components.InfoTooltip, {
71
+ id: descriptionTooltipId,
72
+ description: description,
73
+ placement: _components.TooltipPlacement.Bottom,
74
+ children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(HeaderIconButton, {
75
+ "aria-label": "Panel Description",
76
+ children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_informationOutline.default, {
77
+ "aria-describedby": "info-tooltip",
78
+ "aria-hidden": false,
79
+ sx: {
80
+ color: (theme)=>theme.palette.grey[700]
81
+ }
82
+ })
83
+ })
84
+ });
88
85
  }
89
86
  return /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.CardHeader, {
90
87
  id: id,
@@ -95,6 +92,15 @@ function PanelHeader({ id , title , description , editHandlers , isHovered , sx
95
92
  title: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.Typography, {
96
93
  id: titleElementId,
97
94
  variant: "subtitle1",
95
+ sx: {
96
+ // `minHeight` guarantees that the header has the correct height
97
+ // when there is no title (i.e. in the preview)
98
+ lineHeight: '24px',
99
+ minHeight: '24px',
100
+ whiteSpace: 'nowrap',
101
+ overflow: 'hidden',
102
+ textOverflow: 'ellipsis'
103
+ },
98
104
  children: title
99
105
  }),
100
106
  action: action,
@@ -102,9 +108,7 @@ function PanelHeader({ id , title , description , editHandlers , isHovered , sx
102
108
  padding: theme.spacing(1),
103
109
  borderBottom: `solid 1px ${theme.palette.divider}`,
104
110
  '.MuiCardHeader-content': {
105
- whiteSpace: 'nowrap',
106
- overflow: 'hidden',
107
- textOverflow: 'ellipsis'
111
+ overflow: 'hidden'
108
112
  }
109
113
  }), sx),
110
114
  ...rest
@@ -70,6 +70,29 @@ describe('Panel Drawer', ()=>{
70
70
  }
71
71
  });
72
72
  });
73
+ it('should add panel with duplicate panel name', async ()=>{
74
+ const storeApi = renderPanelDrawer();
75
+ (0, _testUtils.act)(()=>storeApi.getState().openAddPanel());
76
+ const nameInput = await _react.screen.findByLabelText(/Name/);
77
+ _userEvent.default.type(nameInput, 'cpu');
78
+ _userEvent.default.click(_react.screen.getByText('Add'));
79
+ const panels = storeApi.getState().panels;
80
+ expect(panels).toMatchObject({
81
+ // make sure we don't have duplicate panel key by appending "-1"
82
+ 'cpu-1': {
83
+ kind: 'Panel',
84
+ spec: {
85
+ display: {
86
+ name: 'cpu'
87
+ },
88
+ plugin: {
89
+ kind: '',
90
+ spec: {}
91
+ }
92
+ }
93
+ }
94
+ });
95
+ });
73
96
  it('should edit an existing panel', async ()=>{
74
97
  const storeApi = renderPanelDrawer();
75
98
  // Open the drawer for an existing panel
@@ -80,7 +80,9 @@ describe('TimeRangeControls', ()=>{
80
80
  _userEvent.default.click(secondSelected);
81
81
  expect(history.location.search).toEqual('?start=12h');
82
82
  // back button should return to first option selected
83
- history.back();
83
+ (0, _react.act)(()=>{
84
+ history.back();
85
+ });
84
86
  expect(history.location.search).toEqual('?start=5m');
85
87
  });
86
88
  // TODO: add additional tests for absolute time selection, other inputs, form validation, etc.
@@ -199,6 +199,11 @@ function ListVariable({ name }) {
199
199
  disabled: true,
200
200
  children: "Loading"
201
201
  }),
202
+ finalOptions.length === 0 && /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.MenuItem, {
203
+ value: "empty",
204
+ disabled: true,
205
+ children: "No options"
206
+ }),
202
207
  finalOptions.map((option)=>/*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.MenuItem, {
203
208
  value: option.value,
204
209
  children: option.label
@@ -32,9 +32,24 @@ function _interopRequireDefault(obj) {
32
32
  default: obj
33
33
  };
34
34
  }
35
+ function getValidation(variableDefinitions) {
36
+ const errors = [];
37
+ /** Variable names must be unique */ const variableNames = variableDefinitions.map((variableDefinition)=>variableDefinition.spec.name);
38
+ const uniqueVariableNames = new Set(variableNames);
39
+ if (variableNames.length !== uniqueVariableNames.size) {
40
+ errors.push('Variable names must be unique');
41
+ }
42
+ return {
43
+ errors: errors,
44
+ isValid: errors.length === 0
45
+ };
46
+ }
35
47
  function VariableEditor(props) {
36
48
  const [variableDefinitions, setVariableDefinitions] = (0, _useImmer.useImmer)(props.variableDefinitions);
37
49
  const [variableEditIdx, setVariableEditIdx] = (0, _react.useState)(null);
50
+ const validation = (0, _react.useMemo)(()=>getValidation(variableDefinitions), [
51
+ variableDefinitions
52
+ ]);
38
53
  const currentEditingVariableDefinition = typeof variableEditIdx === 'number' && variableDefinitions[variableEditIdx];
39
54
  const removeVariable = (index)=>{
40
55
  setVariableDefinitions((draft)=>{
@@ -118,7 +133,7 @@ function VariableEditor(props) {
118
133
  justifyContent: "end",
119
134
  children: [
120
135
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.Button, {
121
- disabled: props.variableDefinitions === variableDefinitions,
136
+ disabled: props.variableDefinitions === variableDefinitions || !validation.isValid,
122
137
  variant: "contained",
123
138
  onClick: ()=>{
124
139
  props.onChange(variableDefinitions);
@@ -142,6 +157,10 @@ function VariableEditor(props) {
142
157
  /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_material.Stack, {
143
158
  spacing: 2,
144
159
  children: [
160
+ !validation.isValid && validation.errors.map((error)=>/*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.Alert, {
161
+ severity: "error",
162
+ children: error
163
+ }, error)),
145
164
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.TableContainer, {
146
165
  component: _material.Paper,
147
166
  children: /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_material.Table, {
@@ -19,20 +19,69 @@ Object.defineProperty(exports, "VariableEditForm", {
19
19
  get: ()=>VariableEditForm
20
20
  });
21
21
  const _jsxRuntime = require("react/jsx-runtime");
22
- const _react = /*#__PURE__*/ _interopRequireDefault(require("react"));
22
+ const _react = /*#__PURE__*/ _interopRequireWildcard(require("react"));
23
23
  const _material = require("@mui/material");
24
24
  const _useImmer = require("use-immer");
25
25
  const _pluginSystem = require("@perses-dev/plugin-system");
26
26
  const _variableEditorFormModel = require("./variable-editor-form-model");
27
- function _interopRequireDefault(obj) {
28
- return obj && obj.__esModule ? obj : {
29
- default: obj
30
- };
27
+ function _getRequireWildcardCache(nodeInterop) {
28
+ if (typeof WeakMap !== "function") return null;
29
+ var cacheBabelInterop = new WeakMap();
30
+ var cacheNodeInterop = new WeakMap();
31
+ return (_getRequireWildcardCache = function(nodeInterop) {
32
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
33
+ })(nodeInterop);
34
+ }
35
+ function _interopRequireWildcard(obj, nodeInterop) {
36
+ if (!nodeInterop && obj && obj.__esModule) {
37
+ return obj;
38
+ }
39
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
40
+ return {
41
+ default: obj
42
+ };
43
+ }
44
+ var cache = _getRequireWildcardCache(nodeInterop);
45
+ if (cache && cache.has(obj)) {
46
+ return cache.get(obj);
47
+ }
48
+ var newObj = {};
49
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
50
+ for(var key in obj){
51
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
52
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
53
+ if (desc && (desc.get || desc.set)) {
54
+ Object.defineProperty(newObj, key, desc);
55
+ } else {
56
+ newObj[key] = obj[key];
57
+ }
58
+ }
59
+ }
60
+ newObj.default = obj;
61
+ if (cache) {
62
+ cache.set(obj, newObj);
63
+ }
64
+ return newObj;
31
65
  }
32
66
  const VARIABLE_TYPES = [
33
67
  'ListVariable',
34
68
  'TextVariable'
35
69
  ];
70
+ // TODO: Replace with proper validation library
71
+ function getValidation(state) {
72
+ /** Name validation */ let name = null;
73
+ if (!state.name) {
74
+ name = 'Name is required';
75
+ }
76
+ // name can only contain alphanumeric characters and underscores and no spaces
77
+ if (state.name && !/^[a-zA-Z0-9_-]+$/.test(state.name)) {
78
+ name = 'Name can only contain alphanumeric characters, underscores, and dashes';
79
+ }
80
+ return {
81
+ name,
82
+ isValid: !name
83
+ };
84
+ }
36
85
  const SectionHeader = ({ children })=>/*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.Typography, {
37
86
  pb: 2,
38
87
  variant: "subtitle1",
@@ -40,6 +89,9 @@ const SectionHeader = ({ children })=>/*#__PURE__*/ (0, _jsxRuntime.jsx)(_mater
40
89
  });
41
90
  function VariableEditForm({ initialVariableDefinition , onChange , onCancel }) {
42
91
  const [state, setState] = (0, _useImmer.useImmer)((0, _variableEditorFormModel.getInitialState)(initialVariableDefinition));
92
+ const validation = (0, _react.useMemo)(()=>getValidation(state), [
93
+ state
94
+ ]);
43
95
  return /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_material.Box, {
44
96
  children: [
45
97
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(SectionHeader, {
@@ -54,9 +106,12 @@ function VariableEditForm({ initialVariableDefinition , onChange , onCancel })
54
106
  item: true,
55
107
  xs: 6,
56
108
  children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.TextField, {
109
+ required: true,
110
+ error: !!validation.name,
57
111
  fullWidth: true,
58
112
  label: "Name",
59
113
  value: state.name,
114
+ helperText: validation.name,
60
115
  onChange: (v)=>{
61
116
  setState((draft)=>{
62
117
  draft.name = v.target.value;
@@ -220,6 +275,7 @@ function VariableEditForm({ initialVariableDefinition , onChange , onCancel })
220
275
  justifyContent: "end",
221
276
  children: [
222
277
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.Button, {
278
+ disabled: !validation.isValid,
223
279
  variant: "contained",
224
280
  onClick: ()=>{
225
281
  onChange((0, _variableEditorFormModel.getVariableDefinitionFromState)(state));
@@ -89,6 +89,11 @@ function TemplateVariableList(props) {
89
89
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.Drawer, {
90
90
  anchor: 'right',
91
91
  open: isEditing,
92
+ PaperProps: {
93
+ sx: {
94
+ width: '50%'
95
+ }
96
+ },
92
97
  children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_variableEditor.VariableEditor, {
93
98
  onCancel: ()=>{
94
99
  setIsEditing(false);
@@ -18,6 +18,7 @@ _exportStar(require("./Dashboard"), exports);
18
18
  _exportStar(require("./DashboardToolbar"), exports);
19
19
  _exportStar(require("./DeletePanelDialog"), exports);
20
20
  _exportStar(require("./DeletePanelGroupDialog"), exports);
21
+ _exportStar(require("./DownloadButton"), exports);
21
22
  _exportStar(require("./GridLayout"), exports);
22
23
  _exportStar(require("./Panel"), exports);
23
24
  _exportStar(require("./PanelDrawer"), exports);