2022-06-08 08:27:58 -05:00
import { DataQueryResponse , DataFrame , isDataFrame , FieldType , QueryResultMeta , DataQueryError } from '@grafana/data' ;
2022-04-22 08:33:13 -05:00
2022-05-11 02:29:04 -05:00
import { getDerivedFields } from './getDerivedFields' ;
2022-02-25 02:14:17 -06:00
import { makeTableFrames } from './makeTableFrames' ;
2022-09-16 03:16:54 -05:00
import { formatQuery , getHighlighterExpressionsFromQuery } from './queryUtils' ;
2022-05-16 01:29:46 -05:00
import { dataFrameHasLokiError } from './responseUtils' ;
2022-05-11 02:29:04 -05:00
import { DerivedFieldConfig , LokiQuery , LokiQueryType } from './types' ;
2022-02-25 02:14:17 -06:00
function isMetricFrame ( frame : DataFrame ) : boolean {
return frame . fields . every ( ( field ) = > field . type === FieldType . time || field . type === FieldType . number ) ;
}
2022-08-09 03:19:30 -05:00
// returns a new frame, with meta shallow merged with its original meta
2022-02-25 02:14:17 -06:00
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 ,
} ;
}
2022-05-11 02:29:04 -05:00
function processStreamFrame (
frame : DataFrame ,
query : LokiQuery | undefined ,
derivedFieldConfigs : DerivedFieldConfig [ ]
) : DataFrame {
2022-05-16 01:29:46 -05:00
const custom : Record < string , string > = {
2022-05-16 06:38:05 -05:00
. . . frame . meta ? . custom , // keep the original meta.custom
2022-06-29 14:04:30 -05:00
// used by logsModel
2022-05-16 01:29:46 -05:00
lokiQueryStatKey : 'Summary: total bytes processed' ,
} ;
if ( dataFrameHasLokiError ( frame ) ) {
custom . error = 'Error when parsing some of the logs' ;
}
2022-03-04 02:42:18 -06:00
const meta : QueryResultMeta = {
preferredVisualisationType : 'logs' ,
2022-05-12 01:56:09 -05:00
limit : query?.maxLines ,
2022-03-04 02:42:18 -06:00
searchWords : query !== undefined ? getHighlighterExpressionsFromQuery ( formatQuery ( query . expr ) ) : undefined ,
2022-05-16 01:29:46 -05:00
custom ,
2022-03-04 02:42:18 -06:00
} ;
2022-04-12 04:58:48 -05:00
2022-05-11 02:29:04 -05:00
const newFrame = setFrameMeta ( frame , meta ) ;
const derivedFields = getDerivedFields ( newFrame , derivedFieldConfigs ) ;
return {
. . . newFrame ,
fields : [ . . . newFrame . fields , . . . derivedFields ] ,
} ;
2022-03-04 02:42:18 -06:00
}
2022-05-11 02:29:04 -05:00
function processStreamsFrames (
frames : DataFrame [ ] ,
queryMap : Map < string , LokiQuery > ,
derivedFieldConfigs : DerivedFieldConfig [ ]
) : DataFrame [ ] {
2022-02-25 02:14:17 -06:00
return frames . map ( ( frame ) = > {
const query = frame . refId !== undefined ? queryMap . get ( frame . refId ) : undefined ;
2022-05-11 02:29:04 -05:00
return processStreamFrame ( frame , query , derivedFieldConfigs ) ;
2022-02-25 02:14:17 -06:00
} ) ;
}
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 } ;
}
2022-06-08 08:27:58 -05:00
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 ;
}
2022-05-11 02:29:04 -05:00
export function transformBackendResult (
response : DataQueryResponse ,
queries : LokiQuery [ ] ,
derivedFieldConfigs : DerivedFieldConfig [ ]
) : DataQueryResponse {
2022-06-08 08:27:58 -05:00
const { data , error , . . . rest } = response ;
2022-02-25 02:14:17 -06:00
// 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 ;
} ) ;
2022-04-12 04:58:48 -05:00
const queryMap = new Map ( queries . map ( ( query ) = > [ query . refId , query ] ) ) ;
2022-02-25 02:14:17 -06:00
const { streamsFrames , metricInstantFrames , metricRangeFrames } = groupFrames ( dataFrames , queryMap ) ;
return {
. . . rest ,
2022-06-08 08:27:58 -05:00
error : improveError ( error , queryMap ) ,
2022-02-25 02:14:17 -06:00
data : [
. . . processMetricRangeFrames ( metricRangeFrames ) ,
. . . processMetricInstantFrames ( metricInstantFrames ) ,
2022-05-11 02:29:04 -05:00
. . . processStreamsFrames ( streamsFrames , queryMap , derivedFieldConfigs ) ,
2022-02-25 02:14:17 -06:00
] ,
} ;
}