LogsVolumePanel.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { css } from '@emotion/css';
  2. import React, { useState } from 'react';
  3. import { AbsoluteTimeRange, DataQueryError, DataQueryResponse, LoadingState, SplitOpen, TimeZone } from '@grafana/data';
  4. import { Alert, Button, Collapse, InlineField, TooltipDisplayMode, useStyles2, useTheme2 } from '@grafana/ui';
  5. import { ExploreGraph } from './ExploreGraph';
  6. type Props = {
  7. logsVolumeData?: DataQueryResponse;
  8. absoluteRange: AbsoluteTimeRange;
  9. timeZone: TimeZone;
  10. splitOpen: SplitOpen;
  11. width: number;
  12. onUpdateTimeRange: (timeRange: AbsoluteTimeRange) => void;
  13. onLoadLogsVolume: () => void;
  14. };
  15. const SHORT_ERROR_MESSAGE_LIMIT = 100;
  16. function ErrorAlert(props: { error: DataQueryError }) {
  17. const [isOpen, setIsOpen] = useState(false);
  18. // generic get-error-message-logic, taken from
  19. // /public/app/features/explore/ErrorContainer.tsx
  20. const message = props.error.message || props.error.data?.message || '';
  21. const showButton = !isOpen && message.length > SHORT_ERROR_MESSAGE_LIMIT;
  22. return (
  23. <Alert title="Failed to load log volume for this query" severity="warning">
  24. {showButton ? (
  25. <Button
  26. variant="secondary"
  27. size="xs"
  28. onClick={() => {
  29. setIsOpen(true);
  30. }}
  31. >
  32. Show details
  33. </Button>
  34. ) : (
  35. message
  36. )}
  37. </Alert>
  38. );
  39. }
  40. export function LogsVolumePanel(props: Props) {
  41. const { width, logsVolumeData, absoluteRange, timeZone, splitOpen, onUpdateTimeRange, onLoadLogsVolume } = props;
  42. const theme = useTheme2();
  43. const styles = useStyles2(getStyles);
  44. const spacing = parseInt(theme.spacing(2).slice(0, -2), 10);
  45. const height = 150;
  46. let LogsVolumePanelContent;
  47. if (!logsVolumeData) {
  48. return null;
  49. } else if (logsVolumeData?.error) {
  50. return <ErrorAlert error={logsVolumeData?.error} />;
  51. } else if (logsVolumeData?.state === LoadingState.Loading) {
  52. LogsVolumePanelContent = <span>Log volume is loading...</span>;
  53. } else if (logsVolumeData?.data) {
  54. if (logsVolumeData.data.length > 0) {
  55. LogsVolumePanelContent = (
  56. <ExploreGraph
  57. graphStyle="lines"
  58. loadingState={LoadingState.Done}
  59. data={logsVolumeData.data}
  60. height={height}
  61. width={width - spacing}
  62. absoluteRange={absoluteRange}
  63. onChangeTime={onUpdateTimeRange}
  64. timeZone={timeZone}
  65. splitOpenFn={splitOpen}
  66. tooltipDisplayMode={TooltipDisplayMode.Multi}
  67. />
  68. );
  69. } else {
  70. LogsVolumePanelContent = <span>No volume data.</span>;
  71. }
  72. }
  73. const zoomRatio = logsLevelZoomRatio(logsVolumeData, absoluteRange);
  74. let zoomLevelInfo;
  75. if (zoomRatio !== undefined && zoomRatio < 1) {
  76. zoomLevelInfo = (
  77. <InlineField label="Reload log volume" transparent>
  78. <Button size="xs" icon="sync" variant="secondary" onClick={onLoadLogsVolume} id="reload-volume" />
  79. </InlineField>
  80. );
  81. }
  82. return (
  83. <Collapse label="Log volume" isOpen={true} loading={logsVolumeData?.state === LoadingState.Loading}>
  84. <div style={{ height }} className={styles.contentContainer}>
  85. {LogsVolumePanelContent}
  86. </div>
  87. <div className={styles.zoomInfoContainer}>{zoomLevelInfo}</div>
  88. </Collapse>
  89. );
  90. }
  91. const getStyles = () => {
  92. return {
  93. zoomInfoContainer: css`
  94. display: flex;
  95. justify-content: end;
  96. position: absolute;
  97. right: 5px;
  98. top: 5px;
  99. `,
  100. contentContainer: css`
  101. display: flex;
  102. align-items: center;
  103. justify-content: center;
  104. `,
  105. };
  106. };
  107. function logsLevelZoomRatio(
  108. logsVolumeData: DataQueryResponse | undefined,
  109. selectedTimeRange: AbsoluteTimeRange
  110. ): number | undefined {
  111. const dataRange = logsVolumeData && logsVolumeData.data[0] && logsVolumeData.data[0].meta?.custom?.absoluteRange;
  112. return dataRange ? (selectedTimeRange.from - selectedTimeRange.to) / (dataRange.from - dataRange.to) : undefined;
  113. }