@perses-dev/dashboards 0.0.0-snapshot-time-zone-selector-946f408 → 0.0.0-snapshot-panel-actions-520389b

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 (44) hide show
  1. package/dist/cjs/components/DashboardToolbar/DashboardToolbar.js +98 -109
  2. package/dist/cjs/components/Panel/Panel.js +60 -1
  3. package/dist/cjs/components/Panel/PanelActions.js +86 -16
  4. package/dist/cjs/components/Panel/PanelHeader.js +47 -3
  5. package/dist/cjs/components/SaveChangesConfirmationDialog/SaveChangesConfirmationDialog.js +1 -13
  6. package/dist/cjs/components/SaveDashboardButton/SaveDashboardButton.js +3 -10
  7. package/dist/cjs/context/DashboardProvider/DashboardProvider.js +2 -4
  8. package/dist/cjs/context/useDashboard.js +1 -3
  9. package/dist/cjs/views/ViewDashboard/ViewDashboard.js +0 -2
  10. package/dist/components/DashboardToolbar/DashboardToolbar.d.ts.map +1 -1
  11. package/dist/components/DashboardToolbar/DashboardToolbar.js +100 -111
  12. package/dist/components/DashboardToolbar/DashboardToolbar.js.map +1 -1
  13. package/dist/components/Panel/Panel.d.ts +5 -0
  14. package/dist/components/Panel/Panel.d.ts.map +1 -1
  15. package/dist/components/Panel/Panel.js +60 -1
  16. package/dist/components/Panel/Panel.js.map +1 -1
  17. package/dist/components/Panel/PanelActions.d.ts +5 -3
  18. package/dist/components/Panel/PanelActions.d.ts.map +1 -1
  19. package/dist/components/Panel/PanelActions.js +87 -17
  20. package/dist/components/Panel/PanelActions.js.map +1 -1
  21. package/dist/components/Panel/PanelHeader.d.ts +5 -1
  22. package/dist/components/Panel/PanelHeader.d.ts.map +1 -1
  23. package/dist/components/Panel/PanelHeader.js +47 -3
  24. package/dist/components/Panel/PanelHeader.js.map +1 -1
  25. package/dist/components/SaveChangesConfirmationDialog/SaveChangesConfirmationDialog.d.ts.map +1 -1
  26. package/dist/components/SaveChangesConfirmationDialog/SaveChangesConfirmationDialog.js +2 -14
  27. package/dist/components/SaveChangesConfirmationDialog/SaveChangesConfirmationDialog.js.map +1 -1
  28. package/dist/components/SaveDashboardButton/SaveDashboardButton.d.ts.map +1 -1
  29. package/dist/components/SaveDashboardButton/SaveDashboardButton.js +4 -11
  30. package/dist/components/SaveDashboardButton/SaveDashboardButton.js.map +1 -1
  31. package/dist/context/DashboardProvider/DashboardProvider.d.ts +0 -1
  32. package/dist/context/DashboardProvider/DashboardProvider.d.ts.map +1 -1
  33. package/dist/context/DashboardProvider/DashboardProvider.js +3 -5
  34. package/dist/context/DashboardProvider/DashboardProvider.js.map +1 -1
  35. package/dist/context/DashboardProvider/save-changes-dialog-slice.d.ts +1 -2
  36. package/dist/context/DashboardProvider/save-changes-dialog-slice.d.ts.map +1 -1
  37. package/dist/context/DashboardProvider/save-changes-dialog-slice.js.map +1 -1
  38. package/dist/context/useDashboard.d.ts.map +1 -1
  39. package/dist/context/useDashboard.js +1 -3
  40. package/dist/context/useDashboard.js.map +1 -1
  41. package/dist/views/ViewDashboard/ViewDashboard.d.ts.map +1 -1
  42. package/dist/views/ViewDashboard/ViewDashboard.js +1 -3
  43. package/dist/views/ViewDashboard/ViewDashboard.js.map +1 -1
  44. package/package.json +5 -5
@@ -24,7 +24,6 @@ const _jsxruntime = require("react/jsx-runtime");
24
24
  const _material = require("@mui/material");
25
25
  const _components = require("@perses-dev/components");
26
26
  const _pluginsystem = require("@perses-dev/plugin-system");
27
- const _react = require("react");
28
27
  const _context = require("../../context");
