import { css } from '@emotion/css'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { AbsoluteTimeRange, CoreApp, DataFrame, GrafanaTheme2, LoadingState, LogsSortOrder, PanelProps, } from '@grafana/data'; import { config } from '@grafana/runtime'; import { usePanelContext, useStyles2 } from '@grafana/ui'; import { LogList } from 'app/features/logs/components/panel/LogList'; import { PanelDataErrorView } from 'app/features/panel/components/PanelDataErrorView'; import { dataFrameToLogsModel, dedupLogRows } from '../../../features/logs/logsModel'; import { requestMoreLogs } from '../logs/LogsPanel'; import { isOnNewLogsReceivedType } from '../logs/types'; import { useDatasourcesFromTargets } from '../logs/useDatasourcesFromTargets'; import { Options } from './panelcfg.gen'; interface LogsPanelProps extends PanelProps {} export const LogsPanel = ({ data, timeZone, fieldConfig, options: { dedupStrategy, enableInfiniteScrolling, onNewLogsReceived, showTime, sortOrder, wrapLogMessage }, id, }: LogsPanelProps) => { const style = useStyles2(getStyles); const [logsContainer, setLogsContainer] = useState(null); const [panelData, setPanelData] = useState(data); const dataSourcesMap = useDatasourcesFromTargets(data.request?.targets); // Prevents the scroll position to change when new data from infinite scrolling is received const keepScrollPositionRef = useRef(false); // Loading ref to prevent firing multiple requests const loadingRef = useRef(false); const { eventBus } = usePanelContext(); const logs = useMemo(() => { const logsModel = panelData ? dataFrameToLogsModel(panelData.series, panelData.request?.intervalMs, undefined, panelData.request?.targets) : null; return logsModel ? dedupLogRows(logsModel.rows, dedupStrategy) : []; }, [dedupStrategy, panelData]); useEffect(() => { if (data.state !== LoadingState.Loading) { setPanelData(data); } }, [data]); const loadMoreLogs = useCallback( async (scrollRange: AbsoluteTimeRange) => { if (!data.request || !config.featureToggles.logsInfiniteScrolling || loadingRef.current) { return; } loadingRef.current = true; const onNewLogsReceivedCallback = isOnNewLogsReceivedType(onNewLogsReceived) ? onNewLogsReceived : undefined; let newSeries: DataFrame[] = []; try { newSeries = await requestMoreLogs(dataSourcesMap, panelData, scrollRange, timeZone, onNewLogsReceivedCallback); } catch (e) { console.error(e); } finally { loadingRef.current = false; } keepScrollPositionRef.current = true; setPanelData({ ...panelData, series: newSeries, }); }, [data.request, dataSourcesMap, onNewLogsReceived, panelData, timeZone] ); const initialScrollPosition = useMemo(() => { /** * In dashboards, users with newest logs at the bottom have the expectation of keeping the scroll at the bottom * when new data is received. See https://github.com/grafana/grafana/pull/37634 */ if (data.request?.app === CoreApp.Dashboard || data.request?.app === CoreApp.PanelEditor) { return sortOrder === LogsSortOrder.Ascending ? 'bottom' : 'top'; } return 'top'; }, [data.request?.app, sortOrder]); if (!logs.length) { return ; } return (
setLogsContainer(element)}> {logs.length > 0 && logsContainer && ( )}
); }; const getStyles = (theme: GrafanaTheme2) => ({ container: css({ marginBottom: theme.spacing(1.5), minHeight: '100%', maxHeight: '100%', display: 'flex', flex: 1, flexDirection: 'column', }), });