From c58ed41d50198e0ff5a1d1747fd5d0c143b34c46 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Tue, 19 Dec 2023 13:33:18 +0100 Subject: [PATCH] Loki Logs volume: Added a query splitting loading indicator to the Logs Volume graph (#79681) * Logs volume: distinguish annotations from time series frames * Update unit tests * Update unit tests * Formatting --- .../explore/Logs/LogsVolumePanel.test.tsx | 1 + .../features/explore/Logs/LogsVolumePanel.tsx | 3 + .../explore/Logs/LogsVolumePanelList.tsx | 9 ++- public/app/features/logs/logsModel.test.ts | 78 +++++++++++++++++++ public/app/features/logs/logsModel.ts | 5 ++ .../plugins/datasource/loki/querySplitting.ts | 13 ++-- 6 files changed, 100 insertions(+), 9 deletions(-) diff --git a/public/app/features/explore/Logs/LogsVolumePanel.test.tsx b/public/app/features/explore/Logs/LogsVolumePanel.test.tsx index fb973f438a3..46bb7db737f 100644 --- a/public/app/features/explore/Logs/LogsVolumePanel.test.tsx +++ b/public/app/features/explore/Logs/LogsVolumePanel.test.tsx @@ -25,6 +25,7 @@ function renderPanel(logsVolumeData: DataQueryResponse) { onHiddenSeriesChanged={() => null} eventBus={new EventBusSrv()} allLogsVolumeMaximum={20} + annotations={[]} /> ); } diff --git a/public/app/features/explore/Logs/LogsVolumePanel.tsx b/public/app/features/explore/Logs/LogsVolumePanel.tsx index cd31dcce517..7e42f7dd460 100644 --- a/public/app/features/explore/Logs/LogsVolumePanel.tsx +++ b/public/app/features/explore/Logs/LogsVolumePanel.tsx @@ -10,6 +10,7 @@ import { TimeZone, EventBus, GrafanaTheme2, + DataFrame, } from '@grafana/data'; import { Icon, Tooltip, TooltipDisplayMode, useStyles2, useTheme2 } from '@grafana/ui'; @@ -27,6 +28,7 @@ type Props = { onLoadLogsVolume: () => void; onHiddenSeriesChanged: (hiddenSeries: string[]) => void; eventBus: EventBus; + annotations: DataFrame[]; }; export function LogsVolumePanel(props: Props) { @@ -80,6 +82,7 @@ export function LogsVolumePanel(props: Props) { anchorToZero yAxisMaximum={allLogsVolumeMaximum} eventBus={props.eventBus} + annotations={props.annotations} /> {extraInfoComponent &&
{extraInfoComponent}
} diff --git a/public/app/features/explore/Logs/LogsVolumePanelList.tsx b/public/app/features/explore/Logs/LogsVolumePanelList.tsx index 1d513abb3b1..8ca60b4ada8 100644 --- a/public/app/features/explore/Logs/LogsVolumePanelList.tsx +++ b/public/app/features/explore/Logs/LogsVolumePanelList.tsx @@ -6,6 +6,7 @@ import { AbsoluteTimeRange, DataFrame, DataQueryResponse, + DataTopic, EventBus, GrafanaTheme2, LoadingState, @@ -49,9 +50,13 @@ export const LogsVolumePanelList = ({ logVolumes, maximumValue: allLogsVolumeMaximumValue, maximumRange: allLogsVolumeMaximumRange, + annotations, } = useMemo(() => { let maximumValue = -Infinity; - const sorted = sortBy(logsVolumeData?.data || [], 'meta.custom.datasourceName'); + const data = logsVolumeData?.data.filter((frame: DataFrame) => frame.meta?.dataTopic !== DataTopic.Annotations); + const annotations = + logsVolumeData?.data.filter((frame: DataFrame) => frame.meta?.dataTopic === DataTopic.Annotations) || []; + const sorted = sortBy(data || [], 'meta.custom.datasourceName'); const grouped = groupBy(sorted, 'meta.custom.datasourceName'); const logVolumes = mapValues(grouped, (value) => { const mergedData = mergeLogsVolumeDataFrames(value); @@ -63,6 +68,7 @@ export const LogsVolumePanelList = ({ maximumValue, maximumRange, logVolumes, + annotations, }; }, [logsVolumeData]); @@ -127,6 +133,7 @@ export const LogsVolumePanelList = ({ // TODO: Support filtering level from multiple log levels onHiddenSeriesChanged={numberOfLogVolumes > 1 ? () => {} : onHiddenSeriesChanged} eventBus={eventBus} + annotations={annotations} /> ); })} diff --git a/public/app/features/logs/logsModel.test.ts b/public/app/features/logs/logsModel.test.ts index eaca7a1e9ce..58fa2ec512d 100644 --- a/public/app/features/logs/logsModel.test.ts +++ b/public/app/features/logs/logsModel.test.ts @@ -1,10 +1,12 @@ import { Observable } from 'rxjs'; import { + arrayToDataFrame, DataFrame, DataQuery, DataQueryRequest, DataQueryResponse, + DataTopic, dateTimeParse, FieldType, LoadingState, @@ -1282,6 +1284,33 @@ describe('logs volume', () => { ]); } + function setupLogsVolumeWithAnnotations() { + const resultAFrame1 = createFrame({ app: 'app01' }, [100, 200, 300], [5, 5, 5], 'A'); + const loadingFrame = arrayToDataFrame([ + { + time: 100, + timeEnd: 200, + isRegion: true, + color: 'rgba(120, 120, 120, 0.1)', + }, + ]); + loadingFrame.name = 'annotation'; + loadingFrame.meta = { + dataTopic: DataTopic.Annotations, + }; + + datasource = new MockObservableDataSourceApi('loki', [ + { + state: LoadingState.Streaming, + data: [resultAFrame1, loadingFrame], + }, + { + state: LoadingState.Done, + data: [resultAFrame1], + }, + ]); + } + function setupErrorResponse() { datasource = new MockObservableDataSourceApi('loki', [], undefined, 'Error message'); } @@ -1363,6 +1392,55 @@ describe('logs volume', () => { ]); }); }); + + it('handles annotations in responses', async () => { + setup(setupLogsVolumeWithAnnotations); + + const logVolumeCustomMeta: LogsVolumeCustomMetaData = { + sourceQuery: { refId: 'A', target: 'volume query 1' } as DataQuery, + datasourceName: 'loki', + logsVolumeType: LogsVolumeType.FullRange, + absoluteRange: { + from: FROM.valueOf(), + to: TO.valueOf(), + }, + }; + + await expect(volumeProvider).toEmitValuesWith((received) => { + expect(received).toContainEqual({ state: LoadingState.Loading, error: undefined, data: [] }); + expect(received).toContainEqual({ + state: LoadingState.Streaming, + error: undefined, + data: [ + expect.objectContaining({ + fields: expect.anything(), + meta: { + custom: logVolumeCustomMeta, + }, + }), + expect.objectContaining({ + fields: expect.anything(), + meta: { + dataTopic: DataTopic.Annotations, + }, + name: 'annotation', + }), + ], + }); + expect(received).toContainEqual({ + state: LoadingState.Done, + error: undefined, + data: [ + expect.objectContaining({ + fields: expect.anything(), + meta: { + custom: logVolumeCustomMeta, + }, + }), + ], + }); + }); + }); }); describe('logs sample', () => { diff --git a/public/app/features/logs/logsModel.ts b/public/app/features/logs/logsModel.ts index 468406f123d..d49dfd9a414 100644 --- a/public/app/features/logs/logsModel.ts +++ b/public/app/features/logs/logsModel.ts @@ -10,6 +10,7 @@ import { DataQueryResponse, DataSourceApi, DataSourceJsonData, + DataTopic, dateTimeFormat, dateTimeFormatTimeAgo, DateTimeInput, @@ -658,6 +659,10 @@ export function queryLogsVolume { + // Separate possible annotations from data frames + if (dataFrame.meta?.dataTopic === DataTopic.Annotations) { + return dataFrame; + } let sourceRefId = dataFrame.refId || ''; if (sourceRefId.startsWith('log-volume-')) { sourceRefId = sourceRefId.substr('log-volume-'.length); diff --git a/public/app/plugins/datasource/loki/querySplitting.ts b/public/app/plugins/datasource/loki/querySplitting.ts index 74b7a14969a..6e00cb159c0 100644 --- a/public/app/plugins/datasource/loki/querySplitting.ts +++ b/public/app/plugins/datasource/loki/querySplitting.ts @@ -150,17 +150,18 @@ export function runSplitGroupedQueries(datasource: LokiDatasource, requests: Lok return response; } +export const LOADING_FRAME_NAME = 'loki-splitting-progress'; + function updateLoadingFrame( response: DataQueryResponse, request: DataQueryRequest, partition: TimeRange[], requestN: number ): DataQueryResponse { - if (isLogsQuery(request.targets[0].expr) || isLogsVolumeRequest(request)) { + if (isLogsQuery(request.targets[0].expr)) { return response; } - const loadingFrameName = 'loki-splitting-progress'; - response.data = response.data.filter((frame) => frame.name !== loadingFrameName); + response.data = response.data.filter((frame) => frame.name !== LOADING_FRAME_NAME); if (requestN <= 1) { return response; @@ -174,7 +175,7 @@ function updateLoadingFrame( color: 'rgba(120, 120, 120, 0.1)', }, ]); - loadingFrame.name = loadingFrameName; + loadingFrame.name = LOADING_FRAME_NAME; loadingFrame.meta = { dataTopic: DataTopic.Annotations, }; @@ -184,10 +185,6 @@ function updateLoadingFrame( return response; } -function isLogsVolumeRequest(request: DataQueryRequest): boolean { - return request.targets.some((target) => target.refId.startsWith('log-volume')); -} - function getNextRequestPointers(requests: LokiGroupedRequest[], requestGroup: number, requestN: number) { // There's a pending request from the next group: for (let i = requestGroup + 1; i < requests.length; i++) {