@perses-dev/dashboards 0.14.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 (144) hide show
  1. package/dist/cjs/components/DashboardToolbar/DashboardToolbar.js +9 -3
  2. package/dist/cjs/components/DownloadButton/DownloadButton.js +109 -0
  3. package/dist/cjs/{utils → components/DownloadButton}/index.js +1 -1
  4. package/dist/cjs/{css/styles.js → components/GridLayout/GridContainer.js} +66 -39
  5. package/dist/cjs/components/GridLayout/GridLayout.js +51 -64
  6. package/dist/cjs/components/GridLayout/GridTitle.js +11 -14
  7. package/dist/cjs/components/Panel/Panel.js +4 -2
  8. package/dist/cjs/components/Panel/PanelHeader.js +52 -48
  9. package/dist/cjs/components/PanelDrawer/PanelDrawer.test.js +23 -0
  10. package/dist/cjs/components/TimeRangeControls/TimeRangeControls.js +15 -54
  11. package/dist/cjs/components/TimeRangeControls/TimeRangeControls.test.js +27 -11
  12. package/dist/cjs/components/Variables/Variable.js +16 -4
  13. package/dist/cjs/components/Variables/VariableEditor.js +21 -1
  14. package/dist/cjs/components/Variables/VariableEditorForm/VariableEditorForm.js +63 -7
  15. package/dist/cjs/components/Variables/VariableEditorForm/variable-editor-form-model.js +3 -3
  16. package/dist/cjs/components/Variables/VariableList.js +81 -17
  17. package/dist/cjs/components/index.js +1 -0
  18. package/dist/cjs/context/DashboardProvider/DashboardProvider.js +2 -1
  19. package/dist/cjs/context/DashboardProvider/dashboard-provider-api.js +68 -39
  20. package/dist/cjs/context/DashboardProvider/panel-editor-slice.js +40 -15
  21. package/dist/cjs/context/DashboardProvider/panel-group-editor-slice.js +5 -9
  22. package/dist/cjs/context/DashboardProvider/panel-group-slice.js +16 -1
  23. package/dist/cjs/context/{TimeRangeProvider.js → TimeRangeProvider/TimeRangeProvider.js} +4 -4
  24. package/dist/cjs/{utils/component-ids.js → context/TimeRangeProvider/index.js} +12 -14
  25. package/dist/cjs/{utils/time-range-params.js → context/TimeRangeProvider/query-params.js} +11 -5
  26. package/dist/cjs/index.js +0 -1
  27. package/dist/cjs/test/testDashboard.js +1 -1
  28. package/dist/cjs/views/ViewDashboard/DashboardApp.js +2 -1
  29. package/dist/cjs/views/ViewDashboard/ViewDashboard.js +6 -7
  30. package/dist/cjs/views/ViewDashboard/tests/panelGroups.test.js +16 -22
  31. package/dist/components/DashboardToolbar/DashboardToolbar.d.ts +1 -0
  32. package/dist/components/DashboardToolbar/DashboardToolbar.d.ts.map +1 -1
  33. package/dist/components/DashboardToolbar/DashboardToolbar.js +9 -3
  34. package/dist/components/DashboardToolbar/DashboardToolbar.js.map +1 -1
  35. package/dist/components/DownloadButton/DownloadButton.d.ts +3 -0
  36. package/dist/components/DownloadButton/DownloadButton.d.ts.map +1 -0
  37. package/dist/components/DownloadButton/DownloadButton.js +60 -0
  38. package/dist/components/DownloadButton/DownloadButton.js.map +1 -0
  39. package/dist/components/DownloadButton/index.d.ts +2 -0
  40. package/dist/components/DownloadButton/index.d.ts.map +1 -0
  41. package/dist/{utils → components/DownloadButton}/index.js +1 -1
  42. package/dist/components/DownloadButton/index.js.map +1 -0
  43. package/dist/components/GridLayout/GridContainer.d.ts +6 -0
  44. package/dist/components/GridLayout/GridContainer.d.ts.map +1 -0
  45. package/dist/{css/styles.js → components/GridLayout/GridContainer.js} +65 -38
  46. package/dist/components/GridLayout/GridContainer.js.map +1 -0
  47. package/dist/components/GridLayout/GridLayout.d.ts +1 -2
  48. package/dist/components/GridLayout/GridLayout.d.ts.map +1 -1
  49. package/dist/components/GridLayout/GridLayout.js +53 -66
  50. package/dist/components/GridLayout/GridLayout.js.map +1 -1
  51. package/dist/components/GridLayout/GridTitle.d.ts.map +1 -1
  52. package/dist/components/GridLayout/GridTitle.js +12 -15
  53. package/dist/components/GridLayout/GridTitle.js.map +1 -1
  54. package/dist/components/Panel/Panel.d.ts.map +1 -1
  55. package/dist/components/Panel/Panel.js +4 -2
  56. package/dist/components/Panel/Panel.js.map +1 -1
  57. package/dist/components/Panel/PanelHeader.d.ts.map +1 -1
  58. package/dist/components/Panel/PanelHeader.js +52 -48
  59. package/dist/components/Panel/PanelHeader.js.map +1 -1
  60. package/dist/components/PanelDrawer/PanelDrawer.test.js +23 -0
  61. package/dist/components/PanelDrawer/PanelDrawer.test.js.map +1 -1
  62. package/dist/components/TimeRangeControls/TimeRangeControls.d.ts.map +1 -1
  63. package/dist/components/TimeRangeControls/TimeRangeControls.js +19 -58
  64. package/dist/components/TimeRangeControls/TimeRangeControls.js.map +1 -1
  65. package/dist/components/TimeRangeControls/TimeRangeControls.test.js +28 -12
  66. package/dist/components/TimeRangeControls/TimeRangeControls.test.js.map +1 -1
  67. package/dist/components/Variables/Variable.js +16 -4
  68. package/dist/components/Variables/Variable.js.map +1 -1
  69. package/dist/components/Variables/VariableEditor.d.ts.map +1 -1
  70. package/dist/components/Variables/VariableEditor.js +23 -3
  71. package/dist/components/Variables/VariableEditor.js.map +1 -1
  72. package/dist/components/Variables/VariableEditorForm/VariableEditorForm.d.ts.map +1 -1
  73. package/dist/components/Variables/VariableEditorForm/VariableEditorForm.js +25 -3
  74. package/dist/components/Variables/VariableEditorForm/VariableEditorForm.js.map +1 -1
  75. package/dist/components/Variables/VariableEditorForm/variable-editor-form-model.d.ts +1 -1
  76. package/dist/components/Variables/VariableEditorForm/variable-editor-form-model.d.ts.map +1 -1
  77. package/dist/components/Variables/VariableEditorForm/variable-editor-form-model.js +3 -3
  78. package/dist/components/Variables/VariableEditorForm/variable-editor-form-model.js.map +1 -1
  79. package/dist/components/Variables/VariableList.d.ts +5 -1
  80. package/dist/components/Variables/VariableList.d.ts.map +1 -1
  81. package/dist/components/Variables/VariableList.js +43 -18
  82. package/dist/components/Variables/VariableList.js.map +1 -1
  83. package/dist/components/index.d.ts +1 -0
  84. package/dist/components/index.d.ts.map +1 -1
  85. package/dist/components/index.js +1 -0
  86. package/dist/components/index.js.map +1 -1
  87. package/dist/context/DashboardProvider/DashboardProvider.js +2 -1
  88. package/dist/context/DashboardProvider/DashboardProvider.js.map +1 -1
  89. package/dist/context/DashboardProvider/dashboard-provider-api.d.ts +1 -1
  90. package/dist/context/DashboardProvider/dashboard-provider-api.d.ts.map +1 -1
  91. package/dist/context/DashboardProvider/dashboard-provider-api.js +70 -41
  92. package/dist/context/DashboardProvider/dashboard-provider-api.js.map +1 -1
  93. package/dist/context/DashboardProvider/panel-editor-slice.d.ts.map +1 -1
  94. package/dist/context/DashboardProvider/panel-editor-slice.js +40 -15
  95. package/dist/context/DashboardProvider/panel-editor-slice.js.map +1 -1
  96. package/dist/context/DashboardProvider/panel-group-editor-slice.d.ts.map +1 -1
  97. package/dist/context/DashboardProvider/panel-group-editor-slice.js +5 -9
  98. package/dist/context/DashboardProvider/panel-group-editor-slice.js.map +1 -1
  99. package/dist/context/DashboardProvider/panel-group-slice.d.ts +9 -0
  100. package/dist/context/DashboardProvider/panel-group-slice.d.ts.map +1 -1
  101. package/dist/context/DashboardProvider/panel-group-slice.js +17 -0
  102. package/dist/context/DashboardProvider/panel-group-slice.js.map +1 -1
  103. package/dist/context/{TimeRangeProvider.d.ts → TimeRangeProvider/TimeRangeProvider.d.ts} +2 -2
  104. package/dist/context/TimeRangeProvider/TimeRangeProvider.d.ts.map +1 -0
  105. package/dist/context/{TimeRangeProvider.js → TimeRangeProvider/TimeRangeProvider.js} +4 -4
  106. package/dist/context/TimeRangeProvider/TimeRangeProvider.js.map +1 -0
  107. package/dist/context/TimeRangeProvider/index.d.ts +3 -0
  108. package/dist/context/TimeRangeProvider/index.d.ts.map +1 -0
  109. package/dist/{utils/component-ids.js → context/TimeRangeProvider/index.js} +3 -14
  110. package/dist/context/TimeRangeProvider/index.js.map +1 -0
  111. package/dist/{utils/time-range-params.d.ts → context/TimeRangeProvider/query-params.d.ts} +3 -3
  112. package/dist/context/TimeRangeProvider/query-params.d.ts.map +1 -0
  113. package/dist/{utils/time-range-params.js → context/TimeRangeProvider/query-params.js} +13 -7
  114. package/dist/context/TimeRangeProvider/query-params.js.map +1 -0
  115. package/dist/index.d.ts +0 -1
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +0 -1
  118. package/dist/index.js.map +1 -1
  119. package/dist/test/testDashboard.js +1 -1
  120. package/dist/test/testDashboard.js.map +1 -1
  121. package/dist/views/ViewDashboard/DashboardApp.d.ts +1 -0
  122. package/dist/views/ViewDashboard/DashboardApp.d.ts.map +1 -1
  123. package/dist/views/ViewDashboard/DashboardApp.js +2 -1
  124. package/dist/views/ViewDashboard/DashboardApp.js.map +1 -1
  125. package/dist/views/ViewDashboard/ViewDashboard.d.ts +1 -0
  126. package/dist/views/ViewDashboard/ViewDashboard.d.ts.map +1 -1
  127. package/dist/views/ViewDashboard/ViewDashboard.js +6 -7
  128. package/dist/views/ViewDashboard/ViewDashboard.js.map +1 -1
  129. package/dist/views/ViewDashboard/tests/panelGroups.test.js +16 -22
  130. package/dist/views/ViewDashboard/tests/panelGroups.test.js.map +1 -1
  131. package/package.json +4 -4
  132. package/dist/context/TimeRangeProvider.d.ts.map +0 -1
  133. package/dist/context/TimeRangeProvider.js.map +0 -1
  134. package/dist/css/styles.d.ts +0 -172
  135. package/dist/css/styles.d.ts.map +0 -1
  136. package/dist/css/styles.js.map +0 -1
  137. package/dist/utils/component-ids.d.ts +0 -8
  138. package/dist/utils/component-ids.d.ts.map +0 -1
  139. package/dist/utils/component-ids.js.map +0 -1
  140. package/dist/utils/index.d.ts +0 -2
  141. package/dist/utils/index.d.ts.map +0 -1
  142. package/dist/utils/index.js.map +0 -1
  143. package/dist/utils/time-range-params.d.ts.map +0 -1
  144. package/dist/utils/time-range-params.js.map +0 -1
