mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 00:25:46 -06:00
162 lines
5.2 KiB
TypeScript
162 lines
5.2 KiB
TypeScript
import { DataQueryResponse, DataFrame, isDataFrame, FieldType, QueryResultMeta, DataQueryError } from '@grafana/data';
|
|
|
|
import { getDerivedFields } from './getDerivedFields';
|
|
import { makeTableFrames } from './makeTableFrames';
|
|
import { formatQuery, getHighlighterExpressionsFromQuery } from './queryUtils';
|
|
import { dataFrameHasLokiError } from './responseUtils';
|
|
import { DerivedFieldConfig, LokiQuery, LokiQueryType } from './types';
|
|
|
|
function isMetricFrame(frame: DataFrame): boolean {
|
|
return frame.fields.every((field) => field.type === FieldType.time || field.type === FieldType.number);
|
|
}
|
|
|
|
// returns a new frame, with meta shallow merged with its original meta
|
|
function setFrameMeta(frame: DataFrame, meta: QueryResultMeta): DataFrame {
|
|
const { meta: oldMeta, ...rest } = frame;
|
|
// meta maybe be undefined, we need to handle that
|
|
const newMeta = { ...oldMeta, ...meta };
|
|
return {
|
|
...rest,
|
|
meta: newMeta,
|
|
};
|
|
}
|
|
|
|
function processStreamFrame(
|
|
frame: DataFrame,
|
|
query: LokiQuery | undefined,
|
|
derivedFieldConfigs: DerivedFieldConfig[]
|
|
): DataFrame {
|
|
const custom: Record<string, string> = {
|
|
...frame.meta?.custom, // keep the original meta.custom
|
|
// used by logsModel
|
|
lokiQueryStatKey: 'Summary: total bytes processed',
|
|
};
|
|
|
|
if (dataFrameHasLokiError(frame)) {
|
|
custom.error = 'Error when parsing some of the logs';
|
|
}
|
|
|
|
const meta: QueryResultMeta = {
|
|
preferredVisualisationType: 'logs',
|
|
limit: query?.maxLines,
|
|
searchWords: query !== undefined ? getHighlighterExpressionsFromQuery(formatQuery(query.expr)) : undefined,
|
|
custom,
|
|
};
|
|
|
|
const newFrame = setFrameMeta(frame, meta);
|
|
const derivedFields = getDerivedFields(newFrame, derivedFieldConfigs);
|
|
return {
|
|
...newFrame,
|
|
fields: [...newFrame.fields, ...derivedFields],
|
|
};
|
|
}
|
|
|
|
function processStreamsFrames(
|
|
frames: DataFrame[],
|
|
queryMap: Map<string, LokiQuery>,
|
|
derivedFieldConfigs: DerivedFieldConfig[]
|
|
): DataFrame[] {
|
|
return frames.map((frame) => {
|
|
const query = frame.refId !== undefined ? queryMap.get(frame.refId) : undefined;
|
|
return processStreamFrame(frame, query, derivedFieldConfigs);
|
|
});
|
|
}
|
|
|
|
function processMetricInstantFrames(frames: DataFrame[]): DataFrame[] {
|
|
return frames.length > 0 ? makeTableFrames(frames) : [];
|
|
}
|
|
|
|
function processMetricRangeFrames(frames: DataFrame[]): DataFrame[] {
|
|
const meta: QueryResultMeta = { preferredVisualisationType: 'graph' };
|
|
return frames.map((frame) => setFrameMeta(frame, meta));
|
|
}
|
|
|
|
// we split the frames into 3 groups, because we will handle
|
|
// each group slightly differently
|
|
function groupFrames(
|
|
frames: DataFrame[],
|
|
queryMap: Map<string, LokiQuery>
|
|
): {
|
|
streamsFrames: DataFrame[];
|
|
metricInstantFrames: DataFrame[];
|
|
metricRangeFrames: DataFrame[];
|
|
} {
|
|
const streamsFrames: DataFrame[] = [];
|
|
const metricInstantFrames: DataFrame[] = [];
|
|
const metricRangeFrames: DataFrame[] = [];
|
|
|
|
frames.forEach((frame) => {
|
|
if (!isMetricFrame(frame)) {
|
|
streamsFrames.push(frame);
|
|
} else {
|
|
const isInstantFrame = frame.refId != null && queryMap.get(frame.refId)?.queryType === LokiQueryType.Instant;
|
|
if (isInstantFrame) {
|
|
metricInstantFrames.push(frame);
|
|
} else {
|
|
metricRangeFrames.push(frame);
|
|
}
|
|
}
|
|
});
|
|
|
|
return { streamsFrames, metricInstantFrames, metricRangeFrames };
|
|
}
|
|
|
|
function improveError(error: DataQueryError | undefined, queryMap: Map<string, LokiQuery>): DataQueryError | undefined {
|
|
// many things are optional in an error-object, we need an error-message to exist,
|
|
// and we need to find the loki-query, based on the refId in the error-object.
|
|
if (error === undefined) {
|
|
return error;
|
|
}
|
|
|
|
const { refId, message } = error;
|
|
if (refId === undefined || message === undefined) {
|
|
return error;
|
|
}
|
|
|
|
const query = queryMap.get(refId);
|
|
if (query === undefined) {
|
|
return error;
|
|
}
|
|
|
|
if (message.includes('escape') && query.expr.includes('\\')) {
|
|
return {
|
|
...error,
|
|
message: `${message}. Make sure that all special characters are escaped with \\. For more information on escaping of special characters visit LogQL documentation at https://grafana.com/docs/loki/latest/logql/.`,
|
|
};
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
export function transformBackendResult(
|
|
response: DataQueryResponse,
|
|
queries: LokiQuery[],
|
|
derivedFieldConfigs: DerivedFieldConfig[]
|
|
): DataQueryResponse {
|
|
const { data, error, ...rest } = response;
|
|
|
|
// in the typescript type, data is an array of basically anything.
|
|
// we do know that they have to be dataframes, so we make a quick check,
|
|
// this way we can be sure, and also typescript is happy.
|
|
const dataFrames = data.map((d) => {
|
|
if (!isDataFrame(d)) {
|
|
throw new Error('transformation only supports dataframe responses');
|
|
}
|
|
return d;
|
|
});
|
|
|
|
const queryMap = new Map(queries.map((query) => [query.refId, query]));
|
|
|
|
const { streamsFrames, metricInstantFrames, metricRangeFrames } = groupFrames(dataFrames, queryMap);
|
|
|
|
return {
|
|
...rest,
|
|
error: improveError(error, queryMap),
|
|
data: [
|
|
...processMetricRangeFrames(metricRangeFrames),
|
|
...processMetricInstantFrames(metricInstantFrames),
|
|
...processStreamsFrames(streamsFrames, queryMap, derivedFieldConfigs),
|
|
],
|
|
};
|
|
}
|