@perses-dev/dashboards 0.51.1 → 0.52.0-beta.1

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.
@@ -25,8 +25,8 @@ const _material = require("@mui/material");
25
25
  const _components = require("@perses-dev/components");
26
26
  const _DownloadOutline = /*#__PURE__*/ _interop_require_default(require("mdi-material-ui/DownloadOutline"));
27
27
  const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
28
- const _yaml = require("yaml");
29
28
  const _context = require("../../context");
29
+ const _serializeDashboard = require("./serializeDashboard");
30
30
  function _interop_require_default(obj) {
31
31
  return obj && obj.__esModule ? obj : {
32
32
  default: obj
@@ -83,43 +83,13 @@ function DownloadButton() {
83
83
  };
84
84
  const handleItemClick = (format, shape)=>()=>{
85
85
  setAnchorEl(null);
86
- let type, content = '';
87
- switch(format){
88
- case 'json':
89
- type = 'application/json';
90
- content = JSON.stringify(dashboard, null, 2);
91
- break;
92
- case 'yaml':
93
- {
94
- type = 'application/yaml';
95
- if (shape === 'cr') {
96
- const name = dashboard.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
97
- content = (0, _yaml.stringify)({
98
- apiVersion: 'perses.dev/v1alpha1',
99
- kind: 'PersesDashboard',
100
- metadata: {
101
- labels: {
102
- 'app.kubernetes.io/name': 'perses-dashboard',
103
- 'app.kubernetes.io/instance': name,
104
- 'app.kubernetes.io/part-of': 'perses-operator'
105
- },
106
- name,
107
- namespace: dashboard.metadata.project
108
- },
109
- spec: dashboard.spec
110
- });
111
- } else {
112
- content = (0, _yaml.stringify)(dashboard);
113
- }
114
- }
115
- break;
116
- }
86
+ const { contentType, content } = (0, _serializeDashboard.serializeDashboard)(dashboard, format, shape);
117
87
  if (!hiddenLinkRef || !hiddenLinkRef.current) return;
118
88
  // Create blob URL
119
89
  const hiddenLinkUrl = URL.createObjectURL(new Blob([
120
90
  content
121
91
  ], {
122
- type
92
+ type: contentType
123
93
  }));
124
94
  // Simulate click
125
95
  hiddenLinkRef.current.download = `${dashboard.metadata.name}${shape === 'cr' ? '-cr' : ''}.${format}`;
@@ -0,0 +1,64 @@
1
+ // Copyright 2023 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, "serializeDashboard", {
18
+ enumerable: true,
19
+ get: function() {
20
+ return serializeDashboard;
21
+ }
22
+ });
23
+ const _yaml = require("yaml");
24
+ function serializeYaml(dashboard, shape) {
25
+ let content;
26
+ if (shape === 'cr') {
27
+ const name = dashboard.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
28
+ content = (0, _yaml.stringify)({
29
+ apiVersion: 'perses.dev/v1alpha1',
30
+ kind: 'PersesDashboard',
31
+ metadata: {
32
+ labels: {
33
+ 'app.kubernetes.io/name': 'perses-dashboard',
34
+ 'app.kubernetes.io/instance': name,
35
+ 'app.kubernetes.io/part-of': 'perses-operator'
36
+ },
37
+ name,
38
+ namespace: dashboard.metadata.project
39
+ },
40
+ spec: dashboard.spec
41
+ }, {
42
+ schema: 'yaml-1.1'
43
+ });
44
+ } else {
45
+ content = (0, _yaml.stringify)(dashboard, {
46
+ schema: 'yaml-1.1'
47
+ });
48
+ }
49
+ return {
50
+ contentType: 'application/yaml',
51
+ content
52
+ };
53
+ }
54
+ function serializeDashboard(dashboard, format, shape) {
55
+ switch(format){
56
+ case 'json':
57
+ return {
58
+ contentType: 'application/json',
59
+ content: JSON.stringify(dashboard, null, 2)
60
+ };
61
+ case 'yaml':
62
+ return serializeYaml(dashboard, shape);
63
+ }
64
+ }
@@ -54,6 +54,88 @@ const Panel = /*#__PURE__*/ (0, _react.memo)(function Panel(props) {
54
54
  ]);
55
55
  const chartsTheme = (0, _components.useChartsTheme)();
56
56
  const { queryResults } = (0, _pluginsystem.useDataQueriesContext)();
57
+ const { getPlugin } = (0, _pluginsystem.usePluginRegistry)();
58
+ const panelPropsForActions = (0, _react.useMemo)(()=>{
59
+ return {
60
+ spec: definition.spec.plugin.spec,
61
+ queryResults: queryResults.map((query)=>({
62
+ definition: query.definition,
63
+ data: query.data
64
+ })),
65
+ contentDimensions,
66
+ definition
67
+ };
68
+ }, [
69
+ definition,
70
+ contentDimensions,
71
+ queryResults
72
+ ]);
73
+ // Load plugin actions from the plugin
74
+ const [pluginActions, setPluginActions] = (0, _react.useState)([]);
75
+ (0, _react.useEffect)(()=>{
76
+ let cancelled = false;
77
+ const loadPluginActions = async ()=>{
78
+ const panelPluginKind = definition.spec.plugin.kind;
79
+ const panelProps = panelPropsForActions;
80
+ if (!panelPluginKind || !panelProps) {
81
+ if (!cancelled) {
82
+ setPluginActions([]);
83
+ }
84
+ return;
85
+ }
86
+ try {
87
+ // Add defensive check for getPlugin availability
88
+ if (!getPlugin || typeof getPlugin !== 'function') {
89
+ if (!cancelled) {
90
+ setPluginActions([]);
91
+ }
92
+ return;
93
+ }
94
+ const plugin = await getPlugin('Panel', panelPluginKind);
95
+ if (cancelled) return;
96
+ // More defensive checking for plugin and actions
97
+ if (!plugin || typeof plugin !== 'object' || !plugin.actions || !Array.isArray(plugin.actions) || plugin.actions.length === 0) {
98
+ if (!cancelled) {
99
+ setPluginActions([]);
100
+ }
101
+ return;
102
+ }
103
+ // Render plugin actions in header location
104
+ const headerActions = plugin.actions.filter((action)=>!action.location || action.location === 'header').map((action, index)=>{
105
+ const ActionComponent = action.component;
106
+ try {
107
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
+ return /*#__PURE__*/ (0, _jsxruntime.jsx)(ActionComponent, {
109
+ ...panelProps
110
+ }, `plugin-action-${index}`);
111
+ } catch (error) {
112
+ console.warn(`Failed to render plugin action ${index}:`, error);
113
+ return null;
114
+ }
115
+ }).filter((item)=>Boolean(item));
116
+ if (!cancelled) {
117
+ setPluginActions(headerActions);
118
+ }
119
+ } catch (error) {
120
+ if (!cancelled) {
121
+ console.warn('Failed to load plugin actions:', error);
122
+ setPluginActions([]);
123
+ }
124
+ }
125
+ };
126
+ // Use setTimeout to defer the async operation to the next tick
127
+ const timeoutId = setTimeout(()=>{
128
+ loadPluginActions();
129
+ }, 0);
130
+ return ()=>{
131
+ cancelled = true;
132
+ clearTimeout(timeoutId);
133
+ };
134
+ }, [
135
+ definition.spec.plugin.kind,
136
+ panelPropsForActions,
137
+ getPlugin
138
+ ]);
57
139
  const handleMouseEnter = (e)=>{
58
140
  onMouseEnter?.(e);
59
141
  };
@@ -91,6 +173,7 @@ const Panel = /*#__PURE__*/ (0, _react.memo)(function Panel(props) {
91
173
  readHandlers: readHandlers,
92
174
  editHandlers: editHandlers,
93
175
  links: definition.spec.links,
176
+ pluginActions: pluginActions,
94
177
  sx: {
95
178
  paddingX: `${chartsTheme.container.padding.default}px`
96
179
  }
@@ -47,7 +47,7 @@ const ConditionalBox = (0, _material.styled)(_material.Box)({
47
47
  flexGrow: 1,
48
48
  justifyContent: 'flex-end'
49
49
  });
50
- const PanelActions = ({ editHandlers, readHandlers, extra, title, description, descriptionTooltipId, links, queryResults })=>{
50
+ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, descriptionTooltipId, links, queryResults, pluginActions = [] })=>{
51
51
  const descriptionAction = (0, _react.useMemo)(()=>{
52
52
  if (description && description.trim().length > 0) {
53
53
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.InfoTooltip, {
@@ -82,8 +82,6 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
82
82
  const isFetching = queryResults.some((q)=>q.isFetching);
83
83
  const queryErrors = queryResults.filter((q)=>q.error);
84
84
  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
85
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.CircularProgress, {
88
86
  "aria-label": "loading",
89
87
  size: "1.125rem"
@@ -242,6 +240,7 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
242
240
  editActions
243
241
  ]
244
242
  }),
243
+ pluginActions,
245
244
  moveAction
246
245
  ]
247
246
  })
@@ -269,6 +268,8 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
269
268
  extraActions,
270
269
  " ",
271
270
  readActions,
271
+ " ",
272
+ pluginActions,
272
273
  /*#__PURE__*/ (0, _jsxruntime.jsx)(OverflowMenu, {
273
274
  title: title,
274
275
  children: editActions
@@ -303,6 +304,8 @@ const PanelActions = ({ editHandlers, readHandlers, extra, title, description, d
303
304
  " ",
304
305
  readActions,
305
306
  " ",
307
+ pluginActions,
308
+ " ",
306
309
  editActions,
307
310
  " ",
308
311
  moveAction
@@ -26,7 +26,7 @@ const _components = require("@perses-dev/components");
26
26
  const _pluginsystem = require("@perses-dev/plugin-system");
27
27
  const _constants = require("../../constants");
28
28
  const _PanelActions = require("./PanelActions");
29
- function PanelHeader({ id, title: rawTitle, description: rawDescription, links, queryResults, readHandlers, editHandlers, sx, extra, ...rest }) {
29
+ function PanelHeader({ id, title: rawTitle, description: rawDescription, links, queryResults, readHandlers, editHandlers, sx, extra, pluginActions, ...rest }) {
30
30
  const titleElementId = `${id}-title`;
31
31
  const descriptionTooltipId = `${id}-description`;
32
32
  const title = (0, _pluginsystem.useReplaceVariablesInString)(rawTitle);
@@ -59,10 +59,11 @@ function PanelHeader({ id, title: rawTitle, description: rawDescription, links,
59
59
  description: description,
60
60
  descriptionTooltipId: descriptionTooltipId,
61
61
  links: links,
62
- queryResults: queryResults,
63
62
  readHandlers: readHandlers,
64
63
  editHandlers: editHandlers,
65
- extra: extra
64
+ extra: extra,
65
+ queryResults: queryResults,
66
+ pluginActions: pluginActions
66
67
  })
67
68
  ]
68
69
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"DownloadButton.d.ts","sourceRoot":"","sources":["../../../src/components/DownloadButton/DownloadButton.tsx"],"names":[],"mappings":"AAgBA,OAAc,EAAE,YAAY,EAAU,MAAM,OAAO,CAAC;AAKpD,wBAAgB,cAAc,IAAI,YAAY,CAgG7C"}
1
+ {"version":3,"file":"DownloadButton.d.ts","sourceRoot":"","sources":["../../../src/components/DownloadButton/DownloadButton.tsx"],"names":[],"mappings":"AAgBA,OAAc,EAAE,YAAY,EAAU,MAAM,OAAO,CAAC;AAKpD,wBAAgB,cAAc,IAAI,YAAY,CA+D7C"}
@@ -15,8 +15,8 @@ import { ClickAwayListener, Menu, MenuItem, MenuList } from '@mui/material';
15
15
  import { ToolbarIconButton } from '@perses-dev/components';
16
16
  import DownloadIcon from 'mdi-material-ui/DownloadOutline';
17
17
  import React, { useRef } from 'react';
18
- import { stringify } from 'yaml';
19
18
  import { useDashboard } from '../../context';
19
+ import { serializeDashboard } from './serializeDashboard';
20
20
  // Button that enables downloading the dashboard as a JSON file
21
21
  export function DownloadButton() {
22
22
  const { dashboard } = useDashboard();
@@ -28,43 +28,13 @@ export function DownloadButton() {
28
28
  };
29
29
  const handleItemClick = (format, shape)=>()=>{
30
30
  setAnchorEl(null);
31
- let type, content = '';
32
- switch(format){
33
- case 'json':
34
- type = 'application/json';
35
- content = JSON.stringify(dashboard, null, 2);
36
- break;
37
- case 'yaml':
38
- {
39
- type = 'application/yaml';
40
- if (shape === 'cr') {
41
- const name = dashboard.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
42
- content = stringify({
43
- apiVersion: 'perses.dev/v1alpha1',
44
- kind: 'PersesDashboard',
45
- metadata: {
46
- labels: {
47
- 'app.kubernetes.io/name': 'perses-dashboard',
48
- 'app.kubernetes.io/instance': name,
49
- 'app.kubernetes.io/part-of': 'perses-operator'
50
- },
51
- name,
52
- namespace: dashboard.metadata.project
53
- },
54
- spec: dashboard.spec
55
- });
56
- } else {
57
- content = stringify(dashboard);
58
- }
59
- }
60
- break;
61
- }
31
+ const { contentType, content } = serializeDashboard(dashboard, format, shape);
62
32
  if (!hiddenLinkRef || !hiddenLinkRef.current) return;
63
33
  // Create blob URL
64
34
  const hiddenLinkUrl = URL.createObjectURL(new Blob([
65
35
  content
66
36
  ], {
67
- type
37
+ type: contentType
68
38
  }));
69
39
  // Simulate click
70
40
  hiddenLinkRef.current.download = `${dashboard.metadata.name}${shape === 'cr' ? '-cr' : ''}.${format}`;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/DownloadButton/DownloadButton.tsx"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { ClickAwayListener, Menu, MenuItem, MenuList } from '@mui/material';\nimport { ToolbarIconButton } from '@perses-dev/components';\nimport DownloadIcon from 'mdi-material-ui/DownloadOutline';\nimport React, { ReactElement, useRef } from 'react';\nimport { stringify } from 'yaml';\nimport { useDashboard } from '../../context';\n\n// Button that enables downloading the dashboard as a JSON file\nexport function DownloadButton(): ReactElement {\n const { dashboard } = useDashboard();\n const hiddenLinkRef = useRef<HTMLAnchorElement>(null);\n const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);\n const open = Boolean(anchorEl);\n const handleClick = (event: React.MouseEvent<HTMLButtonElement>): void => {\n setAnchorEl(event.currentTarget);\n };\n const handleItemClick = (format: 'json' | 'yaml', shape?: 'cr') => (): void => {\n setAnchorEl(null);\n\n let type,\n content = '';\n\n switch (format) {\n case 'json':\n type = 'application/json';\n content = JSON.stringify(dashboard, null, 2);\n break;\n case 'yaml':\n {\n type = 'application/yaml';\n\n if (shape === 'cr') {\n const name = dashboard.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n content = stringify({\n apiVersion: 'perses.dev/v1alpha1',\n kind: 'PersesDashboard',\n metadata: {\n labels: {\n 'app.kubernetes.io/name': 'perses-dashboard',\n 'app.kubernetes.io/instance': name,\n 'app.kubernetes.io/part-of': 'perses-operator',\n },\n name,\n namespace: dashboard.metadata.project,\n },\n spec: dashboard.spec,\n });\n } else {\n content = stringify(dashboard);\n }\n }\n break;\n }\n\n if (!hiddenLinkRef || !hiddenLinkRef.current) return;\n // Create blob URL\n const hiddenLinkUrl = URL.createObjectURL(new Blob([content], { type }));\n // Simulate click\n hiddenLinkRef.current.download = `${dashboard.metadata.name}${shape === 'cr' ? '-cr' : ''}.${format}`;\n hiddenLinkRef.current.href = hiddenLinkUrl;\n hiddenLinkRef.current.click();\n // Remove blob URL (for memory management)\n URL.revokeObjectURL(hiddenLinkUrl);\n };\n\n return (\n <>\n <ToolbarIconButton\n id=\"download-dashboard-button\"\n aria-controls={open ? 'basic-menu' : undefined}\n aria-haspopup=\"true\"\n aria-expanded={open ? 'true' : undefined}\n onClick={handleClick}\n >\n <DownloadIcon />\n </ToolbarIconButton>\n\n <Menu\n id=\"download-dashboard-formats\"\n anchorEl={anchorEl}\n open={open}\n hideBackdrop={true}\n onClose={() => setAnchorEl(null)}\n MenuListProps={{\n 'aria-labelledby': 'download-dashboard-button',\n }}\n >\n <div>\n <ClickAwayListener onClickAway={() => setAnchorEl(null)}>\n <MenuList>\n <MenuItem onClick={handleItemClick('json')}>JSON</MenuItem>\n <MenuItem onClick={handleItemClick('yaml')}>YAML</MenuItem>\n <MenuItem onClick={handleItemClick('yaml', 'cr')}>YAML (CR)</MenuItem>\n </MenuList>\n </ClickAwayListener>\n </div>\n </Menu>\n\n {/* Hidden link to download the dashboard as a JSON or YAML file */}\n {/* eslint-disable jsx-a11y/anchor-has-content */}\n {/* eslint-disable jsx-a11y/anchor-is-valid */}\n <a ref={hiddenLinkRef} style={{ display: 'none' }} />\n </>\n );\n}\n"],"names":["ClickAwayListener","Menu","MenuItem","MenuList","ToolbarIconButton","DownloadIcon","React","useRef","stringify","useDashboard","DownloadButton","dashboard","hiddenLinkRef","anchorEl","setAnchorEl","useState","open","Boolean","handleClick","event","currentTarget","handleItemClick","format","shape","type","content","JSON","name","metadata","toLowerCase","replace","apiVersion","kind","labels","namespace","project","spec","current","hiddenLinkUrl","URL","createObjectURL","Blob","download","href","click","revokeObjectURL","id","aria-controls","undefined","aria-haspopup","aria-expanded","onClick","hideBackdrop","onClose","MenuListProps","div","onClickAway","a","ref","style","display"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/DownloadButton/DownloadButton.tsx"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { ClickAwayListener, Menu, MenuItem, MenuList } from '@mui/material';\nimport { ToolbarIconButton } from '@perses-dev/components';\nimport DownloadIcon from 'mdi-material-ui/DownloadOutline';\nimport React, { ReactElement, useRef } from 'react';\nimport { useDashboard } from '../../context';\nimport { serializeDashboard } from './serializeDashboard';\n\n// Button that enables downloading the dashboard as a JSON file\nexport function DownloadButton(): ReactElement {\n const { dashboard } = useDashboard();\n const hiddenLinkRef = useRef<HTMLAnchorElement>(null);\n const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);\n const open = Boolean(anchorEl);\n const handleClick = (event: React.MouseEvent<HTMLButtonElement>): void => {\n setAnchorEl(event.currentTarget);\n };\n const handleItemClick = (format: 'json' | 'yaml', shape?: 'cr') => (): void => {\n setAnchorEl(null);\n\n const { contentType, content } = serializeDashboard(dashboard, format, shape);\n\n if (!hiddenLinkRef || !hiddenLinkRef.current) return;\n // Create blob URL\n const hiddenLinkUrl = URL.createObjectURL(new Blob([content], { type: contentType }));\n // Simulate click\n hiddenLinkRef.current.download = `${dashboard.metadata.name}${shape === 'cr' ? '-cr' : ''}.${format}`;\n hiddenLinkRef.current.href = hiddenLinkUrl;\n hiddenLinkRef.current.click();\n // Remove blob URL (for memory management)\n URL.revokeObjectURL(hiddenLinkUrl);\n };\n\n return (\n <>\n <ToolbarIconButton\n id=\"download-dashboard-button\"\n aria-controls={open ? 'basic-menu' : undefined}\n aria-haspopup=\"true\"\n aria-expanded={open ? 'true' : undefined}\n onClick={handleClick}\n >\n <DownloadIcon />\n </ToolbarIconButton>\n\n <Menu\n id=\"download-dashboard-formats\"\n anchorEl={anchorEl}\n open={open}\n hideBackdrop={true}\n onClose={() => setAnchorEl(null)}\n MenuListProps={{\n 'aria-labelledby': 'download-dashboard-button',\n }}\n >\n <div>\n <ClickAwayListener onClickAway={() => setAnchorEl(null)}>\n <MenuList>\n <MenuItem onClick={handleItemClick('json')}>JSON</MenuItem>\n <MenuItem onClick={handleItemClick('yaml')}>YAML</MenuItem>\n <MenuItem onClick={handleItemClick('yaml', 'cr')}>YAML (CR)</MenuItem>\n </MenuList>\n </ClickAwayListener>\n </div>\n </Menu>\n\n {/* Hidden link to download the dashboard as a JSON or YAML file */}\n {/* eslint-disable jsx-a11y/anchor-has-content */}\n {/* eslint-disable jsx-a11y/anchor-is-valid */}\n <a ref={hiddenLinkRef} style={{ display: 'none' }} />\n </>\n );\n}\n"],"names":["ClickAwayListener","Menu","MenuItem","MenuList","ToolbarIconButton","DownloadIcon","React","useRef","useDashboard","serializeDashboard","DownloadButton","dashboard","hiddenLinkRef","anchorEl","setAnchorEl","useState","open","Boolean","handleClick","event","currentTarget","handleItemClick","format","shape","contentType","content","current","hiddenLinkUrl","URL","createObjectURL","Blob","type","download","metadata","name","href","click","revokeObjectURL","id","aria-controls","undefined","aria-haspopup","aria-expanded","onClick","hideBackdrop","onClose","MenuListProps","div","onClickAway","a","ref","style","display"],"mappings":"AAAA,oCAAoC;AACpC,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;;AAEjC,SAASA,iBAAiB,EAAEC,IAAI,EAAEC,QAAQ,EAAEC,QAAQ,QAAQ,gBAAgB;AAC5E,SAASC,iBAAiB,QAAQ,yBAAyB;AAC3D,OAAOC,kBAAkB,kCAAkC;AAC3D,OAAOC,SAAuBC,MAAM,QAAQ,QAAQ;AACpD,SAASC,YAAY,QAAQ,gBAAgB;AAC7C,SAASC,kBAAkB,QAAQ,uBAAuB;AAE1D,+DAA+D;AAC/D,OAAO,SAASC;IACd,MAAM,EAAEC,SAAS,EAAE,GAAGH;IACtB,MAAMI,gBAAgBL,OAA0B;IAChD,MAAM,CAACM,UAAUC,YAAY,GAAGR,MAAMS,QAAQ,CAAqB;IACnE,MAAMC,OAAOC,QAAQJ;IACrB,MAAMK,cAAc,CAACC;QACnBL,YAAYK,MAAMC,aAAa;IACjC;IACA,MAAMC,kBAAkB,CAACC,QAAyBC,QAAiB;YACjET,YAAY;YAEZ,MAAM,EAAEU,WAAW,EAAEC,OAAO,EAAE,GAAGhB,mBAAmBE,WAAWW,QAAQC;YAEvE,IAAI,CAACX,iBAAiB,CAACA,cAAcc,OAAO,EAAE;YAC9C,kBAAkB;YAClB,MAAMC,gBAAgBC,IAAIC,eAAe,CAAC,IAAIC,KAAK;gBAACL;aAAQ,EAAE;gBAAEM,MAAMP;YAAY;YAClF,iBAAiB;YACjBZ,cAAcc,OAAO,CAACM,QAAQ,GAAG,GAAGrB,UAAUsB,QAAQ,CAACC,IAAI,GAAGX,UAAU,OAAO,QAAQ,GAAG,CAAC,EAAED,QAAQ;YACrGV,cAAcc,OAAO,CAACS,IAAI,GAAGR;YAC7Bf,cAAcc,OAAO,CAACU,KAAK;YAC3B,0CAA0C;YAC1CR,IAAIS,eAAe,CAACV;QACtB;IAEA,qBACE;;0BACE,KAACvB;gBACCkC,IAAG;gBACHC,iBAAevB,OAAO,eAAewB;gBACrCC,iBAAc;gBACdC,iBAAe1B,OAAO,SAASwB;gBAC/BG,SAASzB;0BAET,cAAA,KAACb;;0BAGH,KAACJ;gBACCqC,IAAG;gBACHzB,UAAUA;gBACVG,MAAMA;gBACN4B,cAAc;gBACdC,SAAS,IAAM/B,YAAY;gBAC3BgC,eAAe;oBACb,mBAAmB;gBACrB;0BAEA,cAAA,KAACC;8BACC,cAAA,KAAC/C;wBAAkBgD,aAAa,IAAMlC,YAAY;kCAChD,cAAA,MAACX;;8CACC,KAACD;oCAASyC,SAAStB,gBAAgB;8CAAS;;8CAC5C,KAACnB;oCAASyC,SAAStB,gBAAgB;8CAAS;;8CAC5C,KAACnB;oCAASyC,SAAStB,gBAAgB,QAAQ;8CAAO;;;;;;;0BAS1D,KAAC4B;gBAAEC,KAAKtC;gBAAeuC,OAAO;oBAAEC,SAAS;gBAAO;;;;AAGtD"}
@@ -0,0 +1,8 @@
1
+ import { DashboardResource, EphemeralDashboardResource } from '@perses-dev/core';
2
+ type SerializedDashboard = {
3
+ contentType: string;
4
+ content: string;
5
+ };
6
+ export declare function serializeDashboard(dashboard: DashboardResource | EphemeralDashboardResource, format: 'json' | 'yaml', shape?: 'cr'): SerializedDashboard;
7
+ export {};
8
+ //# sourceMappingURL=serializeDashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializeDashboard.d.ts","sourceRoot":"","sources":["../../../src/components/DownloadButton/serializeDashboard.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAGjF,KAAK,mBAAmB,GAAG;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA+BF,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,iBAAiB,GAAG,0BAA0B,EACzD,MAAM,EAAE,MAAM,GAAG,MAAM,EACvB,KAAK,CAAC,EAAE,IAAI,GACX,mBAAmB,CAOrB"}
@@ -0,0 +1,56 @@
1
+ // Copyright 2023 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
+ import { stringify } from 'yaml';
14
+ function serializeYaml(dashboard, shape) {
15
+ let content;
16
+ if (shape === 'cr') {
17
+ const name = dashboard.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
18
+ content = stringify({
19
+ apiVersion: 'perses.dev/v1alpha1',
20
+ kind: 'PersesDashboard',
21
+ metadata: {
22
+ labels: {
23
+ 'app.kubernetes.io/name': 'perses-dashboard',
24
+ 'app.kubernetes.io/instance': name,
25
+ 'app.kubernetes.io/part-of': 'perses-operator'
26
+ },
27
+ name,
28
+ namespace: dashboard.metadata.project
29
+ },
30
+ spec: dashboard.spec
31
+ }, {
32
+ schema: 'yaml-1.1'
33
+ });
34
+ } else {
35
+ content = stringify(dashboard, {
36
+ schema: 'yaml-1.1'
37
+ });
38
+ }
39
+ return {
40
+ contentType: 'application/yaml',
41
+ content
42
+ };
43
+ }
44
+ export function serializeDashboard(dashboard, format, shape) {
45
+ switch(format){
46
+ case 'json':
47
+ return {
48
+ contentType: 'application/json',
49
+ content: JSON.stringify(dashboard, null, 2)
50
+ };
51
+ case 'yaml':
52
+ return serializeYaml(dashboard, shape);
53
+ }
54
+ }
55
+
56
+ //# sourceMappingURL=serializeDashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/DownloadButton/serializeDashboard.ts"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { DashboardResource, EphemeralDashboardResource } from '@perses-dev/core';\nimport { stringify } from 'yaml';\n\ntype SerializedDashboard = {\n contentType: string;\n content: string;\n};\n\nfunction serializeYaml(dashboard: DashboardResource | EphemeralDashboardResource, shape?: 'cr'): SerializedDashboard {\n let content: string;\n\n if (shape === 'cr') {\n const name = dashboard.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n content = stringify(\n {\n apiVersion: 'perses.dev/v1alpha1',\n kind: 'PersesDashboard',\n metadata: {\n labels: {\n 'app.kubernetes.io/name': 'perses-dashboard',\n 'app.kubernetes.io/instance': name,\n 'app.kubernetes.io/part-of': 'perses-operator',\n },\n name,\n namespace: dashboard.metadata.project,\n },\n spec: dashboard.spec,\n },\n { schema: 'yaml-1.1' }\n );\n } else {\n content = stringify(dashboard, { schema: 'yaml-1.1' });\n }\n\n return { contentType: 'application/yaml', content };\n}\n\nexport function serializeDashboard(\n dashboard: DashboardResource | EphemeralDashboardResource,\n format: 'json' | 'yaml',\n shape?: 'cr'\n): SerializedDashboard {\n switch (format) {\n case 'json':\n return { contentType: 'application/json', content: JSON.stringify(dashboard, null, 2) };\n case 'yaml':\n return serializeYaml(dashboard, shape);\n }\n}\n"],"names":["stringify","serializeYaml","dashboard","shape","content","name","metadata","toLowerCase","replace","apiVersion","kind","labels","namespace","project","spec","schema","contentType","serializeDashboard","format","JSON"],"mappings":"AAAA,oCAAoC;AACpC,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;AAGjC,SAASA,SAAS,QAAQ,OAAO;AAOjC,SAASC,cAAcC,SAAyD,EAAEC,KAAY;IAC5F,IAAIC;IAEJ,IAAID,UAAU,MAAM;QAClB,MAAME,OAAOH,UAAUI,QAAQ,CAACD,IAAI,CAACE,WAAW,GAAGC,OAAO,CAAC,eAAe;QAC1EJ,UAAUJ,UACR;YACES,YAAY;YACZC,MAAM;YACNJ,UAAU;gBACRK,QAAQ;oBACN,0BAA0B;oBAC1B,8BAA8BN;oBAC9B,6BAA6B;gBAC/B;gBACAA;gBACAO,WAAWV,UAAUI,QAAQ,CAACO,OAAO;YACvC;YACAC,MAAMZ,UAAUY,IAAI;QACtB,GACA;YAAEC,QAAQ;QAAW;IAEzB,OAAO;QACLX,UAAUJ,UAAUE,WAAW;YAAEa,QAAQ;QAAW;IACtD;IAEA,OAAO;QAAEC,aAAa;QAAoBZ;IAAQ;AACpD;AAEA,OAAO,SAASa,mBACdf,SAAyD,EACzDgB,MAAuB,EACvBf,KAAY;IAEZ,OAAQe;QACN,KAAK;YACH,OAAO;gBAAEF,aAAa;gBAAoBZ,SAASe,KAAKnB,SAAS,CAACE,WAAW,MAAM;YAAG;QACxF,KAAK;YACH,OAAOD,cAAcC,WAAWC;IACpC;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"Panel.d.ts","sourceRoot":"","sources":["../../../src/components/Panel/Panel.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAqB,SAAS,EAAE,MAAM,eAAe,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,SAAS,EAA2B,MAAM,OAAO,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAe,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE9D,MAAM,WAAW,UAAW,SAAQ,SAAS,CAAC,SAAS,CAAC;IACtD,UAAU,EAAE,eAAe,CAAC;IAC5B,YAAY,CAAC,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAChD,YAAY,CAAC,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAChD,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,MAAM,MAAM,YAAY,GAAG;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,SAAS,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;OAEG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,kDAmGhB,CAAC"}
1
+ {"version":3,"file":"Panel.d.ts","sourceRoot":"","sources":["../../../src/components/Panel/Panel.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAqB,SAAS,EAAE,MAAM,eAAe,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,SAAS,EAAsC,MAAM,OAAO,CAAC;AAEtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAe,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE9D,MAAM,WAAW,UAAW,SAAQ,SAAS,CAAC,SAAS,CAAC;IACtD,UAAU,EAAE,eAAe,CAAC;IAC5B,YAAY,CAAC,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAChD,YAAY,CAAC,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAChD,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,MAAM,MAAM,YAAY,GAAG;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,SAAS,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;OAEG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,kDAkMhB,CAAC"}
@@ -13,8 +13,8 @@
13
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
14
  import { Card, CardContent } from '@mui/material';
15
15
  import { ErrorAlert, ErrorBoundary, combineSx, useChartsTheme, useId } from '@perses-dev/components';
16
- import { useDataQueriesContext } from '@perses-dev/plugin-system';
17
- import { memo, useMemo, useState } from 'react';
16
+ import { useDataQueriesContext, usePluginRegistry } from '@perses-dev/plugin-system';
17
+ import { memo, useMemo, useState, useEffect } from 'react';
18
18
  import useResizeObserver from 'use-resize-observer';
19
19
  import { PanelContent } from './PanelContent';
20
20
  import { PanelHeader } from './PanelHeader';
@@ -46,6 +46,88 @@ import { PanelHeader } from './PanelHeader';
46
46
  ]);
47
47
  const chartsTheme = useChartsTheme();
48
48
  const { queryResults } = useDataQueriesContext();
49
+ const { getPlugin } = usePluginRegistry();
50
+ const panelPropsForActions = useMemo(()=>{
51
+ return {
52
+ spec: definition.spec.plugin.spec,
53
+ queryResults: queryResults.map((query)=>({
54
+ definition: query.definition,
55
+ data: query.data
56
+ })),
57
+ contentDimensions,
58
+ definition
59
+ };
60
+ }, [
61
+ definition,
62
+ contentDimensions,
63
+ queryResults
64
+ ]);
65
+ // Load plugin actions from the plugin
66
+ const [pluginActions, setPluginActions] = useState([]);
67
+ useEffect(()=>{
68
+ let cancelled = false;
69
+ const loadPluginActions = async ()=>{
70
+ const panelPluginKind = definition.spec.plugin.kind;
71
+ const panelProps = panelPropsForActions;
72
+ if (!panelPluginKind || !panelProps) {
73
+ if (!cancelled) {
74
+ setPluginActions([]);
75
+ }
76
+ return;
77
+ }
78
+ try {
79
+ // Add defensive check for getPlugin availability
80
+ if (!getPlugin || typeof getPlugin !== 'function') {
81
+ if (!cancelled) {
82
+ setPluginActions([]);
83
+ }
84
+ return;
85
+ }
86
+ const plugin = await getPlugin('Panel', panelPluginKind);
87
+ if (cancelled) return;
88
+ // More defensive checking for plugin and actions
89
+ if (!plugin || typeof plugin !== 'object' || !plugin.actions || !Array.isArray(plugin.actions) || plugin.actions.length === 0) {
90
+ if (!cancelled) {
91
+ setPluginActions([]);
92
+ }
93
+ return;
94
+ }
95
+ // Render plugin actions in header location
96
+ const headerActions = plugin.actions.filter((action)=>!action.location || action.location === 'header').map((action, index)=>{
97
+ const ActionComponent = action.component;
98
+ try {
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ return /*#__PURE__*/ _jsx(ActionComponent, {
101
+ ...panelProps
102
+ }, `plugin-action-${index}`);
103
+ } catch (error) {
104
+ console.warn(`Failed to render plugin action ${index}:`, error);
105
+ return null;
106
+ }
107
+ }).filter((item)=>Boolean(item));
108
+ if (!cancelled) {
109
+ setPluginActions(headerActions);
110
+ }
111
+ } catch (error) {
112
+ if (!cancelled) {
113
+ console.warn('Failed to load plugin actions:', error);
114
+ setPluginActions([]);
115
+ }
116
+ }
117
+ };
118
+ // Use setTimeout to defer the async operation to the next tick
119
+ const timeoutId = setTimeout(()=>{
120
+ loadPluginActions();
121
+ }, 0);
122
+ return ()=>{
123
+ cancelled = true;
124
+ clearTimeout(timeoutId);
125
+ };
126
+ }, [
127
+ definition.spec.plugin.kind,
128
+ panelPropsForActions,
129
+ getPlugin
130
+ ]);
49
131
  const handleMouseEnter = (e)=>{
50
132
  onMouseEnter?.(e);
51
133
  };
@@ -83,6 +165,7 @@ import { PanelHeader } from './PanelHeader';
83
165
  readHandlers: readHandlers,
84
166
  editHandlers: editHandlers,
85
167
  links: definition.spec.links,
168
+ pluginActions: pluginActions,
86
169
  sx: {
87
170
  paddingX: `${chartsTheme.container.padding.default}px`
88
171
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Panel/Panel.tsx"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { Card, CardContent, CardProps } from '@mui/material';\nimport { ErrorAlert, ErrorBoundary, combineSx, useChartsTheme, useId } from '@perses-dev/components';\nimport { PanelDefinition } from '@perses-dev/core';\nimport { useDataQueriesContext } from '@perses-dev/plugin-system';\nimport { ReactNode, memo, useMemo, useState } from 'react';\nimport useResizeObserver from 'use-resize-observer';\nimport { PanelGroupItemId } from '../../context';\nimport { PanelContent } from './PanelContent';\nimport { PanelHeader, PanelHeaderProps } from './PanelHeader';\n\nexport interface PanelProps extends CardProps<'section'> {\n definition: PanelDefinition;\n readHandlers?: PanelHeaderProps['readHandlers'];\n editHandlers?: PanelHeaderProps['editHandlers'];\n panelOptions?: PanelOptions;\n panelGroupItemId?: PanelGroupItemId;\n}\n\nexport type PanelOptions = {\n /**\n * Allow you to hide the panel header if desired.\n * This can be useful in embedded mode for example.\n */\n hideHeader?: boolean;\n /**\n * Content to render in right of the panel header. (top right of the panel)\n * It will only be rendered when the panel is in edit mode.\n */\n extra?: (props: PanelExtraProps) => ReactNode;\n};\n\nexport type PanelExtraProps = {\n /**\n * The PanelDefinition for the panel.\n */\n panelDefinition?: PanelDefinition;\n /**\n * The PanelGroupItemId for the panel.\n */\n panelGroupItemId?: PanelGroupItemId;\n};\n\n/**\n * Renders a PanelDefinition's content inside of a Card.\n *\n * Internal structure:\n * <Panel> // renders an entire panel, incl. header and action buttons\n * <PanelContent> // renders loading, error or panel based on the queries' status\n * <PanelPluginLoader> // loads a panel plugin from the plugin registry and renders the PanelComponent with data from props.queryResults\n */\nexport const Panel = memo(function Panel(props: PanelProps) {\n const {\n definition,\n readHandlers,\n editHandlers,\n onMouseEnter,\n onMouseLeave,\n sx,\n panelOptions,\n panelGroupItemId,\n ...others\n } = props;\n\n // Make sure we have an ID we can use for aria attributes\n const generatedPanelId = useId('Panel');\n const headerId = `${generatedPanelId}-header`;\n\n const [contentElement, setContentElement] = useState<HTMLElement | null>(null);\n\n const { width, height } = useResizeObserver({ ref: contentElement });\n\n const contentDimensions = useMemo(() => {\n if (width === undefined || height === undefined) return undefined;\n return { width, height };\n }, [width, height]);\n\n const chartsTheme = useChartsTheme();\n\n const { queryResults } = useDataQueriesContext();\n\n const handleMouseEnter: CardProps['onMouseEnter'] = (e) => {\n onMouseEnter?.(e);\n };\n\n const handleMouseLeave: CardProps['onMouseLeave'] = (e) => {\n onMouseLeave?.(e);\n };\n\n return (\n <Card\n component=\"section\"\n sx={combineSx(\n {\n width: '100%',\n height: '100%',\n display: 'flex',\n flexFlow: 'column nowrap',\n ':hover': { '--panel-hover': 'block' },\n },\n sx\n )}\n variant=\"outlined\"\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n aria-labelledby={headerId}\n aria-describedby={headerId}\n data-testid=\"panel\"\n {...others}\n >\n {!panelOptions?.hideHeader && (\n <PanelHeader\n extra={panelOptions?.extra?.({ panelDefinition: definition, panelGroupItemId })}\n id={headerId}\n title={definition.spec.display.name}\n description={definition.spec.display.description}\n queryResults={queryResults}\n readHandlers={readHandlers}\n editHandlers={editHandlers}\n links={definition.spec.links}\n sx={{ paddingX: `${chartsTheme.container.padding.default}px` }}\n />\n )}\n <CardContent\n component=\"figure\"\n sx={{\n position: 'relative',\n overflow: 'hidden',\n flexGrow: 1,\n margin: 0,\n padding: 0,\n // Override MUI default style for last-child\n ':last-child': {\n padding: 0,\n },\n }}\n ref={setContentElement}\n >\n <ErrorBoundary FallbackComponent={ErrorAlert} resetKeys={[definition.spec, queryResults]}>\n <PanelContent\n definition={definition}\n panelPluginKind={definition.spec.plugin.kind}\n spec={definition.spec.plugin.spec}\n contentDimensions={contentDimensions}\n queryResults={queryResults}\n />\n </ErrorBoundary>\n </CardContent>\n </Card>\n );\n});\n"],"names":["Card","CardContent","ErrorAlert","ErrorBoundary","combineSx","useChartsTheme","useId","useDataQueriesContext","memo","useMemo","useState","useResizeObserver","PanelContent","PanelHeader","Panel","props","definition","readHandlers","editHandlers","onMouseEnter","onMouseLeave","sx","panelOptions","panelGroupItemId","others","generatedPanelId","headerId","contentElement","setContentElement","width","height","ref","contentDimensions","undefined","chartsTheme","queryResults","handleMouseEnter","e","handleMouseLeave","component","display","flexFlow","variant","aria-labelledby","aria-describedby","data-testid","hideHeader","extra","panelDefinition","id","title","spec","name","description","links","paddingX","container","padding","default","position","overflow","flexGrow","margin","FallbackComponent","resetKeys","panelPluginKind","plugin","kind"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/Panel/Panel.tsx"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { Card, CardContent, CardProps } from '@mui/material';\nimport { ErrorAlert, ErrorBoundary, combineSx, useChartsTheme, useId } from '@perses-dev/components';\nimport { PanelDefinition } from '@perses-dev/core';\nimport { useDataQueriesContext, usePluginRegistry } from '@perses-dev/plugin-system';\nimport { ReactNode, memo, useMemo, useState, useEffect } from 'react';\nimport useResizeObserver from 'use-resize-observer';\nimport { PanelGroupItemId } from '../../context';\nimport { PanelContent } from './PanelContent';\nimport { PanelHeader, PanelHeaderProps } from './PanelHeader';\n\nexport interface PanelProps extends CardProps<'section'> {\n definition: PanelDefinition;\n readHandlers?: PanelHeaderProps['readHandlers'];\n editHandlers?: PanelHeaderProps['editHandlers'];\n panelOptions?: PanelOptions;\n panelGroupItemId?: PanelGroupItemId;\n}\n\nexport type PanelOptions = {\n /**\n * Allow you to hide the panel header if desired.\n * This can be useful in embedded mode for example.\n */\n hideHeader?: boolean;\n /**\n * Content to render in right of the panel header. (top right of the panel)\n * It will only be rendered when the panel is in edit mode.\n */\n extra?: (props: PanelExtraProps) => ReactNode;\n};\n\nexport type PanelExtraProps = {\n /**\n * The PanelDefinition for the panel.\n */\n panelDefinition?: PanelDefinition;\n /**\n * The PanelGroupItemId for the panel.\n */\n panelGroupItemId?: PanelGroupItemId;\n};\n\n/**\n * Renders a PanelDefinition's content inside of a Card.\n *\n * Internal structure:\n * <Panel> // renders an entire panel, incl. header and action buttons\n * <PanelContent> // renders loading, error or panel based on the queries' status\n * <PanelPluginLoader> // loads a panel plugin from the plugin registry and renders the PanelComponent with data from props.queryResults\n */\nexport const Panel = memo(function Panel(props: PanelProps) {\n const {\n definition,\n readHandlers,\n editHandlers,\n onMouseEnter,\n onMouseLeave,\n sx,\n panelOptions,\n panelGroupItemId,\n ...others\n } = props;\n\n // Make sure we have an ID we can use for aria attributes\n const generatedPanelId = useId('Panel');\n const headerId = `${generatedPanelId}-header`;\n\n const [contentElement, setContentElement] = useState<HTMLElement | null>(null);\n\n const { width, height } = useResizeObserver({ ref: contentElement });\n\n const contentDimensions = useMemo(() => {\n if (width === undefined || height === undefined) return undefined;\n return { width, height };\n }, [width, height]);\n\n const chartsTheme = useChartsTheme();\n\n const { queryResults } = useDataQueriesContext();\n const { getPlugin } = usePluginRegistry();\n\n const panelPropsForActions = useMemo(() => {\n return {\n spec: definition.spec.plugin.spec,\n queryResults: queryResults.map((query) => ({\n definition: query.definition,\n data: query.data,\n })),\n contentDimensions,\n definition,\n };\n }, [definition, contentDimensions, queryResults]);\n\n // Load plugin actions from the plugin\n const [pluginActions, setPluginActions] = useState<ReactNode[]>([]);\n\n useEffect(() => {\n let cancelled = false;\n\n const loadPluginActions = async (): Promise<void> => {\n const panelPluginKind = definition.spec.plugin.kind;\n const panelProps = panelPropsForActions;\n\n if (!panelPluginKind || !panelProps) {\n if (!cancelled) {\n setPluginActions([]);\n }\n return;\n }\n\n try {\n // Add defensive check for getPlugin availability\n if (!getPlugin || typeof getPlugin !== 'function') {\n if (!cancelled) {\n setPluginActions([]);\n }\n return;\n }\n\n const plugin = await getPlugin('Panel', panelPluginKind);\n\n if (cancelled) return;\n\n // More defensive checking for plugin and actions\n if (\n !plugin ||\n typeof plugin !== 'object' ||\n !plugin.actions ||\n !Array.isArray(plugin.actions) ||\n plugin.actions.length === 0\n ) {\n if (!cancelled) {\n setPluginActions([]);\n }\n return;\n }\n\n // Render plugin actions in header location\n const headerActions = plugin.actions\n .filter((action) => !action.location || action.location === 'header')\n .map((action, index): ReactNode | null => {\n const ActionComponent = action.component;\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return <ActionComponent key={`plugin-action-${index}`} {...(panelProps as any)} />;\n } catch (error) {\n console.warn(`Failed to render plugin action ${index}:`, error);\n return null;\n }\n })\n .filter((item): item is ReactNode => Boolean(item));\n\n if (!cancelled) {\n setPluginActions(headerActions);\n }\n } catch (error) {\n if (!cancelled) {\n console.warn('Failed to load plugin actions:', error);\n setPluginActions([]);\n }\n }\n };\n\n // Use setTimeout to defer the async operation to the next tick\n const timeoutId = setTimeout(() => {\n loadPluginActions();\n }, 0);\n\n return (): void => {\n cancelled = true;\n clearTimeout(timeoutId);\n };\n }, [definition.spec.plugin.kind, panelPropsForActions, getPlugin]);\n\n const handleMouseEnter: CardProps['onMouseEnter'] = (e) => {\n onMouseEnter?.(e);\n };\n\n const handleMouseLeave: CardProps['onMouseLeave'] = (e) => {\n onMouseLeave?.(e);\n };\n\n return (\n <Card\n component=\"section\"\n sx={combineSx(\n {\n width: '100%',\n height: '100%',\n display: 'flex',\n flexFlow: 'column nowrap',\n ':hover': { '--panel-hover': 'block' },\n },\n sx\n )}\n variant=\"outlined\"\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n aria-labelledby={headerId}\n aria-describedby={headerId}\n data-testid=\"panel\"\n {...others}\n >\n {!panelOptions?.hideHeader && (\n <PanelHeader\n extra={panelOptions?.extra?.({ panelDefinition: definition, panelGroupItemId })}\n id={headerId}\n title={definition.spec.display.name}\n description={definition.spec.display.description}\n queryResults={queryResults}\n readHandlers={readHandlers}\n editHandlers={editHandlers}\n links={definition.spec.links}\n pluginActions={pluginActions}\n sx={{ paddingX: `${chartsTheme.container.padding.default}px` }}\n />\n )}\n <CardContent\n component=\"figure\"\n sx={{\n position: 'relative',\n overflow: 'hidden',\n flexGrow: 1,\n margin: 0,\n padding: 0,\n // Override MUI default style for last-child\n ':last-child': {\n padding: 0,\n },\n }}\n ref={setContentElement}\n >\n <ErrorBoundary FallbackComponent={ErrorAlert} resetKeys={[definition.spec, queryResults]}>\n <PanelContent\n definition={definition}\n panelPluginKind={definition.spec.plugin.kind}\n spec={definition.spec.plugin.spec}\n contentDimensions={contentDimensions}\n queryResults={queryResults}\n />\n </ErrorBoundary>\n </CardContent>\n </Card>\n );\n});\n"],"names":["Card","CardContent","ErrorAlert","ErrorBoundary","combineSx","useChartsTheme","useId","useDataQueriesContext","usePluginRegistry","memo","useMemo","useState","useEffect","useResizeObserver","PanelContent","PanelHeader","Panel","props","definition","readHandlers","editHandlers","onMouseEnter","onMouseLeave","sx","panelOptions","panelGroupItemId","others","generatedPanelId","headerId","contentElement","setContentElement","width","height","ref","contentDimensions","undefined","chartsTheme","queryResults","getPlugin","panelPropsForActions","spec","plugin","map","query","data","pluginActions","setPluginActions","cancelled","loadPluginActions","panelPluginKind","kind","panelProps","actions","Array","isArray","length","headerActions","filter","action","location","index","ActionComponent","component","error","console","warn","item","Boolean","timeoutId","setTimeout","clearTimeout","handleMouseEnter","e","handleMouseLeave","display","flexFlow","variant","aria-labelledby","aria-describedby","data-testid","hideHeader","extra","panelDefinition","id","title","name","description","links","paddingX","container","padding","default","position","overflow","flexGrow","margin","FallbackComponent","resetKeys"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- /// <reference types="react" />
1
+ import { ReactNode } from 'react';
2
2
  import { QueryData } from '@perses-dev/plugin-system';
3
3
  import { Link } from '@perses-dev/core';
4
4
  export interface PanelActionsProps {
@@ -17,6 +17,7 @@ export interface PanelActionsProps {
17
17
  onViewPanelClick: () => void;
18
18
  };
19
19
  queryResults: QueryData[];
20
+ pluginActions?: ReactNode[];
20
21
  }
21
22
  export declare const PanelActions: React.FC<PanelActionsProps>;
22
23
  //# sourceMappingURL=PanelActions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PanelActions.d.ts","sourceRoot":"","sources":["../../../src/components/Panel/PanelActions.tsx"],"names":[],"mappings":";AAuBA,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAGtD,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAWxC,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,YAAY,CAAC,EAAE;QACb,gBAAgB,EAAE,MAAM,IAAI,CAAC;QAC7B,qBAAqB,EAAE,MAAM,IAAI,CAAC;QAClC,kBAAkB,EAAE,MAAM,IAAI,CAAC;KAChC,CAAC;IACF,YAAY,CAAC,EAAE;QACb,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,gBAAgB,EAAE,MAAM,IAAI,CAAC;KAC9B,CAAC;IACF,YAAY,EAAE,SAAS,EAAE,CAAC;CAC3B;AASD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAqMpD,CAAC"}
1
+ {"version":3,"file":"PanelActions.d.ts","sourceRoot":"","sources":["../../../src/components/Panel/PanelActions.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAqC,SAAS,EAAqB,MAAM,OAAO,CAAC;AAExF,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAUtD,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAWxC,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,YAAY,CAAC,EAAE;QACb,gBAAgB,EAAE,MAAM,IAAI,CAAC;QAC7B,qBAAqB,EAAE,MAAM,IAAI,CAAC;QAClC,kBAAkB,EAAE,MAAM,IAAI,CAAC;KAChC,CAAC;IACF,YAAY,CAAC,EAAE;QACb,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,gBAAgB,EAAE,MAAM,IAAI,CAAC;KAC9B,CAAC;IACF,YAAY,EAAE,SAAS,EAAE,CAAC;IAC1B,aAAa,CAAC,EAAE,SAAS,EAAE,CAAC;CAC7B;AASD,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAuMpD,CAAC"}
@@ -32,7 +32,7 @@ const ConditionalBox = styled(Box)({
32
32
  flexGrow: 1,
33
33
  justifyContent: 'flex-end'
34
34
  });
35
- export const PanelActions = ({ editHandlers, readHandlers, extra, title, description, descriptionTooltipId, links, queryResults })=>{
35
+ export const PanelActions = ({ editHandlers, readHandlers, extra, title, description, descriptionTooltipId, links, queryResults, pluginActions = [] })=>{
36
36
  const descriptionAction = useMemo(()=>{
37
37
  if (description && description.trim().length > 0) {
38
38
  return /*#__PURE__*/ _jsx(InfoTooltip, {
@@ -67,8 +67,6 @@ export const PanelActions = ({ editHandlers, readHandlers, extra, title, descrip
67
67
  const isFetching = queryResults.some((q)=>q.isFetching);
68
68
  const queryErrors = queryResults.filter((q)=>q.error);
69
69
  if (isFetching && hasData) {
70
- // If the panel has no data, the panel content will show the loading overlay.
71
- // Therefore, show the circular loading indicator only in case the panel doesn't display the loading overlay already.
72
70
  return /*#__PURE__*/ _jsx(CircularProgress, {
73
71
  "aria-label": "loading",
74
72
  size: "1.125rem"
@@ -227,6 +225,7 @@ export const PanelActions = ({ editHandlers, readHandlers, extra, title, descrip
227
225
  editActions
228
226
  ]
229
227
  }),
228
+ pluginActions,
230
229
  moveAction
231
230
  ]
232
231
  })
@@ -254,6 +253,8 @@ export const PanelActions = ({ editHandlers, readHandlers, extra, title, descrip
254
253
  extraActions,
255
254
  " ",
256
255
  readActions,
256
+ " ",
257
+ pluginActions,
257
258
  /*#__PURE__*/ _jsx(OverflowMenu, {
258
259
  title: title,
259
260
  children: editActions
@@ -288,6 +289,8 @@ export const PanelActions = ({ editHandlers, readHandlers, extra, title, descrip
288
289
  " ",
289
290
  readActions,
290
291
  " ",
292
+ pluginActions,
293
+ " ",
291
294
  editActions,
292
295
  " ",
293
296
  moveAction
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Panel/PanelActions.tsx"],"sourcesContent":["// Copyright 2025 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { Stack, Box, Popover, CircularProgress, styled, PopoverPosition } from '@mui/material';\nimport { isValidElement, PropsWithChildren, ReactNode, useMemo, useState } from 'react';\nimport { InfoTooltip } from '@perses-dev/components';\nimport ArrowCollapseIcon from 'mdi-material-ui/ArrowCollapse';\nimport ArrowExpandIcon from 'mdi-material-ui/ArrowExpand';\nimport PencilIcon from 'mdi-material-ui/PencilOutline';\nimport DeleteIcon from 'mdi-material-ui/DeleteOutline';\nimport DragIcon from 'mdi-material-ui/DragVertical';\nimport ContentCopyIcon from 'mdi-material-ui/ContentCopy';\nimport MenuIcon from 'mdi-material-ui/Menu';\nimport { QueryData } from '@perses-dev/plugin-system';\nimport AlertIcon from 'mdi-material-ui/Alert';\nimport InformationOutlineIcon from 'mdi-material-ui/InformationOutline';\nimport { Link } from '@perses-dev/core';\nimport {\n ARIA_LABEL_TEXT,\n HEADER_ACTIONS_CONTAINER_NAME,\n HEADER_MEDIUM_WIDTH,\n HEADER_SMALL_WIDTH,\n TOOLTIP_TEXT,\n} from '../../constants';\nimport { HeaderIconButton } from './HeaderIconButton';\nimport { PanelLinks } from './PanelLinks';\n\nexport interface PanelActionsProps {\n title: string;\n description?: string;\n descriptionTooltipId: string;\n links?: Link[];\n extra?: React.ReactNode;\n editHandlers?: {\n onEditPanelClick: () => void;\n onDuplicatePanelClick: () => void;\n onDeletePanelClick: () => void;\n };\n readHandlers?: {\n isPanelViewed?: boolean;\n onViewPanelClick: () => void;\n };\n queryResults: QueryData[];\n}\n\nconst ConditionalBox = styled(Box)({\n display: 'none',\n alignItems: 'center',\n flexGrow: 1,\n justifyContent: 'flex-end',\n});\n\nexport const PanelActions: React.FC<PanelActionsProps> = ({\n editHandlers,\n readHandlers,\n extra,\n title,\n description,\n descriptionTooltipId,\n links,\n queryResults,\n}) => {\n const descriptionAction = useMemo(() => {\n if (description && description.trim().length > 0) {\n return (\n <InfoTooltip id={descriptionTooltipId} description={description} enterDelay={100}>\n <HeaderIconButton aria-label=\"panel description\" size=\"small\">\n <InformationOutlineIcon\n aria-describedby=\"info-tooltip\"\n aria-hidden={false}\n fontSize=\"inherit\"\n sx={{ color: (theme) => theme.palette.text.secondary }}\n />\n </HeaderIconButton>\n </InfoTooltip>\n );\n }\n return undefined;\n }, [descriptionTooltipId, description]);\n const linksAction = links && links.length > 0 && <PanelLinks links={links} />;\n const extraActions = editHandlers === undefined && extra;\n\n const queryStateIndicator = useMemo(() => {\n const hasData = queryResults.some((q) => q.data);\n const isFetching = queryResults.some((q) => q.isFetching);\n const queryErrors = queryResults.filter((q) => q.error);\n if (isFetching && hasData) {\n // If the panel has no data, the panel content will show the loading overlay.\n // Therefore, show the circular loading indicator only in case the panel doesn't display the loading overlay already.\n return <CircularProgress aria-label=\"loading\" size=\"1.125rem\" />;\n } else if (queryErrors.length > 0) {\n const errorTexts = queryErrors\n .map((q) => q.error)\n .map((e: any) => e?.message ?? e?.toString() ?? 'Unknown error') // eslint-disable-line @typescript-eslint/no-explicit-any\n .join('\\n');\n\n return (\n <InfoTooltip description={errorTexts}>\n <HeaderIconButton aria-label=\"panel errors\" size=\"small\">\n <AlertIcon fontSize=\"inherit\" />\n </HeaderIconButton>\n </InfoTooltip>\n );\n }\n }, [queryResults]);\n\n const readActions = useMemo(() => {\n if (readHandlers !== undefined) {\n return (\n <InfoTooltip description={TOOLTIP_TEXT.viewPanel}>\n <HeaderIconButton\n aria-label={ARIA_LABEL_TEXT.viewPanel(title)}\n size=\"small\"\n onClick={readHandlers.onViewPanelClick}\n >\n {readHandlers.isPanelViewed ? (\n <ArrowCollapseIcon fontSize=\"inherit\" />\n ) : (\n <ArrowExpandIcon fontSize=\"inherit\" />\n )}\n </HeaderIconButton>\n </InfoTooltip>\n );\n }\n return undefined;\n }, [readHandlers, title]);\n\n const editActions = useMemo(() => {\n if (editHandlers !== undefined) {\n // If there are edit handlers, always just show the edit buttons\n return (\n <>\n <InfoTooltip description={TOOLTIP_TEXT.editPanel}>\n <HeaderIconButton\n aria-label={ARIA_LABEL_TEXT.editPanel(title)}\n size=\"small\"\n onClick={editHandlers.onEditPanelClick}\n >\n <PencilIcon fontSize=\"inherit\" />\n </HeaderIconButton>\n </InfoTooltip>\n <InfoTooltip description={TOOLTIP_TEXT.duplicatePanel}>\n <HeaderIconButton\n aria-label={ARIA_LABEL_TEXT.duplicatePanel(title)}\n size=\"small\"\n onClick={editHandlers.onDuplicatePanelClick}\n >\n <ContentCopyIcon\n fontSize=\"inherit\"\n sx={{\n // Shrink this icon a little bit to look more consistent\n // with the other icons in the header.\n transform: 'scale(0.925)',\n }}\n />\n </HeaderIconButton>\n </InfoTooltip>\n <InfoTooltip description={TOOLTIP_TEXT.deletePanel}>\n <HeaderIconButton\n aria-label={ARIA_LABEL_TEXT.deletePanel(title)}\n size=\"small\"\n onClick={editHandlers.onDeletePanelClick}\n >\n <DeleteIcon fontSize=\"inherit\" />\n </HeaderIconButton>\n </InfoTooltip>\n </>\n );\n }\n return undefined;\n }, [editHandlers, title]);\n\n const moveAction = useMemo(() => {\n if (editActions && !readHandlers?.isPanelViewed) {\n return (\n <InfoTooltip description={TOOLTIP_TEXT.movePanel}>\n <HeaderIconButton aria-label={ARIA_LABEL_TEXT.movePanel(title)} size=\"small\">\n <DragIcon className=\"drag-handle\" sx={{ cursor: 'grab' }} fontSize=\"inherit\" />\n </HeaderIconButton>\n </InfoTooltip>\n );\n }\n return undefined;\n }, [editActions, readHandlers, title]);\n\n const divider = <Box sx={{ flexGrow: 1 }}></Box>;\n\n // if the panel is in non-editing, non-fullscreen mode, show certain icons only on hover\n const OnHover = ({ children }: PropsWithChildren): ReactNode =>\n editHandlers === undefined && !readHandlers?.isPanelViewed ? (\n <Box sx={{ display: 'var(--panel-hover, none)' }}>{children}</Box>\n ) : (\n <>{children}</>\n );\n\n return (\n <>\n {/* small panel width: move all icons except move/grab to overflow menu */}\n <ConditionalBox\n sx={(theme) => ({\n [theme.containerQueries(HEADER_ACTIONS_CONTAINER_NAME).between(0, HEADER_SMALL_WIDTH)]: { display: 'flex' },\n })}\n >\n {divider}\n <OnHover>\n <OverflowMenu title={title}>\n {descriptionAction} {linksAction} {queryStateIndicator} {extraActions} {readActions} {editActions}\n </OverflowMenu>\n {moveAction}\n </OnHover>\n </ConditionalBox>\n\n {/* medium panel width: move edit icons to overflow menu */}\n <ConditionalBox\n sx={(theme) => ({\n [theme.containerQueries(HEADER_ACTIONS_CONTAINER_NAME).between(HEADER_SMALL_WIDTH, HEADER_MEDIUM_WIDTH)]: {\n display: 'flex',\n },\n })}\n >\n <OnHover>\n {descriptionAction} {linksAction}\n </OnHover>\n {divider} {queryStateIndicator}\n <OnHover>\n {extraActions} {readActions}\n <OverflowMenu title={title}>{editActions}</OverflowMenu>\n {moveAction}\n </OnHover>\n </ConditionalBox>\n\n {/* large panel width: show all icons in panel header */}\n <ConditionalBox\n sx={(theme) => ({\n // flip the logic here; if the browser (or jsdom) does not support container queries, always show all icons\n display: 'flex',\n [theme.containerQueries(HEADER_ACTIONS_CONTAINER_NAME).down(HEADER_MEDIUM_WIDTH)]: { display: 'none' },\n })}\n >\n <OnHover>\n {descriptionAction} {linksAction}\n </OnHover>\n {divider} {queryStateIndicator}\n <OnHover>\n {extraActions} {readActions} {editActions} {moveAction}\n </OnHover>\n </ConditionalBox>\n </>\n );\n};\n\nconst OverflowMenu: React.FC<PropsWithChildren<{ title: string }>> = ({ children, title }) => {\n const [anchorPosition, setAnchorPosition] = useState<PopoverPosition>();\n\n // do not show overflow menu if there is no content (for example, edit actions are hidden)\n const hasContent = isValidElement(children) || (Array.isArray(children) && children.some(isValidElement));\n if (!hasContent) {\n return undefined;\n }\n\n const handleClick = (event: React.MouseEvent<HTMLElement>): undefined => {\n setAnchorPosition(event.currentTarget.getBoundingClientRect());\n };\n\n const handleClose = (): undefined => {\n setAnchorPosition(undefined);\n };\n\n const open = Boolean(anchorPosition);\n const id = open ? 'actions-menu' : undefined;\n\n return (\n <>\n <HeaderIconButton\n className=\"show-actions\"\n aria-describedby={id}\n onClick={handleClick}\n aria-label={ARIA_LABEL_TEXT.showPanelActions(title)}\n size=\"small\"\n >\n <MenuIcon fontSize=\"inherit\" />\n </HeaderIconButton>\n <Popover\n id={id}\n open={open}\n anchorReference=\"anchorPosition\"\n anchorPosition={anchorPosition}\n onClose={handleClose}\n anchorOrigin={{\n vertical: 'bottom',\n horizontal: 'left',\n }}\n >\n <Stack direction=\"row\" alignItems=\"center\" sx={{ padding: 1 }} onClick={handleClose}>\n {children}\n </Stack>\n </Popover>\n </>\n );\n};\n"],"names":["Stack","Box","Popover","CircularProgress","styled","isValidElement","useMemo","useState","InfoTooltip","ArrowCollapseIcon","ArrowExpandIcon","PencilIcon","DeleteIcon","DragIcon","ContentCopyIcon","MenuIcon","AlertIcon","InformationOutlineIcon","ARIA_LABEL_TEXT","HEADER_ACTIONS_CONTAINER_NAME","HEADER_MEDIUM_WIDTH","HEADER_SMALL_WIDTH","TOOLTIP_TEXT","HeaderIconButton","PanelLinks","ConditionalBox","display","alignItems","flexGrow","justifyContent","PanelActions","editHandlers","readHandlers","extra","title","description","descriptionTooltipId","links","queryResults","descriptionAction","trim","length","id","enterDelay","aria-label","size","aria-describedby","aria-hidden","fontSize","sx","color","theme","palette","text","secondary","undefined","linksAction","extraActions","queryStateIndicator","hasData","some","q","data","isFetching","queryErrors","filter","error","errorTexts","map","e","message","toString","join","readActions","viewPanel","onClick","onViewPanelClick","isPanelViewed","editActions","editPanel","onEditPanelClick","duplicatePanel","onDuplicatePanelClick","transform","deletePanel","onDeletePanelClick","moveAction","movePanel","className","cursor","divider","OnHover","children","containerQueries","between","OverflowMenu","down","anchorPosition","setAnchorPosition","hasContent","Array","isArray","handleClick","event","currentTarget","getBoundingClientRect","handleClose","open","Boolean","showPanelActions","anchorReference","onClose","anchorOrigin","vertical","horizontal","direction","padding"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/Panel/PanelActions.tsx"],"sourcesContent":["// Copyright 2025 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { Stack, Box, Popover, CircularProgress, styled, PopoverPosition } from '@mui/material';\nimport { isValidElement, PropsWithChildren, ReactNode, useMemo, useState } from 'react';\nimport { InfoTooltip } from '@perses-dev/components';\nimport { QueryData } from '@perses-dev/plugin-system';\nimport ArrowCollapseIcon from 'mdi-material-ui/ArrowCollapse';\nimport ArrowExpandIcon from 'mdi-material-ui/ArrowExpand';\nimport PencilIcon from 'mdi-material-ui/PencilOutline';\nimport DeleteIcon from 'mdi-material-ui/DeleteOutline';\nimport DragIcon from 'mdi-material-ui/DragVertical';\nimport ContentCopyIcon from 'mdi-material-ui/ContentCopy';\nimport MenuIcon from 'mdi-material-ui/Menu';\nimport AlertIcon from 'mdi-material-ui/Alert';\nimport InformationOutlineIcon from 'mdi-material-ui/InformationOutline';\nimport { Link } from '@perses-dev/core';\nimport {\n ARIA_LABEL_TEXT,\n HEADER_ACTIONS_CONTAINER_NAME,\n HEADER_MEDIUM_WIDTH,\n HEADER_SMALL_WIDTH,\n TOOLTIP_TEXT,\n} from '../../constants';\nimport { HeaderIconButton } from './HeaderIconButton';\nimport { PanelLinks } from './PanelLinks';\n\nexport interface PanelActionsProps {\n title: string;\n description?: string;\n descriptionTooltipId: string;\n links?: Link[];\n extra?: React.ReactNode;\n editHandlers?: {\n onEditPanelClick: () => void;\n onDuplicatePanelClick: () => void;\n onDeletePanelClick: () => void;\n };\n readHandlers?: {\n isPanelViewed?: boolean;\n onViewPanelClick: () => void;\n };\n queryResults: QueryData[];\n pluginActions?: ReactNode[];\n}\n\nconst ConditionalBox = styled(Box)({\n display: 'none',\n alignItems: 'center',\n flexGrow: 1,\n justifyContent: 'flex-end',\n});\n\nexport const PanelActions: React.FC<PanelActionsProps> = ({\n editHandlers,\n readHandlers,\n extra,\n title,\n description,\n descriptionTooltipId,\n links,\n queryResults,\n pluginActions = [],\n}) => {\n const descriptionAction = useMemo((): ReactNode | undefined => {\n if (description && description.trim().length > 0) {\n return (\n <InfoTooltip id={descriptionTooltipId} description={description} enterDelay={100}>\n <HeaderIconButton aria-label=\"panel description\" size=\"small\">\n <InformationOutlineIcon\n aria-describedby=\"info-tooltip\"\n aria-hidden={false}\n fontSize=\"inherit\"\n sx={{ color: (theme) => theme.palette.text.secondary }}\n />\n </HeaderIconButton>\n </InfoTooltip>\n );\n }\n return undefined;\n }, [descriptionTooltipId, description]);\n\n const linksAction = links && links.length > 0 && <PanelLinks links={links} />;\n const extraActions = editHandlers === undefined && extra;\n\n const queryStateIndicator = useMemo((): ReactNode | undefined => {\n const hasData = queryResults.some((q) => q.data);\n const isFetching = queryResults.some((q) => q.isFetching);\n const queryErrors = queryResults.filter((q) => q.error);\n\n if (isFetching && hasData) {\n return <CircularProgress aria-label=\"loading\" size=\"1.125rem\" />;\n } else if (queryErrors.length > 0) {\n const errorTexts = queryErrors\n .map((q) => q.error)\n .map((e: any) => e?.message ?? e?.toString() ?? 'Unknown error') // eslint-disable-line @typescript-eslint/no-explicit-any\n .join('\\n');\n\n return (\n <InfoTooltip description={errorTexts}>\n <HeaderIconButton aria-label=\"panel errors\" size=\"small\">\n <AlertIcon fontSize=\"inherit\" />\n </HeaderIconButton>\n </InfoTooltip>\n );\n }\n }, [queryResults]);\n\n const readActions = useMemo((): ReactNode | undefined => {\n if (readHandlers !== undefined) {\n return (\n <InfoTooltip description={TOOLTIP_TEXT.viewPanel}>\n <HeaderIconButton\n aria-label={ARIA_LABEL_TEXT.viewPanel(title)}\n size=\"small\"\n onClick={readHandlers.onViewPanelClick}\n >\n {readHandlers.isPanelViewed ? (\n <ArrowCollapseIcon fontSize=\"inherit\" />\n ) : (\n <ArrowExpandIcon fontSize=\"inherit\" />\n )}\n </HeaderIconButton>\n </InfoTooltip>\n );\n }\n return undefined;\n }, [readHandlers, title]);\n\n const editActions = useMemo((): ReactNode | undefined => {\n if (editHandlers !== undefined) {\n // If there are edit handlers, always just show the edit buttons\n return (\n <>\n <InfoTooltip description={TOOLTIP_TEXT.editPanel}>\n <HeaderIconButton\n aria-label={ARIA_LABEL_TEXT.editPanel(title)}\n size=\"small\"\n onClick={editHandlers.onEditPanelClick}\n >\n <PencilIcon fontSize=\"inherit\" />\n </HeaderIconButton>\n </InfoTooltip>\n <InfoTooltip description={TOOLTIP_TEXT.duplicatePanel}>\n <HeaderIconButton\n aria-label={ARIA_LABEL_TEXT.duplicatePanel(title)}\n size=\"small\"\n onClick={editHandlers.onDuplicatePanelClick}\n >\n <ContentCopyIcon\n fontSize=\"inherit\"\n sx={{\n // Shrink this icon a little bit to look more consistent\n // with the other icons in the header.\n transform: 'scale(0.925)',\n }}\n />\n </HeaderIconButton>\n </InfoTooltip>\n <InfoTooltip description={TOOLTIP_TEXT.deletePanel}>\n <HeaderIconButton\n aria-label={ARIA_LABEL_TEXT.deletePanel(title)}\n size=\"small\"\n onClick={editHandlers.onDeletePanelClick}\n >\n <DeleteIcon fontSize=\"inherit\" />\n </HeaderIconButton>\n </InfoTooltip>\n </>\n );\n }\n return undefined;\n }, [editHandlers, title]);\n\n const moveAction = useMemo((): ReactNode | undefined => {\n if (editActions && !readHandlers?.isPanelViewed) {\n return (\n <InfoTooltip description={TOOLTIP_TEXT.movePanel}>\n <HeaderIconButton aria-label={ARIA_LABEL_TEXT.movePanel(title)} size=\"small\">\n <DragIcon className=\"drag-handle\" sx={{ cursor: 'grab' }} fontSize=\"inherit\" />\n </HeaderIconButton>\n </InfoTooltip>\n );\n }\n return undefined;\n }, [editActions, readHandlers, title]);\n\n const divider = <Box sx={{ flexGrow: 1 }}></Box>;\n\n // if the panel is in non-editing, non-fullscreen mode, show certain icons only on hover\n const OnHover = ({ children }: PropsWithChildren): ReactNode =>\n editHandlers === undefined && !readHandlers?.isPanelViewed ? (\n <Box sx={{ display: 'var(--panel-hover, none)' }}>{children}</Box>\n ) : (\n <>{children}</>\n );\n\n return (\n <>\n {/* small panel width: move all icons except move/grab to overflow menu */}\n <ConditionalBox\n sx={(theme) => ({\n [theme.containerQueries(HEADER_ACTIONS_CONTAINER_NAME).between(0, HEADER_SMALL_WIDTH)]: { display: 'flex' },\n })}\n >\n {divider}\n <OnHover>\n <OverflowMenu title={title}>\n {descriptionAction} {linksAction} {queryStateIndicator} {extraActions} {readActions} {editActions}\n </OverflowMenu>\n {pluginActions}\n {moveAction}\n </OnHover>\n </ConditionalBox>\n\n {/* medium panel width: move edit icons to overflow menu */}\n <ConditionalBox\n sx={(theme) => ({\n [theme.containerQueries(HEADER_ACTIONS_CONTAINER_NAME).between(HEADER_SMALL_WIDTH, HEADER_MEDIUM_WIDTH)]: {\n display: 'flex',\n },\n })}\n >\n <OnHover>\n {descriptionAction} {linksAction}\n </OnHover>\n {divider} {queryStateIndicator}\n <OnHover>\n {extraActions} {readActions} {pluginActions}\n <OverflowMenu title={title}>{editActions}</OverflowMenu>\n {moveAction}\n </OnHover>\n </ConditionalBox>\n\n {/* large panel width: show all icons in panel header */}\n <ConditionalBox\n sx={(theme) => ({\n // flip the logic here; if the browser (or jsdom) does not support container queries, always show all icons\n display: 'flex',\n [theme.containerQueries(HEADER_ACTIONS_CONTAINER_NAME).down(HEADER_MEDIUM_WIDTH)]: { display: 'none' },\n })}\n >\n <OnHover>\n {descriptionAction} {linksAction}\n </OnHover>\n {divider} {queryStateIndicator}\n <OnHover>\n {extraActions} {readActions} {pluginActions} {editActions} {moveAction}\n </OnHover>\n </ConditionalBox>\n </>\n );\n};\n\nconst OverflowMenu: React.FC<PropsWithChildren<{ title: string }>> = ({ children, title }) => {\n const [anchorPosition, setAnchorPosition] = useState<PopoverPosition>();\n\n // do not show overflow menu if there is no content (for example, edit actions are hidden)\n const hasContent = isValidElement(children) || (Array.isArray(children) && children.some(isValidElement));\n if (!hasContent) {\n return undefined;\n }\n\n const handleClick = (event: React.MouseEvent<HTMLElement>): void => {\n setAnchorPosition(event.currentTarget.getBoundingClientRect());\n };\n\n const handleClose = (): void => {\n setAnchorPosition(undefined);\n };\n\n const open = Boolean(anchorPosition);\n const id = open ? 'actions-menu' : undefined;\n\n return (\n <>\n <HeaderIconButton\n className=\"show-actions\"\n aria-describedby={id}\n onClick={handleClick}\n aria-label={ARIA_LABEL_TEXT.showPanelActions(title)}\n size=\"small\"\n >\n <MenuIcon fontSize=\"inherit\" />\n </HeaderIconButton>\n <Popover\n id={id}\n open={open}\n anchorReference=\"anchorPosition\"\n anchorPosition={anchorPosition}\n onClose={handleClose}\n anchorOrigin={{\n vertical: 'bottom',\n horizontal: 'left',\n }}\n >\n <Stack direction=\"row\" alignItems=\"center\" sx={{ padding: 1 }} onClick={handleClose}>\n {children}\n </Stack>\n </Popover>\n </>\n );\n};\n"],"names":["Stack","Box","Popover","CircularProgress","styled","isValidElement","useMemo","useState","InfoTooltip","ArrowCollapseIcon","ArrowExpandIcon","PencilIcon","DeleteIcon","DragIcon","ContentCopyIcon","MenuIcon","AlertIcon","InformationOutlineIcon","ARIA_LABEL_TEXT","HEADER_ACTIONS_CONTAINER_NAME","HEADER_MEDIUM_WIDTH","HEADER_SMALL_WIDTH","TOOLTIP_TEXT","HeaderIconButton","PanelLinks","ConditionalBox","display","alignItems","flexGrow","justifyContent","PanelActions","editHandlers","readHandlers","extra","title","description","descriptionTooltipId","links","queryResults","pluginActions","descriptionAction","trim","length","id","enterDelay","aria-label","size","aria-describedby","aria-hidden","fontSize","sx","color","theme","palette","text","secondary","undefined","linksAction","extraActions","queryStateIndicator","hasData","some","q","data","isFetching","queryErrors","filter","error","errorTexts","map","e","message","toString","join","readActions","viewPanel","onClick","onViewPanelClick","isPanelViewed","editActions","editPanel","onEditPanelClick","duplicatePanel","onDuplicatePanelClick","transform","deletePanel","onDeletePanelClick","moveAction","movePanel","className","cursor","divider","OnHover","children","containerQueries","between","OverflowMenu","down","anchorPosition","setAnchorPosition","hasContent","Array","isArray","handleClick","event","currentTarget","getBoundingClientRect","handleClose","open","Boolean","showPanelActions","anchorReference","onClose","anchorOrigin","vertical","horizontal","direction","padding"],"mappings":"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"}
@@ -13,7 +13,8 @@ export interface PanelHeaderProps extends Omit<CardHeaderProps, OmittedProps> {
13
13
  queryResults: QueryData[];
14
14
  readHandlers?: PanelActionsProps['readHandlers'];
15
15
  editHandlers?: PanelActionsProps['editHandlers'];
16
+ pluginActions?: ReactNode[];
16
17
  }
17
- export declare function PanelHeader({ id, title: rawTitle, description: rawDescription, links, queryResults, readHandlers, editHandlers, sx, extra, ...rest }: PanelHeaderProps): ReactElement;
18
+ export declare function PanelHeader({ id, title: rawTitle, description: rawDescription, links, queryResults, readHandlers, editHandlers, sx, extra, pluginActions, ...rest }: PanelHeaderProps): ReactElement;
18
19
  export {};
19
20
  //# sourceMappingURL=PanelHeader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PanelHeader.d.ts","sourceRoot":"","sources":["../../../src/components/Panel/PanelHeader.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAc,eAAe,EAAqB,MAAM,eAAe,CAAC;AAE/E,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,SAAS,EAA+B,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEhD,OAAO,EAAgB,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEjE,KAAK,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,mBAAmB,CAAC;AAE1E,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,eAAe,EAAE,YAAY,CAAC;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,YAAY,EAAE,SAAS,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC;IACjD,YAAY,CAAC,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC;CAClD;AAED,wBAAgB,WAAW,CAAC,EAC1B,EAAE,EACF,KAAK,EAAE,QAAQ,EACf,WAAW,EAAE,cAAc,EAC3B,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,EAAE,EACF,KAAK,EACL,GAAG,IAAI,EACR,EAAE,gBAAgB,GAAG,YAAY,CA0DjC"}
1
+ {"version":3,"file":"PanelHeader.d.ts","sourceRoot":"","sources":["../../../src/components/Panel/PanelHeader.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAc,eAAe,EAAqB,MAAM,eAAe,CAAC;AAE/E,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,SAAS,EAA+B,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEhD,OAAO,EAAgB,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEjE,KAAK,YAAY,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,mBAAmB,CAAC;AAE1E,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,eAAe,EAAE,YAAY,CAAC;IAC3E,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,YAAY,EAAE,SAAS,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC;IACjD,YAAY,CAAC,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAC;IACjD,aAAa,CAAC,EAAE,SAAS,EAAE,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,EAC1B,EAAE,EACF,KAAK,EAAE,QAAQ,EACf,WAAW,EAAE,cAAc,EAC3B,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,EAAE,EACF,KAAK,EACL,aAAa,EACb,GAAG,IAAI,EACR,EAAE,gBAAgB,GAAG,YAAY,CA2DjC"}
@@ -16,7 +16,7 @@ import { combineSx } from '@perses-dev/components';
16
16
  import { useReplaceVariablesInString } from '@perses-dev/plugin-system';
17
17
  import { HEADER_ACTIONS_CONTAINER_NAME } from '../../constants';
18
18
  import { PanelActions } from './PanelActions';
19
- export function PanelHeader({ id, title: rawTitle, description: rawDescription, links, queryResults, readHandlers, editHandlers, sx, extra, ...rest }) {
19
+ export function PanelHeader({ id, title: rawTitle, description: rawDescription, links, queryResults, readHandlers, editHandlers, sx, extra, pluginActions, ...rest }) {
20
20
  const titleElementId = `${id}-title`;
21
21
  const descriptionTooltipId = `${id}-description`;
22
22
  const title = useReplaceVariablesInString(rawTitle);
@@ -49,10 +49,11 @@ export function PanelHeader({ id, title: rawTitle, description: rawDescription,
49
49
  description: description,
50
50
  descriptionTooltipId: descriptionTooltipId,
51
51
  links: links,
52
- queryResults: queryResults,
53
52
  readHandlers: readHandlers,
54
53
  editHandlers: editHandlers,
55
- extra: extra
54
+ extra: extra,
55
+ queryResults: queryResults,
56
+ pluginActions: pluginActions
56
57
  })
57
58
  ]
58
59
  }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/Panel/PanelHeader.tsx"],"sourcesContent":["// Copyright 2025 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { CardHeader, CardHeaderProps, Stack, Typography } from '@mui/material';\nimport { combineSx } from '@perses-dev/components';\nimport { Link } from '@perses-dev/core';\nimport { QueryData, useReplaceVariablesInString } from '@perses-dev/plugin-system';\nimport { ReactElement, ReactNode } from 'react';\nimport { HEADER_ACTIONS_CONTAINER_NAME } from '../../constants';\nimport { PanelActions, PanelActionsProps } from './PanelActions';\n\ntype OmittedProps = 'children' | 'action' | 'title' | 'disableTypography';\n\nexport interface PanelHeaderProps extends Omit<CardHeaderProps, OmittedProps> {\n id: string;\n title: string;\n description?: string;\n links?: Link[];\n extra?: ReactNode;\n queryResults: QueryData[];\n readHandlers?: PanelActionsProps['readHandlers'];\n editHandlers?: PanelActionsProps['editHandlers'];\n}\n\nexport function PanelHeader({\n id,\n title: rawTitle,\n description: rawDescription,\n links,\n queryResults,\n readHandlers,\n editHandlers,\n sx,\n extra,\n ...rest\n}: PanelHeaderProps): ReactElement {\n const titleElementId = `${id}-title`;\n const descriptionTooltipId = `${id}-description`;\n\n const title = useReplaceVariablesInString(rawTitle) as string;\n const description = useReplaceVariablesInString(rawDescription);\n\n return (\n <CardHeader\n id={id}\n component=\"header\"\n aria-labelledby={titleElementId}\n aria-describedby={descriptionTooltipId}\n disableTypography\n title={\n <Stack direction=\"row\">\n <Typography\n id={titleElementId}\n variant=\"subtitle1\"\n sx={{\n // `minHeight` guarantees that the header has the correct height\n // when there is no title (i.e. in the preview)\n lineHeight: '24px',\n minHeight: '26px',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {title}\n </Typography>\n <PanelActions\n title={title}\n description={description}\n descriptionTooltipId={descriptionTooltipId}\n links={links}\n queryResults={queryResults}\n readHandlers={readHandlers}\n editHandlers={editHandlers}\n extra={extra}\n />\n </Stack>\n }\n sx={combineSx(\n (theme) => ({\n containerType: 'inline-size',\n containerName: HEADER_ACTIONS_CONTAINER_NAME,\n padding: theme.spacing(1),\n borderBottom: `solid 1px ${theme.palette.divider}`,\n '.MuiCardHeader-content': {\n overflow: 'hidden',\n },\n }),\n sx\n )}\n {...rest}\n />\n );\n}\n"],"names":["CardHeader","Stack","Typography","combineSx","useReplaceVariablesInString","HEADER_ACTIONS_CONTAINER_NAME","PanelActions","PanelHeader","id","title","rawTitle","description","rawDescription","links","queryResults","readHandlers","editHandlers","sx","extra","rest","titleElementId","descriptionTooltipId","component","aria-labelledby","aria-describedby","disableTypography","direction","variant","lineHeight","minHeight","whiteSpace","overflow","textOverflow","theme","containerType","containerName","padding","spacing","borderBottom","palette","divider"],"mappings":"AAAA,oCAAoC;AACpC,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;;AAEjC,SAASA,UAAU,EAAmBC,KAAK,EAAEC,UAAU,QAAQ,gBAAgB;AAC/E,SAASC,SAAS,QAAQ,yBAAyB;AAEnD,SAAoBC,2BAA2B,QAAQ,4BAA4B;AAEnF,SAASC,6BAA6B,QAAQ,kBAAkB;AAChE,SAASC,YAAY,QAA2B,iBAAiB;AAejE,OAAO,SAASC,YAAY,EAC1BC,EAAE,EACFC,OAAOC,QAAQ,EACfC,aAAaC,cAAc,EAC3BC,KAAK,EACLC,YAAY,EACZC,YAAY,EACZC,YAAY,EACZC,EAAE,EACFC,KAAK,EACL,GAAGC,MACc;IACjB,MAAMC,iBAAiB,GAAGZ,GAAG,MAAM,CAAC;IACpC,MAAMa,uBAAuB,GAAGb,GAAG,YAAY,CAAC;IAEhD,MAAMC,QAAQL,4BAA4BM;IAC1C,MAAMC,cAAcP,4BAA4BQ;IAEhD,qBACE,KAACZ;QACCQ,IAAIA;QACJc,WAAU;QACVC,mBAAiBH;QACjBI,oBAAkBH;QAClBI,iBAAiB;QACjBhB,qBACE,MAACR;YAAMyB,WAAU;;8BACf,KAACxB;oBACCM,IAAIY;oBACJO,SAAQ;oBACRV,IAAI;wBACF,gEAAgE;wBAChE,+CAA+C;wBAC/CW,YAAY;wBACZC,WAAW;wBACXC,YAAY;wBACZC,UAAU;wBACVC,cAAc;oBAChB;8BAECvB;;8BAEH,KAACH;oBACCG,OAAOA;oBACPE,aAAaA;oBACbU,sBAAsBA;oBACtBR,OAAOA;oBACPC,cAAcA;oBACdC,cAAcA;oBACdC,cAAcA;oBACdE,OAAOA;;;;QAIbD,IAAId,UACF,CAAC8B,QAAW,CAAA;gBACVC,eAAe;gBACfC,eAAe9B;gBACf+B,SAASH,MAAMI,OAAO,CAAC;gBACvBC,cAAc,CAAC,UAAU,EAAEL,MAAMM,OAAO,CAACC,OAAO,EAAE;gBAClD,0BAA0B;oBACxBT,UAAU;gBACZ;YACF,CAAA,GACAd;QAED,GAAGE,IAAI;;AAGd"}
1
+ {"version":3,"sources":["../../../src/components/Panel/PanelHeader.tsx"],"sourcesContent":["// Copyright 2025 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { CardHeader, CardHeaderProps, Stack, Typography } from '@mui/material';\nimport { combineSx } from '@perses-dev/components';\nimport { Link } from '@perses-dev/core';\nimport { QueryData, useReplaceVariablesInString } from '@perses-dev/plugin-system';\nimport { ReactElement, ReactNode } from 'react';\nimport { HEADER_ACTIONS_CONTAINER_NAME } from '../../constants';\nimport { PanelActions, PanelActionsProps } from './PanelActions';\n\ntype OmittedProps = 'children' | 'action' | 'title' | 'disableTypography';\n\nexport interface PanelHeaderProps extends Omit<CardHeaderProps, OmittedProps> {\n id: string;\n title: string;\n description?: string;\n links?: Link[];\n extra?: ReactNode;\n queryResults: QueryData[];\n readHandlers?: PanelActionsProps['readHandlers'];\n editHandlers?: PanelActionsProps['editHandlers'];\n pluginActions?: ReactNode[]; // Add pluginActions prop\n}\n\nexport function PanelHeader({\n id,\n title: rawTitle,\n description: rawDescription,\n links,\n queryResults,\n readHandlers,\n editHandlers,\n sx,\n extra,\n pluginActions,\n ...rest\n}: PanelHeaderProps): ReactElement {\n const titleElementId = `${id}-title`;\n const descriptionTooltipId = `${id}-description`;\n\n const title = useReplaceVariablesInString(rawTitle) as string;\n const description = useReplaceVariablesInString(rawDescription);\n\n return (\n <CardHeader\n id={id}\n component=\"header\"\n aria-labelledby={titleElementId}\n aria-describedby={descriptionTooltipId}\n disableTypography\n title={\n <Stack direction=\"row\">\n <Typography\n id={titleElementId}\n variant=\"subtitle1\"\n sx={{\n // `minHeight` guarantees that the header has the correct height\n // when there is no title (i.e. in the preview)\n lineHeight: '24px',\n minHeight: '26px',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {title}\n </Typography>\n <PanelActions\n title={title}\n description={description}\n descriptionTooltipId={descriptionTooltipId}\n links={links}\n readHandlers={readHandlers}\n editHandlers={editHandlers}\n extra={extra}\n queryResults={queryResults}\n pluginActions={pluginActions}\n />\n </Stack>\n }\n sx={combineSx(\n (theme) => ({\n containerType: 'inline-size',\n containerName: HEADER_ACTIONS_CONTAINER_NAME,\n padding: theme.spacing(1),\n borderBottom: `solid 1px ${theme.palette.divider}`,\n '.MuiCardHeader-content': {\n overflow: 'hidden',\n },\n }),\n sx\n )}\n {...rest}\n />\n );\n}\n"],"names":["CardHeader","Stack","Typography","combineSx","useReplaceVariablesInString","HEADER_ACTIONS_CONTAINER_NAME","PanelActions","PanelHeader","id","title","rawTitle","description","rawDescription","links","queryResults","readHandlers","editHandlers","sx","extra","pluginActions","rest","titleElementId","descriptionTooltipId","component","aria-labelledby","aria-describedby","disableTypography","direction","variant","lineHeight","minHeight","whiteSpace","overflow","textOverflow","theme","containerType","containerName","padding","spacing","borderBottom","palette","divider"],"mappings":"AAAA,oCAAoC;AACpC,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;;AAEjC,SAASA,UAAU,EAAmBC,KAAK,EAAEC,UAAU,QAAQ,gBAAgB;AAC/E,SAASC,SAAS,QAAQ,yBAAyB;AAEnD,SAAoBC,2BAA2B,QAAQ,4BAA4B;AAEnF,SAASC,6BAA6B,QAAQ,kBAAkB;AAChE,SAASC,YAAY,QAA2B,iBAAiB;AAgBjE,OAAO,SAASC,YAAY,EAC1BC,EAAE,EACFC,OAAOC,QAAQ,EACfC,aAAaC,cAAc,EAC3BC,KAAK,EACLC,YAAY,EACZC,YAAY,EACZC,YAAY,EACZC,EAAE,EACFC,KAAK,EACLC,aAAa,EACb,GAAGC,MACc;IACjB,MAAMC,iBAAiB,GAAGb,GAAG,MAAM,CAAC;IACpC,MAAMc,uBAAuB,GAAGd,GAAG,YAAY,CAAC;IAEhD,MAAMC,QAAQL,4BAA4BM;IAC1C,MAAMC,cAAcP,4BAA4BQ;IAEhD,qBACE,KAACZ;QACCQ,IAAIA;QACJe,WAAU;QACVC,mBAAiBH;QACjBI,oBAAkBH;QAClBI,iBAAiB;QACjBjB,qBACE,MAACR;YAAM0B,WAAU;;8BACf,KAACzB;oBACCM,IAAIa;oBACJO,SAAQ;oBACRX,IAAI;wBACF,gEAAgE;wBAChE,+CAA+C;wBAC/CY,YAAY;wBACZC,WAAW;wBACXC,YAAY;wBACZC,UAAU;wBACVC,cAAc;oBAChB;8BAECxB;;8BAEH,KAACH;oBACCG,OAAOA;oBACPE,aAAaA;oBACbW,sBAAsBA;oBACtBT,OAAOA;oBACPE,cAAcA;oBACdC,cAAcA;oBACdE,OAAOA;oBACPJ,cAAcA;oBACdK,eAAeA;;;;QAIrBF,IAAId,UACF,CAAC+B,QAAW,CAAA;gBACVC,eAAe;gBACfC,eAAe/B;gBACfgC,SAASH,MAAMI,OAAO,CAAC;gBACvBC,cAAc,CAAC,UAAU,EAAEL,MAAMM,OAAO,CAACC,OAAO,EAAE;gBAClD,0BAA0B;oBACxBT,UAAU;gBACZ;YACF,CAAA,GACAf;QAED,GAAGG,IAAI;;AAGd"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perses-dev/dashboards",
3
- "version": "0.51.1",
3
+ "version": "0.52.0-beta.1",
4
4
  "description": "The dashboards feature in Perses",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/perses/perses/blob/main/README.md",
@@ -29,9 +29,9 @@
29
29
  "lint:fix": "eslint --fix src --ext .ts,.tsx"
30
30
  },
31
31
  "dependencies": {
32
- "@perses-dev/components": "0.51.1",
33
- "@perses-dev/core": "0.51.1",
34
- "@perses-dev/plugin-system": "0.51.1",
32
+ "@perses-dev/components": "0.52.0-beta.1",
33
+ "@perses-dev/core": "0.52.0-beta.1",
34
+ "@perses-dev/plugin-system": "0.52.0-beta.1",
35
35
  "@types/react-grid-layout": "^1.3.2",
36
36
  "date-fns": "^4.1.0",
37
37
  "immer": "^10.1.1",
@@ -46,7 +46,7 @@
46
46
  "zustand": "^4.3.3"
47
47
  },
48
48
  "devDependencies": {
49
- "@perses-dev/internal-utils": "0.51.1",
49
+ "@perses-dev/internal-utils": "0.52.0-beta.1",
50
50
  "history": "^5.3.0",
51
51
  "intersection-observer": "^0.12.2"
52
52
  },