29
28
  const _AddPanelButton = require("../AddPanelButton");
30
29
  const _AddGroupButton = require("../AddGroupButton");
@@ -40,126 +39,116 @@ const DashboardToolbar = (props)=>{
40
39
  const { isEditMode } = (0, _context.useEditMode)();
41
40
  const isBiggerThanSm = (0, _material.useMediaQuery)((0, _material.useTheme)().breakpoints.up('sm'));
42
41
  const isBiggerThanMd = (0, _material.useMediaQuery)((0, _material.useTheme)().breakpoints.up('md'));
43
- const { timeZone, setTimeZone } = (0, _pluginsystem.useTimeZone)();
44
- const timeZoneOptions = (0, _components.getTimeZoneOptions)();
45
42
  const dashboardTitle = dashboardTitleComponent ? dashboardTitleComponent : /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Typography, {
46
43
  variant: "h2",
47
44
  children: dashboardName
48
45
  });
49
46
  const testId = 'dashboard-toolbar';
50
- const handleTimeZoneChange = (0, _react.useCallback)((timeZoneOption)=>{
51
- setTimeZone(timeZoneOption.value);
52
- }, [
53
- setTimeZone
54
- ]);
55
- return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
56
- "data-testid": testId,
57
- children: [
58
- /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Box, {
59
- px: 2,
60
- py: 1.5,
61
- display: "flex",
62
- sx: {
63
- backgroundColor: (theme)=>theme.palette.primary.main + (isEditMode ? '30' : '0')
64
- },
65
- children: [
66
- dashboardTitle,
67
- isEditMode ? /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
68
- direction: "row",
69
- gap: 1,
70
- ml: "auto",
71
- children: [
72
- isReadonly && /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Alert, {
73
- severity: "warning",
74
- sx: {
75
- backgroundColor: 'transparent',
76
- padding: 0
77
- },
78
- children: "Dashboard managed via code only. Download JSON and commit changes to save."
79
- }),
80
- /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
81
- direction: "row",
82
- spacing: 0.5,
83
- ml: 1,
84
- whiteSpace: "nowrap",
85
- children: [
86
- isVariableEnabled && /*#__PURE__*/ (0, _jsxruntime.jsx)(_Variables.EditVariablesButton, {}),
87
- isDatasourceEnabled && /*#__PURE__*/ (0, _jsxruntime.jsx)(_Datasources.EditDatasourcesButton, {}),
88
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_AddPanelButton.AddPanelButton, {}),
89
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_AddGroupButton.AddGroupButton, {})
90
- ]
91
- }),
92
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_SaveDashboardButton.SaveDashboardButton, {
93
- onSave: onSave,
94
- isDisabled: isReadonly
95
- }),
96
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Button, {
97
- variant: "outlined",
98
- onClick: onCancelButtonClick,
99
- children: "Cancel"
100
- })
101
- ]
102
- }) : /*#__PURE__*/ (0, _jsxruntime.jsx)(_jsxruntime.Fragment, {
103
- children: isBiggerThanSm && /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Stack, {
47
+ return /*#__PURE__*/ (0, _jsxruntime.jsx)(_jsxruntime.Fragment, {
48
+ children: /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
49
+ "data-testid": testId,
50
+ children: [
51
+ /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Box, {
52
+ px: 2,
53
+ py: 1.5,
54
+ display: "flex",
55
+ sx: {
56
+ backgroundColor: (theme)=>theme.palette.primary.main + (isEditMode ? '30' : '0')
57
+ },
58
+ children: [
59
+ dashboardTitle,
60
+ isEditMode ? /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
104
61
  direction: "row",
105
62
  gap: 1,
106
63
  ml: "auto",
107
- children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_EditButton.EditButton, {
108
- onClick: onEditButtonClick
109
- })
110
- })
111
- })
112
- ]
113
- }),
114
- /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Box, {
115
- sx: {
116
- display: 'flex',
117
- width: '100%',
118
- alignItems: 'start',
119
- padding: (theme)=>theme.spacing(1, 2, 0, 2),
120
- flexDirection: isBiggerThanMd ? 'row' : 'column',
121
- flexWrap: 'nowrap',
122
- gap: 1
123
- },
124
- children: [
125
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Box, {
126
- width: "100%",
127
- children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.ErrorBoundary, {
128
- FallbackComponent: _components.ErrorAlert,
129
- children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_DashboardStickyToolbar.DashboardStickyToolbar, {
130
- initialVariableIsSticky: initialVariableIsSticky,
131
- sx: {
132
- backgroundColor: ({ palette })=>palette.background.default
133
- }
134
- })
135
- })
136
- }),
137
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Stack, {
138
- direction: "row",
139
- ml: "auto",
140
- flexWrap: "wrap",
141
- justifyContent: "end",
142
- children: /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
143
- direction: "row",
144
- spacing: 1,
145
- mt: 1,
146
- ml: 1,
147
64
  children: [
148
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_pluginsystem.TimeRangeControls, {}),
149
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.TimeZoneSelector, {
150
- timeZoneOptions: timeZoneOptions,
151
- value: timeZone,
152
- onChange: handleTimeZoneChange
65
+ isReadonly && /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Alert, {
66
+ severity: "warning",
67
+ sx: {
68
+ backgroundColor: 'transparent',
69
+ padding: 0
70
+ },
71
+ children: "Dashboard managed via code only. Download JSON and commit changes to save."
153
72
  }),
154
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_DownloadButton.DownloadButton, {}),
155
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_EditJsonButton.EditJsonButton, {
156
- isReadonly: !isEditMode
73
+ /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
74
+ direction: "row",
75
+ spacing: 0.5,
76
+ ml: 1,
77
+ whiteSpace: "nowrap",
78
+ children: [
79
+ isVariableEnabled && /*#__PURE__*/ (0, _jsxruntime.jsx)(_Variables.EditVariablesButton, {}),
80
+ isDatasourceEnabled && /*#__PURE__*/ (0, _jsxruntime.jsx)(_Datasources.EditDatasourcesButton, {}),
81
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_AddPanelButton.AddPanelButton, {}),
82
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_AddGroupButton.AddGroupButton, {})
83
+ ]
84
+ }),
85
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_SaveDashboardButton.SaveDashboardButton, {
86
+ onSave: onSave,
87
+ isDisabled: isReadonly
88
+ }),
89
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Button, {
90
+ variant: "outlined",
91
+ onClick: onCancelButtonClick,
92
+ children: "Cancel"
157
93
  })
