131 lines
4.3 KiB
TypeScript
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',
|
|
}),
|
|
});
|