import { css } from '@emotion/css'; import React, { useCallback, useMemo, useRef, useLayoutEffect, useState } from 'react'; import { PanelProps, Field, Labels, GrafanaTheme2, LogsSortOrder, LogRowModel, DataHoverClearEvent, DataHoverEvent, } from '@grafana/data'; import { LogRows, CustomScrollbar, LogLabels, useStyles2, usePanelContext } from '@grafana/ui'; import { dataFrameToLogsModel, dedupLogRows } from 'app/core/logs_model'; import { getFieldLinksForExplore } from 'app/features/explore/utils/links'; import { PanelDataErrorView } from 'app/features/panel/components/PanelDataErrorView'; import { COMMON_LABELS } from '../../../core/logs_model'; import { Options } from './types'; interface LogsPanelProps extends PanelProps {} export const LogsPanel: React.FunctionComponent = ({ data, timeZone, fieldConfig, options: { showLabels, showTime, wrapLogMessage, showCommonLabels, prettifyLogMessage, sortOrder, dedupStrategy, enableLogDetails, }, title, id, }) => { const isAscending = sortOrder === LogsSortOrder.Ascending; const style = useStyles2(getStyles(title, isAscending)); const [scrollTop, setScrollTop] = useState(0); const logsContainerRef = useRef(null); const { eventBus } = usePanelContext(); const onLogRowHover = useCallback( (row?: LogRowModel) => { if (!row) { eventBus.publish(new DataHoverClearEvent()); } else { eventBus.publish( new DataHoverEvent({ point: { time: row.timeEpochMs, }, }) ); } }, [eventBus] ); // Important to memoize stuff here, as panel rerenders a lot for example when resizing. const [logRows, deduplicatedRows, commonLabels] = useMemo(() => { const newResults = data ? dataFrameToLogsModel(data.series, data.request?.intervalMs) : null; const logRows = newResults?.rows || []; const commonLabels = newResults?.meta?.find((m) => m.label === COMMON_LABELS); const deduplicatedRows = dedupLogRows(logRows, dedupStrategy); return [logRows, deduplicatedRows, commonLabels]; }, [data, dedupStrategy]); useLayoutEffect(() => { if (isAscending && logsContainerRef.current) { setScrollTop(logsContainerRef.current.offsetHeight); } else { setScrollTop(0); } }, [isAscending, logRows]); const getFieldLinks = useCallback( (field: Field, rowIndex: number) => { return getFieldLinksForExplore({ field, rowIndex, range: data.timeRange }); }, [data] ); if (!data || logRows.length === 0) { return ; } const renderCommonLabels = () => (
Common labels:
); return (
{showCommonLabels && !isAscending && renderCommonLabels()} {showCommonLabels && isAscending && renderCommonLabels()}
); }; const getStyles = (title: string, isAscending: boolean) => (theme: GrafanaTheme2) => ({ container: css` margin-bottom: ${theme.spacing(1.5)}; //We can remove this hot-fix when we fix panel menu with no title overflowing top of all panels margin-top: ${theme.spacing(!title ? 2.5 : 0)}; `, labelContainer: css` margin: ${isAscending ? theme.spacing(0.5, 0, 0.5, 0) : theme.spacing(0, 0, 0.5, 0.5)}; display: flex; align-items: center; `, label: css` margin-right: ${theme.spacing(0.5)}; font-size: ${theme.typography.bodySmall.fontSize}; font-weight: ${theme.typography.fontWeightMedium}; `, });