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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
|
||||
Reference in New Issue
Block a user