158
94
  ]
95
+ }) : /*#__PURE__*/ (0, _jsxruntime.jsx)(_jsxruntime.Fragment, {
96
+ children: isBiggerThanSm && /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Stack, {
97
+ direction: "row",
98
+ gap: 1,
99
+ ml: "auto",
100
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_EditButton.EditButton, {
101
+ onClick: onEditButtonClick
102
+ })
103
+ })
104
+ })
105
+ ]
106
+ }),
107
+ /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Box, {
108
+ sx: {
109
+ display: 'flex',
110
+ width: '100%',
111
+ alignItems: 'start',
112
+ padding: (theme)=>theme.spacing(1, 2, 0, 2),
113
+ flexDirection: isBiggerThanMd ? 'row' : 'column',
114
+ flexWrap: 'nowrap',
115
+ gap: 1
116
+ },
117
+ children: [
118
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Box, {
119
+ width: "100%",
120
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.ErrorBoundary, {
121
+ FallbackComponent: _components.ErrorAlert,
122
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_DashboardStickyToolbar.DashboardStickyToolbar, {
123
+ initialVariableIsSticky: initialVariableIsSticky,
124
+ sx: {
125
+ backgroundColor: ({ palette })=>palette.background.default
126
+ }
127
+ })
128
+ })
129
+ }),
130
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Stack, {
131
+ direction: "row",
132
+ ml: "auto",
133
+ flexWrap: "wrap",
134
+ justifyContent: "end",
135
+ children: /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.Stack, {
136
+ direction: "row",
137
+ spacing: 1,
138
+ mt: 1,
139
+ ml: 1,
140
+ children: [
141
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_pluginsystem.TimeRangeControls, {}),
142
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_DownloadButton.DownloadButton, {}),
143
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_EditJsonButton.EditJsonButton, {
144
+ isReadonly: !isEditMode
145
+ })
146
+ ]
147
+ })
159
148
  })
160
- })
161
- ]
162
- })
163
- ]
149
+ ]
150
+ })
151
+ ]
152
+ })
164
153
  });
165
154
  };
@@ -33,8 +33,41 @@ function _interop_require_default(obj) {
33
33
  default: obj
34
34
  };
