2025-04-01 10:38:02 +09:00

131 lines
4.3 KiB
TypeScript

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<Options> {}
export const LogsPanel = ({
data,
timeZone,
fieldConfig,
options: { dedupStrategy, enableInfiniteScrolling, onNewLogsReceived, showTime, sortOrder, wrapLogMessage },
id,
}: LogsPanelProps) => {
const style = useStyles2(getStyles);
const [logsContainer, setLogsContainer] = useState<HTMLDivElement | null>(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 <PanelDataErrorView fieldConfig={fieldConfig} panelId={id} data={data} needsStringField />;
}
return (
<div className={style.container} ref={(element: HTMLDivElement) => setLogsContainer(element)}>
{logs.length > 0 && logsContainer && (
<LogList
app={CoreApp.Dashboard}
containerElement={logsContainer}
displayedFields={[]}
eventBus={eventBus}
initialScrollPosition={initialScrollPosition}
logs={logs}
loadMore={enableInfiniteScrolling ? loadMoreLogs : undefined}
showTime={showTime}
sortOrder={sortOrder}
timeRange={data.timeRange}
timeZone={timeZone}
wrapLogMessage={wrapLogMessage}
/>
)}
</div>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
container: css({
marginBottom: theme.spacing(1.5),
minHeight: '100%',
maxHeight: '100%',
display: 'flex',
flex: 1,
flexDirection: 'column',
}),
});