Logs Volume: Show visible range of logs in Explore (#114501)

* Logs Volume: Show visible range of logs

* Translations

* Prettier and translations

* Unfocus test
This commit is contained in:
Matias Chomicki
2025-12-02 18:08:48 +01:00
committed by GitHub
parent a112c6c169
commit ec5165d1a4
5 changed files with 104 additions and 8 deletions
@@ -808,6 +808,7 @@ const UnthemedLogs: React.FunctionComponent<Props> = (props: Props) => {
onDisplayedSeriesChanged={onDisplayedSeriesChanged}
eventBus={logsVolumeEventBus}
onClose={() => onToggleLogsVolumeCollapse(true)}
logs={logRows}
/>
)}
</PanelChrome>
@@ -1,7 +1,10 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { DataQueryResponse, LoadingState, EventBusSrv } from '@grafana/data';
import { DataQueryResponse, LoadingState, EventBusSrv, LogRowModel, arrayToDataFrame, DataTopic } from '@grafana/data';
import { createLogLine } from 'app/features/logs/components/mocks/logRow';
import * as logUtils from '../../logs/utils';
import { LogsVolumePanelList } from './LogsVolumePanelList';
@@ -12,7 +15,7 @@ jest.mock('../Graph/ExploreGraph', () => {
};
});
function renderPanel(logsVolumeData?: DataQueryResponse, onLoadLogsVolume = () => {}) {
function renderPanel(logsVolumeData?: DataQueryResponse, onLoadLogsVolume = () => {}, logs: LogRowModel[] = []) {
render(
<LogsVolumePanelList
absoluteRange={{ from: 0, to: 1 }}
@@ -24,6 +27,7 @@ function renderPanel(logsVolumeData?: DataQueryResponse, onLoadLogsVolume = () =
onLoadLogsVolume={onLoadLogsVolume}
onDisplayedSeriesChanged={() => null}
eventBus={new EventBusSrv()}
logs={logs}
/>
);
}
@@ -74,8 +78,48 @@ describe('LogsVolumePanelList', () => {
expect(screen.getByText('No logs volume available')).toBeInTheDocument();
});
it('does not show an info message with empty responses when the current state is streaming', async () => {
renderPanel({ state: LoadingState.Streaming, data: [] });
expect(screen.queryByText('No logs volume available')).not.toBeInTheDocument();
describe('Visible range', () => {
it('computes the visible range when logs are passed', async () => {
const spy = jest.spyOn(logUtils, 'getLogsVisibleRange');
spy.mockClear();
renderPanel({ state: LoadingState.Streaming, data: [] });
expect(spy).not.toHaveBeenCalled();
const logs = [createLogLine({ timeEpochMs: 1 }), createLogLine({ timeEpochMs: 2 })];
renderPanel({ state: LoadingState.Streaming, data: [] }, undefined, logs);
expect(spy).toHaveBeenCalledWith(logs);
});
it('does not compute the visible range when an annotation frame is present', async () => {
const spy = jest.spyOn(logUtils, 'getLogsVisibleRange');
spy.mockClear();
const logs = [createLogLine({ timeEpochMs: 1 }), createLogLine({ timeEpochMs: 2 })];
const loadingFrame = arrayToDataFrame([
{
time: 0,
timeEnd: 1,
isRegion: true,
color: 'rgba(120, 120, 120, 0.1)',
},
]);
loadingFrame.meta = {
dataTopic: DataTopic.Annotations,
};
renderPanel(
{
state: LoadingState.Streaming,
data: [loadingFrame],
},
undefined,
logs
);
expect(spy).not.toHaveBeenCalled();
});
});
});
@@ -5,6 +5,7 @@ import * as React from 'react';
import {
AbsoluteTimeRange,
arrayToDataFrame,
DataFrame,
DataQueryResponse,
DataTopic,
@@ -13,6 +14,7 @@ import {
getFrameDisplayName,
GrafanaTheme2,
LoadingState,
LogRowModel,
shallowCompare,
SplitOpen,
TimeRange,
@@ -22,7 +24,12 @@ import { Trans, t } from '@grafana/i18n';
import { config } from '@grafana/runtime';
import { Button, InlineField, Alert, useStyles2, SeriesVisibilityChangeMode } from '@grafana/ui';
import { mergeLogsVolumeDataFrames, isLogsVolumeLimited, getLogsVolumeMaximumRange } from '../../logs/utils';
import {
mergeLogsVolumeDataFrames,
isLogsVolumeLimited,
getLogsVolumeMaximumRange,
getLogsVisibleRange,
} from '../../logs/utils';
import { SupplementaryResultError } from '../SupplementaryResultError';
import { LogsVolumePanel } from './LogsVolumePanel';
@@ -40,8 +47,10 @@ type Props = {
eventBus: EventBus;
onClose?(): void;
toggleLegendRef?: React.MutableRefObject<(name: string | undefined, mode: SeriesVisibilityChangeMode) => void>;
logs: LogRowModel[];
};
export const VISIBLE_RANGE_FRAME_NAME = 'Visible range';
export const LogsVolumePanelList = ({
logsVolumeData,
absoluteRange,
@@ -54,6 +63,7 @@ export const LogsVolumePanelList = ({
timeZone,
onClose,
toggleLegendRef,
logs,
}: Props) => {
const {
logVolumes,
@@ -63,6 +73,7 @@ export const LogsVolumePanelList = ({
} = useMemo(() => {
let maximumValue = -Infinity;
const data = logsVolumeData?.data.filter((frame: DataFrame) => frame.meta?.dataTopic !== DataTopic.Annotations);
// Loading frame from query splitting.
const annotations =
logsVolumeData?.data.filter((frame: DataFrame) => frame.meta?.dataTopic === DataTopic.Annotations) || [];
const sorted = sortBy(data || [], 'meta.custom.datasourceName');
@@ -73,13 +84,38 @@ export const LogsVolumePanelList = ({
return mergedData.dataFrames;
});
const maximumRange = getLogsVolumeMaximumRange(flatten(Object.values(logVolumes)));
// No loading frame, show visible range
if (!annotations.length && logs.length) {
const { start, end } = getLogsVisibleRange(logs);
if (start > 0 && end > 0) {
const frame = arrayToDataFrame([
{
color: 'rgba(58, 113, 255, 0.3)',
isRegion: true,
text: t(
'explore.logs-volume-panel-list.visible-range-description',
'Range from oldest to newest logs in display'
),
time: start,
timeEnd: end,
},
]);
frame.name = VISIBLE_RANGE_FRAME_NAME;
frame.meta = {
dataTopic: DataTopic.Annotations,
};
annotations.push(frame);
}
}
return {
maximumValue,
maximumRange,
logVolumes,
annotations,
};
}, [logsVolumeData]);
}, [logs, logsVolumeData?.data]);
const styles = useStyles2(getStyles);
+14
View File
@@ -523,3 +523,17 @@ const addISODateTransformation: CustomTransformOperator = () => (source: Observa
})
);
};
/**
* Get the start and end timestamps from series.
*/
export function getLogsVisibleRange(logs: LogRowModel[]) {
let start = 0;
let end = 0;
if (logs.length > 1) {
const values = [logs[0].timeEpochMs, logs[logs.length - 1].timeEpochMs].sort();
start = values[0];
end = values[values.length - 1];
}
return { end, start };
}
+2 -1
View File
@@ -7330,7 +7330,8 @@
"title-failed-volume-query": "Failed to load log volume for this query",
"title-no-logs-volume-available": "No logs volume available",
"title-showing-partial-data": "Showing partial data",
"title-unable-to-show-log-volume": "Unable to show log volume"
"title-unable-to-show-log-volume": "Unable to show log volume",
"visible-range-description": "Range from oldest to newest logs in display"
},
"logs-volumne-panel-list": {
"body-no-logs-volume-available": "No volume information available for the current queries and time range."