From 373e86965276d46f9e51d7f1ab7b17694e41ba6a Mon Sep 17 00:00:00 2001 From: Besart Berisha Date: Fri, 17 Jul 2020 10:30:27 +0200 Subject: [PATCH] Explore: show meta data when available (#25798) * Return valid metadata even if data is empty * Make sure fields is not accessed when there is no data * Meta Info should depend only on meta data * Refactor condition for better reading * Change data to empty array for better condition handling * Remove unecessary checks * Add test for showing metaData even when data is empty --- public/app/core/logs_model.test.ts | 33 ++++++++++++- public/app/core/logs_model.ts | 46 +++++++++++-------- .../features/dashboard/state/runRequest.ts | 8 ++-- public/app/features/explore/Logs.tsx | 2 +- .../datasource/loki/result_transformer.ts | 28 ++++++++--- public/app/plugins/panel/table/TablePanel.tsx | 3 +- 6 files changed, 89 insertions(+), 31 deletions(-) diff --git a/public/app/core/logs_model.test.ts b/public/app/core/logs_model.test.ts index 8f0840a7972..407c117b04d 100644 --- a/public/app/core/logs_model.test.ts +++ b/public/app/core/logs_model.test.ts @@ -8,7 +8,7 @@ import { MutableDataFrame, toDataFrame, } from '@grafana/data'; -import { dataFrameToLogsModel, dedupLogRows, getSeriesProperties } from './logs_model'; +import { dataFrameToLogsModel, dedupLogRows, getSeriesProperties, logSeriesToLogsModel } from './logs_model'; describe('dedupLogRows()', () => { test('should return rows as is when dedup is set to none', () => { @@ -596,6 +596,37 @@ describe('dataFrameToLogsModel', () => { }); }); +describe('logSeriesToLogsModel', () => { + it('should return correct metaData even if the data is empty', () => { + const logSeries: DataFrame[] = [ + { + fields: [], + length: 0, + refId: 'A', + + meta: { + searchWords: ['test'], + limit: 1000, + stats: [{ displayName: 'Summary: total bytes processed', value: 97048, unit: 'decbytes' }], + custom: { lokiQueryStatKey: 'Summary: total bytes processed' }, + preferredVisualisationType: 'logs', + }, + }, + ]; + + const metaData = { + hasUniqueLabels: false, + meta: [ + { label: 'Limit', value: '1000 (0 returned)', kind: 1 }, + { label: 'Total bytes processed', value: '97 kB', kind: 1 }, + ], + rows: [], + }; + + expect(logSeriesToLogsModel(logSeries)).toMatchObject(metaData); + }); +}); + describe('getSeriesProperties()', () => { it('sets a minimum bucket size', () => { const result = getSeriesProperties([], 2, undefined, 3, 123); diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index 8e80f04d31d..c8f9c470700 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -269,7 +269,7 @@ function separateLogsAndMetrics(dataFrames: DataFrame[]) { const logSeries: DataFrame[] = []; for (const dataFrame of dataFrames) { - if (isLogsData(dataFrame)) { + if (isLogsData(dataFrame) || !dataFrame.fields.length) { logSeries.push(dataFrame); continue; } @@ -303,23 +303,29 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi const allLabels: Labels[] = []; // Find the fields we care about and collect all labels - const allSeries: LogFields[] = logSeries.map(series => { - const fieldCache = new FieldCache(series); - const stringField = fieldCache.getFirstFieldOfType(FieldType.string); - if (stringField?.labels) { - allLabels.push(stringField.labels); - } - return { - series, - timeField: fieldCache.getFirstFieldOfType(FieldType.time), - timeNanosecondField: fieldCache.hasFieldWithNameAndType('tsNs', FieldType.time) - ? fieldCache.getFieldByName('tsNs') - : undefined, - stringField, - logLevelField: fieldCache.getFieldByName('level'), - idField: getIdField(fieldCache), - } as LogFields; - }); + let allSeries: LogFields[] = []; + + if (hasFields(logSeries)) { + allSeries = logSeries.map(series => { + const fieldCache = new FieldCache(series); + const stringField = fieldCache.getFirstFieldOfType(FieldType.string); + + if (stringField?.labels) { + allLabels.push(stringField.labels); + } + + return { + series, + timeField: fieldCache.getFirstFieldOfType(FieldType.time), + timeNanosecondField: fieldCache.hasFieldWithNameAndType('tsNs', FieldType.time) + ? fieldCache.getFieldByName('tsNs') + : undefined, + stringField, + logLevelField: fieldCache.getFieldByName('level'), + idField: getIdField(fieldCache), + } as LogFields; + }); + } const commonLabels = allLabels.length > 0 ? findCommonLabels(allLabels) : {}; @@ -445,6 +451,10 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi }; } +function hasFields(logSeries: DataFrame[]): boolean { + return logSeries.some(series => series.fields.length); +} + function getIdField(fieldCache: FieldCache): FieldWithIndex | undefined { const idFieldNames = ['id']; for (const fieldName of idFieldNames) { diff --git a/public/app/features/dashboard/state/runRequest.ts b/public/app/features/dashboard/state/runRequest.ts index a1a9b5030e3..e5206165a49 100644 --- a/public/app/features/dashboard/state/runRequest.ts +++ b/public/app/features/dashboard/state/runRequest.ts @@ -171,9 +171,11 @@ export function getProcessedDataFrames(results?: DataQueryResponseData[]): DataF for (const result of results) { const dataFrame = guessFieldTypes(toDataFrame(result)); - // clear out the cached info - for (const field of dataFrame.fields) { - field.state = null; + if (dataFrame.fields && dataFrame.fields.length) { + // clear out the cached info + for (const field of dataFrame.fields) { + field.state = null; + } } dataFrames.push(dataFrame); diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index e47d8111337..7d4b907ab92 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -225,7 +225,7 @@ export class Logs extends PureComponent { - {hasData && meta && ( + {meta && ( { return { diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index 9e8ac9711bf..6768874730e 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -312,22 +312,36 @@ export function lokiStreamsToDataframes( lokiQueryStatKey: 'Summary: total bytes processed', }; + const meta: QueryResultMeta = { + searchWords: getHighlighterExpressionsFromQuery(formatQuery(target.expr)), + limit, + stats, + custom, + preferredVisualisationType: 'logs', + }; + const series: DataFrame[] = data.map(stream => { const dataFrame = lokiStreamResultToDataFrame(stream, reverse); enhanceDataFrame(dataFrame, config); + return { ...dataFrame, refId: target.refId, - meta: { - searchWords: getHighlighterExpressionsFromQuery(formatQuery(target.expr)), - limit, - stats, - custom, - preferredVisualisationType: 'logs', - }, + meta, }; }); + if (stats.length && !data.length) { + return [ + { + fields: [], + length: 0, + refId: target.refId, + meta, + }, + ]; + } + return series; } diff --git a/public/app/plugins/panel/table/TablePanel.tsx b/public/app/plugins/panel/table/TablePanel.tsx index 49d424470dc..c979a1904aa 100644 --- a/public/app/plugins/panel/table/TablePanel.tsx +++ b/public/app/plugins/panel/table/TablePanel.tsx @@ -107,8 +107,9 @@ export class TablePanel extends Component { const { data, height, width } = this.props; const count = data.series?.length; + const hasFields = data.series[0]?.fields.length; - if (!count || count < 1) { + if (!count || !hasFields) { return
No data
; }