@@ -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,
@@ -96,6 +93,10 @@ function PanelHeader({ id , title , description , editHandlers , isHovered , sx
96
93
  id: titleElementId,
97
94
  variant: "subtitle1",
98
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',
99
100
  whiteSpace: 'nowrap',
100
101
  overflow: 'hidden',
101
102
  textOverflow: 'ellipsis'
@@ -105,7 +106,10 @@ function PanelHeader({ id , title , description , editHandlers , isHovered , sx
105
106
  action: action,
106
107
  sx: (0, _components.combineSx)((theme)=>({
107
108
  padding: theme.spacing(1),
108
- borderBottom: `solid 1px ${theme.palette.divider}`
109
+ borderBottom: `solid 1px ${theme.palette.divider}`,
110
+ '.MuiCardHeader-content': {
111
+ overflow: 'hidden'
112
+ }
109
113
  }), sx),
110
114
  ...rest
111
115
  });
@@ -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
@@ -25,8 +25,6 @@ _export(exports, {
25
25
  TimeRangeControls: ()=>TimeRangeControls
26
26
  });
27
27
  const _jsxRuntime = require("react/jsx-runtime");
28
- const _react = require("react");
29
- const _material = require("@mui/material");
30
28
  const _components = require("@perses-dev/components");
31
29
  const _core = require("@perses-dev/core");
32
30
  const _context = require("../../context");
@@ -88,58 +86,21 @@ const TIME_OPTIONS = [
88
86
  ];
89
87
  function TimeRangeControls() {
90
88
  const { timeRange , setTimeRange } = (0, _context.useDashboardTimeRange)();
91
- const [showCustomDateSelector, setShowCustomDateSelector] = (0, _react.useState)(false);
92
- const anchorEl = (0, _react.useRef)();
93
- const convertedTimeRange = (0, _react.useMemo)(()=>{
94
- return (0, _core.isRelativeTimeRange)(timeRange) ? (0, _core.toAbsoluteTimeRange)(timeRange) : timeRange;
95
- }, [
96
- timeRange
97
- ]);
98
- return /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_material.Stack, {
99
- direction: "row",
100
- spacing: 1,
101
- children: [
102
- /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.Popover, {
103
- anchorEl: anchorEl.current,
104
- anchorOrigin: {
105
- vertical: 'bottom',
106
- horizontal: 'center'
89
+ const defaultTimeRange = (0, _context.useDefaultTimeRange)();
90
+ // add time shortcut if one does not match duration from dashboard JSON
91
+ if (!TIME_OPTIONS.some((option)=>option.value.pastDuration === defaultTimeRange.pastDuration)) {
92
+ if ((0, _core.isDurationString)(defaultTimeRange.pastDuration)) {
93
+ TIME_OPTIONS.push({
94
+ value: {
95
+ pastDuration: defaultTimeRange.pastDuration
107
96
  },
108
- open: showCustomDateSelector,
109
- onClose: ()=>setShowCustomDateSelector(false),
110
- sx: (theme)=>({
111
- padding: theme.spacing(2)
112
- }),
113
- children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_components.AbsoluteTimePicker, {
114
- initialTimeRange: convertedTimeRange,
115
- onChange: (timeRange)=>{
116
- setTimeRange(timeRange);
117
- setShowCustomDateSelector(false);
118
- }
119
- })
120
- }),
121
- /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.FormControl, {
122
- fullWidth: true,
123
- children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.Box, {
124
- ref: anchorEl,
125
- children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_components.TimeRangeSelector, {
126
- timeOptions: TIME_OPTIONS,
127
- value: timeRange,
128
- onSelectChange: (event)=>{
129
- const duration = event.target.value;
130
- const relativeTimeInput = {
131
- pastDuration: duration,
132
- end: new Date()
133
- };
134
- setTimeRange(relativeTimeInput);
135
- setShowCustomDateSelector(false);
136
- },
137
- onCustomClick: ()=>{
138
- setShowCustomDateSelector(true);
139
- }
140
- })
141
- })
142
- })
143
- ]
97
+ display: `Last ${defaultTimeRange.pastDuration}`
98
+ });
99
+ }
100
+ }
101
+ return /*#__PURE__*/ (0, _jsxRuntime.jsx)(_components.DateTimeRangePicker, {
102
+ timeOptions: TIME_OPTIONS,
103
+ value: timeRange,
104
+ onChange: setTimeRange
144
105
  });
