LogsPanel.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { css } from '@emotion/css';
  2. import React, { useCallback, useMemo, useRef, useLayoutEffect, useState } from 'react';
  3. import {
  4. PanelProps,
  5. Field,
  6. Labels,
  7. GrafanaTheme2,
  8. LogsSortOrder,
  9. LogRowModel,
  10. DataHoverClearEvent,
  11. DataHoverEvent,
  12. } from '@grafana/data';
  13. import { LogRows, CustomScrollbar, LogLabels, useStyles2, usePanelContext } from '@grafana/ui';
  14. import { dataFrameToLogsModel, dedupLogRows } from 'app/core/logs_model';
  15. import { getFieldLinksForExplore } from 'app/features/explore/utils/links';
  16. import { PanelDataErrorView } from 'app/features/panel/components/PanelDataErrorView';
  17. import { COMMON_LABELS } from '../../../core/logs_model';
  18. import { Options } from './types';
  19. interface LogsPanelProps extends PanelProps<Options> {}
  20. export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
  21. data,
  22. timeZone,
  23. fieldConfig,
  24. options: {
  25. showLabels,
  26. showTime,
  27. wrapLogMessage,
  28. showCommonLabels,
  29. prettifyLogMessage,
  30. sortOrder,
  31. dedupStrategy,
  32. enableLogDetails,
  33. },
  34. title,
  35. id,
  36. }) => {
  37. const isAscending = sortOrder === LogsSortOrder.Ascending;
  38. const style = useStyles2(getStyles(title, isAscending));
  39. const [scrollTop, setScrollTop] = useState(0);
  40. const logsContainerRef = useRef<HTMLDivElement>(null);
  41. const { eventBus } = usePanelContext();
  42. const onLogRowHover = useCallback(
  43. (row?: LogRowModel) => {
  44. if (!row) {
  45. eventBus.publish(new DataHoverClearEvent());
  46. } else {
  47. eventBus.publish(
  48. new DataHoverEvent({
  49. point: {
  50. time: row.timeEpochMs,
  51. },
  52. })
  53. );
  54. }
  55. },
  56. [eventBus]
  57. );
  58. // Important to memoize stuff here, as panel rerenders a lot for example when resizing.
  59. const [logRows, deduplicatedRows, commonLabels] = useMemo(() => {
  60. const newResults = data ? dataFrameToLogsModel(data.series, data.request?.intervalMs) : null;
  61. const logRows = newResults?.rows || [];
  62. const commonLabels = newResults?.meta?.find((m) => m.label === COMMON_LABELS);
  63. const deduplicatedRows = dedupLogRows(logRows, dedupStrategy);
  64. return [logRows, deduplicatedRows, commonLabels];
  65. }, [data, dedupStrategy]);
  66. useLayoutEffect(() => {
  67. if (isAscending && logsContainerRef.current) {
  68. setScrollTop(logsContainerRef.current.offsetHeight);
  69. } else {
  70. setScrollTop(0);
  71. }
  72. }, [isAscending, logRows]);
  73. const getFieldLinks = useCallback(
  74. (field: Field, rowIndex: number) => {
  75. return getFieldLinksForExplore({ field, rowIndex, range: data.timeRange });
  76. },
  77. [data]
  78. );
  79. if (!data || logRows.length === 0) {
  80. return <PanelDataErrorView fieldConfig={fieldConfig} panelId={id} data={data} needsStringField />;
  81. }
  82. const renderCommonLabels = () => (
  83. <div className={style.labelContainer}>
  84. <span className={style.label}>Common labels:</span>
  85. <LogLabels labels={commonLabels ? (commonLabels.value as Labels) : { labels: '(no common labels)' }} />
  86. </div>
  87. );
  88. return (
  89. <CustomScrollbar autoHide scrollTop={scrollTop}>
  90. <div className={style.container} ref={logsContainerRef}>
  91. {showCommonLabels && !isAscending && renderCommonLabels()}
  92. <LogRows
  93. logRows={logRows}
  94. deduplicatedRows={deduplicatedRows}
  95. dedupStrategy={dedupStrategy}
  96. showLabels={showLabels}
  97. showTime={showTime}
  98. wrapLogMessage={wrapLogMessage}
  99. prettifyLogMessage={prettifyLogMessage}
  100. timeZone={timeZone}
  101. getFieldLinks={getFieldLinks}
  102. logsSortOrder={sortOrder}
  103. enableLogDetails={enableLogDetails}
  104. previewLimit={isAscending ? logRows.length : undefined}
  105. onLogRowHover={onLogRowHover}
  106. />
  107. {showCommonLabels && isAscending && renderCommonLabels()}
  108. </div>
  109. </CustomScrollbar>
  110. );
  111. };
  112. const getStyles = (title: string, isAscending: boolean) => (theme: GrafanaTheme2) => ({
  113. container: css`
  114. margin-bottom: ${theme.spacing(1.5)};
  115. //We can remove this hot-fix when we fix panel menu with no title overflowing top of all panels
  116. margin-top: ${theme.spacing(!title ? 2.5 : 0)};
  117. `,
  118. labelContainer: css`
  119. margin: ${isAscending ? theme.spacing(0.5, 0, 0.5, 0) : theme.spacing(0, 0, 0.5, 0.5)};
  120. display: flex;
  121. align-items: center;
  122. `,
  123. label: css`
  124. margin-right: ${theme.spacing(0.5)};
  125. font-size: ${theme.typography.bodySmall.fontSize};
  126. font-weight: ${theme.typography.fontWeightMedium};
  127. `,
  128. });