PanelHeader.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { css, cx } from '@emotion/css';
  2. import React, { FC } from 'react';
  3. import { DataLink, GrafanaTheme2, PanelData } from '@grafana/data';
  4. import { selectors } from '@grafana/e2e-selectors';
  5. import { Icon, useStyles2, ClickOutsideWrapper } from '@grafana/ui';
  6. import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
  7. import { PanelModel } from 'app/features/dashboard/state/PanelModel';
  8. import { getPanelLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
  9. import PanelHeaderCorner from './PanelHeaderCorner';
  10. import { PanelHeaderLoadingIndicator } from './PanelHeaderLoadingIndicator';
  11. import { PanelHeaderMenuTrigger } from './PanelHeaderMenuTrigger';
  12. import { PanelHeaderMenuWrapper } from './PanelHeaderMenuWrapper';
  13. import { PanelHeaderNotices } from './PanelHeaderNotices';
  14. export interface Props {
  15. panel: PanelModel;
  16. dashboard: DashboardModel;
  17. title?: string;
  18. description?: string;
  19. links?: DataLink[];
  20. error?: string;
  21. alertState?: string;
  22. isViewing: boolean;
  23. isEditing: boolean;
  24. data: PanelData;
  25. }
  26. export const PanelHeader: FC<Props> = ({ panel, error, isViewing, isEditing, data, alertState, dashboard }) => {
  27. const onCancelQuery = () => panel.getQueryRunner().cancelQuery();
  28. const title = panel.getDisplayTitle();
  29. const className = cx('panel-header', !(isViewing || isEditing) ? 'grid-drag-handle' : '');
  30. const styles = useStyles2(panelStyles);
  31. return (
  32. <>
  33. <PanelHeaderLoadingIndicator state={data.state} onClick={onCancelQuery} />
  34. <PanelHeaderCorner
  35. panel={panel}
  36. title={panel.title}
  37. description={panel.description}
  38. scopedVars={panel.scopedVars}
  39. links={getPanelLinksSupplier(panel)}
  40. error={error}
  41. />
  42. <div className={className}>
  43. <PanelHeaderMenuTrigger data-testid={selectors.components.Panels.Panel.title(title)}>
  44. {({ closeMenu, panelMenuOpen }) => {
  45. return (
  46. <ClickOutsideWrapper onClick={closeMenu} parent={document}>
  47. <div className="panel-title">
  48. <PanelHeaderNotices frames={data.series} panelId={panel.id} />
  49. {alertState ? (
  50. <Icon
  51. name={alertState === 'alerting' ? 'heart-break' : 'heart'}
  52. className="icon-gf panel-alert-icon"
  53. style={{ marginRight: '4px' }}
  54. size="sm"
  55. />
  56. ) : null}
  57. <h2 className={styles.titleText}>{title}</h2>
  58. <Icon name="angle-down" className="panel-menu-toggle" />
  59. {panelMenuOpen ? (
  60. <PanelHeaderMenuWrapper panel={panel} dashboard={dashboard} onClose={closeMenu} />
  61. ) : null}
  62. {data.request && data.request.timeInfo && (
  63. <span className="panel-time-info">
  64. <Icon name="clock-nine" size="sm" /> {data.request.timeInfo}
  65. </span>
  66. )}
  67. </div>
  68. </ClickOutsideWrapper>
  69. );
  70. }}
  71. </PanelHeaderMenuTrigger>
  72. </div>
  73. </>
  74. );
  75. };
  76. const panelStyles = (theme: GrafanaTheme2) => {
  77. return {
  78. titleText: css`
  79. text-overflow: ellipsis;
  80. overflow: hidden;
  81. white-space: nowrap;
  82. max-width: calc(100% - 38px);
  83. cursor: pointer;
  84. font-weight: ${theme.typography.fontWeightMedium};
  85. font-size: ${theme.typography.body.fontSize};
  86. margin: 0;
  87. &:hover {
  88. color: ${theme.colors.text.primary};
  89. }
  90. .panel-has-alert & {
  91. max-width: calc(100% - 54px);
  92. }
  93. `,
  94. };
  95. };