145
106
  }
@@ -45,29 +45,45 @@ describe('TimeRangeControls', ()=>{
45
45
  dashboardResource: _testDashboard.default
46
46
  };
47
47
  });
48
- const renderTimeRangeControls = ()=>{
48
+ const renderTimeRangeControls = (testURLParams)=>{
49
49
  (0, _test.renderWithContext)(/*#__PURE__*/ (0, _jsxRuntime.jsx)(_context.DashboardProvider, {
50
50
  initialState: initialState,
51
51
  children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_context.TimeRangeProvider, {
52
- timeRange: testDefaultTimeRange,
52
+ initialTimeRange: testDefaultTimeRange,
53
+ enabledURLParams: testURLParams,
53
54
  children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_timeRangeControls.TimeRangeControls, {})
54
55
  })
55
56
  }), undefined, history);
56
57
  };
57
- it('should render correct initial relative time shortcut', async ()=>{
58
- renderTimeRangeControls();
59
- expect(_react.screen.getByText('Last 5 minutes')).toBeInTheDocument();
60
- });
61
- // TODO: fix setTimeRange no-op, test query params
62
- it('should be able to select the first option', ()=>{
63
- renderTimeRangeControls();
58
+ it('should default to dashboard duration and update selected time option when clicked', async ()=>{
59
+ renderTimeRangeControls(false);
60
+ expect(_react.screen.getByText('Last 30 minutes')).toBeInTheDocument();
64
61
  const dateButton = _react.screen.getByRole('button');
65
62
  _userEvent.default.click(dateButton);
66
- const firstOption = _react.screen.getByRole('option', {
63
+ const firstSelected = _react.screen.getByRole('option', {
67
64
  name: 'Last 5 minutes'
68
65
  });
69
- _userEvent.default.click(firstOption);
66
+ _userEvent.default.click(firstSelected);
70
67
  expect(dateButton).toHaveTextContent(/5 minutes/i);
71
68
  });
69
+ it('should update URL params with correct time range values', ()=>{
70
+ renderTimeRangeControls(true);
71
+ const dateButton = _react.screen.getByRole('button');
72
+ _userEvent.default.click(dateButton);
73
+ const firstSelected = _react.screen.getByRole('option', {
74
+ name: 'Last 5 minutes'
75
+ });
76
+ _userEvent.default.click(firstSelected);
77
+ expect(history.location.search).toEqual('?start=5m');
78
+ // pick another option from TimeRangeSelector dropdown
79
+ const secondSelected = _react.screen.getByText('Last 12 hours');
80
+ _userEvent.default.click(secondSelected);
81
+ expect(history.location.search).toEqual('?start=12h');
82
+ // back button should return to first option selected
83
+ (0, _react.act)(()=>{
84
+ history.back();
85
+ });
86
+ expect(history.location.search).toEqual('?start=5m');
87
+ });
72
88
  // TODO: add additional tests for absolute time selection, other inputs, form validation, etc.
73
89
  });
@@ -57,16 +57,24 @@ function ListVariable({ name }) {
57
57
  const { data: variablePlugin } = (0, _pluginSystem.usePlugin)('Variable', definition.spec.plugin.kind);
58
58
  const { setVariableValue , setVariableLoading , setVariableOptions } = (0, _context.useTemplateVariableActions)();
59
59
  const datasourceStore = (0, _pluginSystem.useDatasourceStore)();
60
+ const allVariables = (0, _pluginSystem.useTemplateVariableValues)();
61
+ const { timeRange } = (0, _pluginSystem.useTimeRange)();
62
+ const variablePluginCtx = {
63
+ timeRange,
64
+ datasourceStore,
65
+ variables: allVariables
66
+ };
60
67
  const spec = definition.spec.plugin.spec;
61
68
  let dependsOnVariables;
62
69
  if (variablePlugin === null || variablePlugin === void 0 ? void 0 : variablePlugin.dependsOn) {
63
- dependsOnVariables = variablePlugin.dependsOn(spec);
70
+ const dependencies = variablePlugin.dependsOn(spec, variablePluginCtx);
71
+ dependsOnVariables = dependencies.variables;
64
72
  }
65
73
  const variables = (0, _pluginSystem.useTemplateVariableValues)(dependsOnVariables);
66
74
  const allowMultiple = (definition === null || definition === void 0 ? void 0 : definition.spec.allow_multiple) === true;
67
75
  const allowAllValue = (definition === null || definition === void 0 ? void 0 : definition.spec.allow_all_value) === true;
68
76
  var ref4;
69
- const label = (ref4 = (ref = definition === null || definition === void 0 ? void 0 : definition.spec.display) === null || ref === void 0 ? void 0 : ref.label) !== null && ref4 !== void 0 ? ref4 : name;
77
+ const title = (ref4 = (ref = definition === null || definition === void 0 ? void 0 : definition.spec.display) === null || ref === void 0 ? void 0 : ref.name) !== null && ref4 !== void 0 ? ref4 : name;
70
78
  let waitToLoad = false;
71
79
  if (dependsOnVariables) {
72
80
  waitToLoad = dependsOnVariables.some((v)=>{
@@ -75,7 +83,6 @@ function ListVariable({ name }) {
75
83
  });
76
84
  }
77
85
  const variablesValueKey = getVariableValuesKey(variables);
78
- const { timeRange } = (0, _pluginSystem.useTimeRange)();
79
86
  const variablesOptionsQuery = (0, _reactQuery.useQuery)([
80
87
  name,
81
88
  definition,
@@ -165,7 +172,7 @@ function ListVariable({ name }) {
165
172
  children: [
166
173
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.InputLabel, {
167
174
  id: name,
168
- children: label
175
+ children: title
169
176
  }),
170
177
  /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_material.Select, {
171
178
  sx: {
@@ -192,6 +199,11 @@ function ListVariable({ name }) {
192
199
  disabled: true,
193
200
  children: "Loading"
194
201
  }),
202
+ finalOptions.length === 0 && /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.MenuItem, {
203
+ value: "empty",
204
+ disabled: true,
205
+ children: "No options"
206
+ }),
195
207
  finalOptions.map((option)=>/*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.MenuItem, {
196
208
  value: option.value,
197
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)=>{
@@ -60,6 +75,7 @@ function VariableEditor(props) {
60
75
  }
61
76
  if (!v.spec.display) {
62
77
  v.spec.display = {
78
+ name: v.spec.name,
63
79
  hidden: false
64
80
  };
65
81
  }
@@ -117,7 +133,7 @@ function VariableEditor(props) {
117
133
  justifyContent: "end",
118
134
  children: [
119
135
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.Button, {
120
- disabled: props.variableDefinitions === variableDefinitions,
136
+ disabled: props.variableDefinitions === variableDefinitions || !validation.isValid,
121
137
  variant: "contained",
122
138
  onClick: ()=>{
123
139
  props.onChange(variableDefinitions);
@@ -141,6 +157,10 @@ function VariableEditor(props) {
141
157
  /*#__PURE__*/ (0, _jsxRuntime.jsxs)(_material.Stack, {
142
158
  spacing: 2,
143
159
  children: [
160
+ !validation.isValid && validation.errors.map((error)=>/*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.Alert, {
161
+ severity: "error",
162
+ children: error
163
+ }, error)),
144
164
  /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.TableContainer, {
145
165
  component: _material.Paper,
146
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;
@@ -98,10 +153,10 @@ function VariableEditForm({ initialVariableDefinition , onChange , onCancel })
98
153
  children: /*#__PURE__*/ (0, _jsxRuntime.jsx)(_material.TextField, {
99
154
  fullWidth: true,
100
155
  label: "Label",
101
- value: state.label,
156
+ value: state.title,
102
157
  onChange: (v)=>{
103
158
  setState((draft)=>{
104
- draft.label = v.target.value;
159
+ draft.title = v.target.value;
105
160
  });
106
161
  }
107
162
  })
@@ -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));
@@ -47,7 +47,7 @@ function getInitialState(initialVariableDefinition) {
47
47
  }
48
48
  return {
49
49
  name: initialVariableDefinition.spec.name,
50
- label: (ref = initialVariableDefinition.spec.display) === null || ref === void 0 ? void 0 : ref.label,
50
+ title: (ref = initialVariableDefinition.spec.display) === null || ref === void 0 ? void 0 : ref.name,
51
51
  kind: initialVariableDefinition.kind,
52
52
  description: '',
53
53
  listVariableFields,
@@ -55,11 +55,11 @@ function getInitialState(initialVariableDefinition) {
55
55
  };
56
56
  }
57
57
  function getVariableDefinitionFromState(state) {
58
- const { name , label , kind } = state;
58
+ const { name , title , kind } = state;
59
59
  const commonSpec = {
60
60
  name,
61
61
  display: {
62
- label
62
+ name: title
63
63
  }
64
64
  };
65
65
  if (kind === 'TextVariable') {