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
This commit is contained in:
@@ -25,6 +25,7 @@ function renderPanel(logsVolumeData: DataQueryResponse) {
|
||||
onHiddenSeriesChanged={() => null}
|
||||
eventBus={new EventBusSrv()}
|
||||
allLogsVolumeMaximum={20}
|
||||
annotations={[]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 && <div className={styles.extraInfoContainer}>{extraInfoComponent}</div>}
|
||||
</div>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
DataQueryResponse,
|
||||
DataSourceApi,
|
||||
DataSourceJsonData,
|
||||
DataTopic,
|
||||
dateTimeFormat,
|
||||
dateTimeFormatTimeAgo,
|
||||
DateTimeInput,
|
||||
@@ -658,6 +659,10 @@ export function queryLogsVolume<TQuery extends DataQuery, TOptions extends DataS
|
||||
} else {
|
||||
const framesByRefId = groupBy(dataQueryResponse.data, 'refId');
|
||||
logsVolumeData = dataQueryResponse.data.map((dataFrame) => {
|
||||
// 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);
|
||||
|
||||
@@ -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<LokiQuery>,
|
||||
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<LokiQuery>): 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++) {
|
||||
|
||||
Reference in New Issue
Block a user