35
35
  }
36
+ // Function to extract project name from URL (kept as fallback)
37
+ const extractProjectNameFromUrl = ()=>{
38
+ try {
39
+ if (process.env.NODE_ENV === 'test') {
40
+ return 'test-project';
41
+ }
42
+ if (typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
43
+ const urlPath = window.location.pathname;
44
+ if (urlPath === '/' || urlPath === '') {
45
+ return 'dev-project';
46
+ }
47
+ }
48
+ const urlPath = window.location.pathname;
49
+ // Split the path and look for the project name after "/projects/"
50
+ const pathSegments = urlPath.split('/').filter((segment)=>segment.length > 0);
51
+ const projectsIndex = pathSegments.findIndex((segment)=>segment === 'projects');
52
+ if (projectsIndex !== -1 && projectsIndex + 1 < pathSegments.length) {
53
+ const projectName = pathSegments[projectsIndex + 1];
54
+ if (projectName && projectName.trim().length > 0) {
55
+ return projectName;
56
+ }
57
+ }
58
+ // Fallback: try to extract from URL parameters
59
+ const urlParams = new URLSearchParams(window.location.search);
60
+ const projectParam = urlParams.get('project');
61
+ if (projectParam && projectParam.trim().length > 0) {
62
+ return projectParam;
63
+ }
64
+ return 'unknown-project';
65
+ } catch {
66
+ return 'unknown-project';
67
+ }
68
+ };
36
69
  const Panel = /*#__PURE__*/ (0, _react.memo)(function Panel(props) {
37
- const { definition, readHandlers, editHandlers, onMouseEnter, onMouseLeave, sx, panelOptions, panelGroupItemId, ...others } = props;
70
+ const { definition, readHandlers, editHandlers, onMouseEnter, onMouseLeave, sx, panelOptions, panelGroupItemId, projectName: providedProjectName, ...others } = props;
38
71
  // Make sure we have an ID we can use for aria attributes
39
72
  const generatedPanelId = (0, _components.useId)('Panel');
40
73
  const headerId = `${generatedPanelId}-header`;
@@ -54,6 +87,29 @@ const Panel = /*#__PURE__*/ (0, _react.memo)(function Panel(props) {
54
87
  ]);
55
88
  const chartsTheme = (0, _components.useChartsTheme)();
56
89
  const { queryResults } = (0, _pluginsystem.useDataQueriesContext)();
90
+ // Use provided project name or extract from URL as fallback
91
+ const projectName = (0, _react.useMemo)(()=>{
92
+ return providedProjectName || extractProjectNameFromUrl();
93
+ }, [
94
+ providedProjectName
95
+ ]);
96
+ const panelPropsForActions = (0, _react.useMemo)(()=>{
97
+ return {
98
+ spec: definition.spec.plugin.spec,
99
+ queryResults: queryResults.map((query)=>({
100
+ definition: query.definition,
101
+ data: query.data
102
+ })),
103
+ contentDimensions,
104
+ definition,
105
+ projectName
106
+ };
107
+ }, [
108
+ definition,
109
+ contentDimensions,
110
+ queryResults,
111
+ projectName
112
+ ]);
57
113
  const handleMouseEnter = (e)=>{
58
114
  onMouseEnter?.(e);
59
115
  };
@@ -88,9 +144,12 @@ const Panel = /*#__PURE__*/ (0, _react.memo)(function Panel(props) {
88
144
  title: definition.spec.display.name,
89
145
  description: definition.spec.display.description,
90
146
  queryResults: queryResults,
147
+ panelPluginKind: definition.spec.plugin.kind,
91
148
  readHandlers: readHandlers,
92
149
  editHandlers: editHandlers,
93
150
  links: definition.spec.links,
151
+ projectName: projectName,
152
+ panelProps: panelPropsForActions,
94
153
  sx: {
95
154
  paddingX: `${chartsTheme.container.padding.default}px`
96
155
  }
@@ -24,6 +24,7 @@ const _jsxruntime = require("react/jsx-runtime");
24
24
  const _material = require("@mui/material");
25
25
  const _react = require("react");
26
26
  const _components = require("@perses-dev/components");
27
+ const _pluginsystem = require("@perses-dev/plugin-system");
27
28
  const _ArrowCollapse = /*#__PURE__*/ _interop_require_default(require("mdi-material-ui/ArrowCollapse"));
28
29
  const _ArrowExpand = /*#__PURE__*/ _interop_require_default(require("mdi-material-ui/ArrowExpand"));
29
30
  const _PencilOutline = /*#__PURE__*/ _interop_require_default(require("mdi-material-ui/PencilOutline"));
@@ -47,7 +48,72 @@ const ConditionalBox = (0, _material.styled)(_material.Box)({
47
48
  flexGrow: 1,
48
49
  justifyContent: 'flex-end'
49
50
  });
50
- const PanelActions = ({ editHandlers, readHandlers, extra, title, description, descriptionTooltipId, links, queryResults })=>{
51
+ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, descriptionTooltipId, links, queryResults, panelPluginKind, projectName: _propsProjectName, panelProps })=>{
52
+ const { isFetching, errors } = (0, _pluginsystem.useDataQueriesContext)();
53
+ const { getPlugin } = (0, _pluginsystem.usePluginRegistry)();
54
+ const [pluginActions, setPluginActions] = (0, _react.useState)([]);
55
+ (0, _react.useEffect)(()=>{
56
+ let cancelled = false;
57
+ const loadPluginActions = async ()=>{
58
+ if (!panelPluginKind || !panelProps) {
59
+ if (!cancelled) {
60
+ setPluginActions([]);
61
+ }
62
+ return;
63
+ }
64
+ try {
65
+ // Add defensive check for getPlugin availability
66
+ if (!getPlugin || typeof getPlugin !== 'function') {
67
+ if (!cancelled) {
68
+ setPluginActions([]);
69
+ }
70
+ return;
71
+ }
72
+ const plugin = await getPlugin('Panel', panelPluginKind);
73
+ if (cancelled) return;
74
+ // More defensive checking for plugin and actions
75
+ if (!plugin || typeof plugin !== 'object' || !plugin.actions || !Array.isArray(plugin.actions) || plugin.actions.length === 0) {
76
+ if (!cancelled) {
77
+ setPluginActions([]);
78
+ }
79
+ return;
80
+ }
81
+ // Render plugin actions in header location
82
+ const headerActions = plugin.actions.filter((action)=>!action.location || action.location === 'header').map((action, index)=>{
83
+ const ActionComponent = action.component;
84
+ try {
85
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ return /*#__PURE__*/ (0, _jsxruntime.jsx)(ActionComponent, {
87
+ ...panelProps
88
+ }, `plugin-action-${index}`);
89
+ } catch (error) {
90
+ console.warn(`Failed to render plugin action ${index}:`, error);
91
+ return null;
92
+ }
93
+ }).filter((item)=>Boolean(item));
94
+ if (!cancelled) {
95
+ setPluginActions(headerActions);
96
+ }
97
+ } catch (error) {
98
+ if (!cancelled) {
99
+ console.warn('Failed to load plugin actions:', error);
100
+ setPluginActions([]);
101
+ }
102
+ }
103
+ };
104
+ // Use setTimeout to defer the async operation to the next tick
105
+ const timeoutId = setTimeout(()=>{
106
+ loadPluginActions();
107
+ }, 0);
108
+ return ()=>{
109
+ cancelled = true;
110
+ clearTimeout(timeoutId);
111
+ };
112
+ }, [
113
+ panelPluginKind,
114
+ panelProps,
115
+ getPlugin
116
+ ]);
51
117
  const descriptionAction = (0, _react.useMemo)(()=>{
52
118
  if (description && description.trim().length > 0) {
53
119
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.InfoTooltip, {
@@ -78,19 +144,23 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
78
144
  });
79
145
  const extraActions = editHandlers === undefined && extra;
80
146
  const queryStateIndicator = (0, _react.useMemo)(()=>{
81
- const hasData = queryResults.some((q)=>q.data);
82
- const isFetching = queryResults.some((q)=>q.isFetching);
83
- const queryErrors = queryResults.filter((q)=>q.error);
147
+ const hasData = queryResults && queryResults.series && queryResults.series.length > 0;
84
148
  if (isFetching && hasData) {
85
- // If the panel has no data, the panel content will show the loading overlay.
86
- // Therefore, show the circular loading indicator only in case the panel doesn't display the loading overlay already.
87
149
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.CircularProgress, {
88
150
  "aria-label": "loading",
89
151
  size: "1.125rem"
90
152
  });
91
- } else if (queryErrors.length > 0) {
92
- const errorTexts = queryErrors.map((q)=>q.error).map((e)=>e?.message ?? e?.toString() ?? 'Unknown error') // eslint-disable-line @typescript-eslint/no-explicit-any
93
- .join('\n');
153
+ }
154
+ const validErrors = (errors || []).filter((error)=>error !== null);
155
+ if (validErrors.length > 0) {
156
+ const errorTexts = validErrors.map((e)=>{
157
+ if (typeof e === 'string') return e;
158
+ if (e && typeof e === 'object') {
159
+ const errorObj = e;
160
+ return errorObj.message ?? errorObj.toString?.() ?? 'Unknown error';
161
+ }
162
+ return 'Unknown error';
163
+ }).join('\n');
94
164
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.InfoTooltip, {
95
165
  description: errorTexts,
96
166
  children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_HeaderIconButton.HeaderIconButton, {
@@ -103,7 +173,9 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
103
173
  });
104
174
  }
105
175
  }, [
106
- queryResults
176
+ queryResults,
177
+ isFetching,
178
+ errors
107
179
  ]);
108
180
  const readActions = (0, _react.useMemo)(()=>{
109
181
  if (readHandlers !== undefined) {
@@ -128,7 +200,6 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
128
200
  ]);
129
201
  const editActions = (0, _react.useMemo)(()=>{
130
202
  if (editHandlers !== undefined) {
131
- // If there are edit handlers, always just show the edit buttons
132
203
  return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_jsxruntime.Fragment, {
133
204
  children: [
134
205
  /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.InfoTooltip, {
@@ -151,8 +222,6 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
151
222
  children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_ContentCopy.default, {
152
223
  fontSize: "inherit",
153
224
  sx: {
154
- // Shrink this icon a little bit to look more consistent
155
- // with the other icons in the header.
156
225
  transform: 'scale(0.925)'
157
226
  }
158
227
  })
@@ -242,6 +311,7 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
242
311
  editActions
243
312
  ]
244
313
  }),
314
+ pluginActions,
245
315
  moveAction
246
316
  ]
247
317
  })
@@ -269,6 +339,7 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
269
339
  extraActions,
270
340
  " ",
271
341
  readActions,
342
+ pluginActions,
272
343
  /*#__PURE__*/ (0, _jsxruntime.jsx)(OverflowMenu, {
273
344
  title: title,
274
345
  children: editActions
@@ -280,7 +351,6 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
280
351
  }),
281
352
  /*#__PURE__*/ (0, _jsxruntime.jsxs)(ConditionalBox, {
282
353
  sx: (theme)=>({
283
- // flip the logic here; if the browser (or jsdom) does not support container queries, always show all icons
284
354
  display: 'flex',
285
355
  [theme.containerQueries(_constants.HEADER_ACTIONS_CONTAINER_NAME).down(_constants.HEADER_MEDIUM_WIDTH)]: {
286
356
  display: 'none'
@@ -302,7 +372,7 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
302
372
  extraActions,
303
373
  " ",
304
374
  readActions,
305
- " ",
375
+ pluginActions,
306
376
  editActions,
307
377
  " ",
308
378
  moveAction
@@ -315,7 +385,7 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
315
385
  };
316
386
  const OverflowMenu = ({ children, title })=>{
317
387
  const [anchorPosition, setAnchorPosition] = (0, _react.useState)();
318
- // do not show overflow menu if there is no content (for example, edit actions are hidden)
388
+ // do not show overflow menu if there is no content
319
389
  const hasContent = /*#__PURE__*/ (0, _react.isValidElement)(children) || Array.isArray(children) && children.some(_react.isValidElement);
320
390
  if (!hasContent) {
321
391
  return undefined;
@@ -24,13 +24,53 @@ const _jsxruntime = require("react/jsx-runtime");
24
24
  const _material = require("@mui/material");
25
25
  const _components = require("@perses-dev/components");
26
26
  const _pluginsystem = require("@perses-dev/plugin-system");
27
+ const _react = require("react");
27
28
  const _constants = require("../../constants");
28
29
  const _PanelActions = require("./PanelActions");
29
- function PanelHeader({ id, title: rawTitle, description: rawDescription, links, queryResults, readHandlers, editHandlers, sx, extra, ...rest }) {
30
+ function PanelHeader({ id, title: rawTitle, description: rawDescription, links, queryResults, readHandlers, editHandlers, sx, extra, panelPluginKind, projectName, panelProps, ...rest }) {
30
31
  const titleElementId = `${id}-title`;
31
32
  const descriptionTooltipId = `${id}-description`;
32
33
  const title = (0, _pluginsystem.useReplaceVariablesInString)(rawTitle);
33
34
  const description = (0, _pluginsystem.useReplaceVariablesInString)(rawDescription);
35
+ const timeSeriesDataForExport = (0, _react.useMemo)(()=>{
36
+ // Collect all series from all queries
37
+ const allSeries = [];
38
+ let timeRange = undefined;
39
+ let stepMs = undefined;
40
+ let metadata = undefined;
41
+ queryResults.forEach((query)=>{
42
+ if (query.data && 'series' in query.data) {
43
+ const timeSeriesData = query.data;
44
+ // Collect series from this query
45
+ if (timeSeriesData.series && timeSeriesData.series.length > 0) {
46
+ allSeries.push(...timeSeriesData.series);
47
+ // Use the first query's metadata/timeRange/stepMs as the base
48
+ if (!timeRange && timeSeriesData.timeRange) {
49
+ timeRange = timeSeriesData.timeRange;
50
+ }
51
+ if (!stepMs && timeSeriesData.stepMs) {
52
+ stepMs = timeSeriesData.stepMs;
53
+ }
54
+ if (!metadata && timeSeriesData.metadata) {
55
+ metadata = timeSeriesData.metadata;
56
+ }
57
+ }
58
+ }
59
+ });
60
+ // If we found series, create a combined TimeSeriesData object
61
+ if (allSeries.length > 0) {
62
+ const combinedData = {
63
+ series: allSeries,
64
+ timeRange,
65
+ stepMs,
66
+ metadata
67
+ };
68
+ return combinedData;
69
+ }
70
+ return undefined;
71
+ }, [
72
+ queryResults
73
+ ]);
34
74
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.CardHeader, {
35
75
  id: id,
36
76
  component: "header",
@@ -59,10 +99,14 @@ function PanelHeader({ id, title: rawTitle, description: rawDescription, links,
59
99
  description: description,
60
100
  descriptionTooltipId: descriptionTooltipId,
61
101
  links: links,
62
- queryResults: queryResults,
102
+ queryResults: timeSeriesDataForExport,
103
+ panelPluginKind: panelPluginKind,
63
104
  readHandlers: readHandlers,
64
105
  editHandlers: editHandlers,
65
- extra: extra
106
+ extra: extra,
107
+ projectName: projectName,
108
+ // ========== ADDED: Pass panel props for actions ==========
109
+ panelProps: panelProps
66
110
  })
67
111
  ]
68
112
  }),
@@ -31,11 +31,8 @@ const SaveChangesConfirmationDialog = ()=>{
31
31
  const { saveChangesConfirmationDialog: dialog } = (0, _context.useSaveChangesConfirmationDialog)();
32
32
  const isSavedDurationModified = dialog?.isSavedDurationModified ?? true;
33
33
  const isSavedVariableModified = dialog?.isSavedVariableModified ?? true;
34
- const isSavedTimeZoneModified = dialog?.isSavedTimeZoneModified ?? true;
35
34
  const [saveDefaultTimeRange, setSaveDefaultTimeRange] = (0, _react.useState)(isSavedDurationModified);
36
35
  const [saveDefaultVariables, setSaveDefaultVariables] = (0, _react.useState)(isSavedVariableModified);
37
- const [saveDefaultTimeZone, setSaveDefaultTimeZone] = (0, _react.useState)(isSavedTimeZoneModified);
38
- const { timeZone } = (0, _pluginsystem.useTimeZone)();
39
36
  const { getSavedVariablesStatus } = (0, _context.useVariableDefinitionActions)();
40
37
  const { modifiedVariableNames } = getSavedVariablesStatus();
41
38
  const isOpen = dialog !== undefined;
@@ -43,7 +40,6 @@ const SaveChangesConfirmationDialog = ()=>{
43
40
  const currentTimeRangeText = (0, _core.isRelativeTimeRange)(timeRange) ? `(Last ${timeRange.pastDuration})` : '(Absolute time ranges can not be saved)';
44
41
  const saveTimeRangeText = `Save current time range as new default ${currentTimeRangeText}`;
45
42
  const saveVariablesText = `Save current variable values as new default (${modifiedVariableNames.length > 0 ? modifiedVariableNames.join(', ') : 'No modified variables'})`;
46
- const saveTimeZoneText = `Save time zone as "${timeZone}" time`;
47
43
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.Dialog, {
48
44
  open: isOpen,
49
45
  children: dialog !== undefined && /*#__PURE__*/ (0, _jsxruntime.jsxs)(_jsxruntime.Fragment, {
@@ -75,14 +71,6 @@ const SaveChangesConfirmationDialog = ()=>{
75
71
  onChange: (e)=>setSaveDefaultVariables(e.target.checked)
76
72
  }),
77
73
  label: saveVariablesText
78
- }),
79
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.FormControlLabel, {
80
- control: /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Checkbox, {
81
- disabled: !isSavedTimeZoneModified,
82
- checked: saveDefaultTimeZone && isSavedTimeZoneModified,
83
- onChange: (e)=>setSaveDefaultTimeZone(e.target.checked)
84
- }),
85
- label: saveTimeZoneText
86
74
  })
87
75
  ]
88
76
  })
@@ -92,7 +80,7 @@ const SaveChangesConfirmationDialog = ()=>{
92
80
  children: [
93
81
  /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.Dialog.PrimaryButton, {
94
82
  onClick: ()=>{
95
- return dialog.onSaveChanges(saveDefaultTimeRange, saveDefaultVariables, saveDefaultTimeZone);
83
+ return dialog.onSaveChanges(saveDefaultTimeRange, saveDefaultVariables);
96
84
  },
97
85
  children: "Save Changes"
98
86
  }),
@@ -34,17 +34,14 @@ const SaveDashboardButton = ({ onSave, isDisabled, variant = 'contained' })=>{
34
34
  const { timeRange } = (0, _pluginsystem.useTimeRange)();
35
35
  const { setEditMode } = (0, _context.useEditMode)();
36
36
  const { openSaveChangesConfirmationDialog, closeSaveChangesConfirmationDialog } = (0, _context.useSaveChangesConfirmationDialog)();
37
- const { timeZone: currentTimeZone } = (0, _pluginsystem.useTimeZone)();
38
37
  const onSaveButtonClick = ()=>{
39
38
  const isSavedDurationModified = (0, _core.isRelativeTimeRange)(timeRange) && dashboard.spec.duration !== timeRange.pastDuration;
40
- const isSavedTimeZoneModified = dashboard.spec.timeZone !== currentTimeZone;
41
39
  // Save dashboard
42
40
  // - if active timeRange from plugin-system is relative and different from currently saved
43
41
  // - or if the saved variables are different from currently saved
44
- // - or if the saved timeZone are different from currently saved
45
- if (isSavedDurationModified || isSavedVariableModified || isSavedTimeZoneModified) {
42
+ if (isSavedDurationModified || isSavedVariableModified) {
46
43
  openSaveChangesConfirmationDialog({
47
- onSaveChanges: (saveDefaultTimeRange, saveDefaultVariables, isSavedTimeZone)=>{
44
+ onSaveChanges: (saveDefaultTimeRange, saveDefaultVariables)=>{
48
45
  if ((0, _core.isRelativeTimeRange)(timeRange) && saveDefaultTimeRange === true) {
49
46
  dashboard.spec.duration = timeRange.pastDuration;
50
47
  }
@@ -52,9 +49,6 @@ const SaveDashboardButton = ({ onSave, isDisabled, variant = 'contained' })=>{
52
49
  const variables = setVariableDefaultValues();
53
50
  dashboard.spec.variables = variables;
54
51
  }
55
- if (isSavedTimeZone) {
56
- dashboard.spec.timeZone = currentTimeZone;
57
- }
58
52
  setDashboard(dashboard);
59
53
  saveDashboard();
60
54
  },
@@ -62,8 +56,7 @@ const SaveDashboardButton = ({ onSave, isDisabled, variant = 'contained' })=>{
62
56
  closeSaveChangesConfirmationDialog();
63
57
  },
64
58
  isSavedDurationModified,
65
- isSavedVariableModified,
66
- isSavedTimeZoneModified
59
+ isSavedVariableModified
67
60
  });
68
61
  } else {
69
62
  saveDashboard();