diff --git a/packages/grafana-data/src/dataframe/MutableDataFrame.ts b/packages/grafana-data/src/dataframe/MutableDataFrame.ts index 6c192cfcd5b..834f7aa96a0 100644 --- a/packages/grafana-data/src/dataframe/MutableDataFrame.ts +++ b/packages/grafana-data/src/dataframe/MutableDataFrame.ts @@ -1,5 +1,5 @@ import { Field, DataFrame, DataFrameDTO, FieldDTO, FieldType } from '../types/dataFrame'; -import { KeyValue, QueryResultMeta, Labels } from '../types/data'; +import { KeyValue, QueryResultMeta } from '../types/data'; import { guessFieldTypeFromValue, guessFieldTypeForField, toDataFrameDTO } from './processDataFrame'; import isArray from 'lodash/isArray'; import isString from 'lodash/isString'; @@ -16,7 +16,6 @@ export const MISSING_VALUE: any = null; export class MutableDataFrame implements DataFrame, MutableVector { name?: string; - labels?: Labels; refId?: string; meta?: QueryResultMeta; @@ -36,13 +35,10 @@ export class MutableDataFrame implements DataFrame, MutableVector { // Copy values from if (source) { - const { name, labels, refId, meta, fields } = source; + const { name, refId, meta, fields } = source; if (name) { this.name = name; } - if (labels) { - this.labels = labels; - } if (refId) { this.refId = refId; } @@ -116,6 +112,7 @@ export class MutableDataFrame implements DataFrame, MutableVector { type, config: f.config || {}, values: this.creator(buffer), + labels: f.labels, }; if (type === FieldType.other) { diff --git a/packages/grafana-data/src/dataframe/processDataFrame.ts b/packages/grafana-data/src/dataframe/processDataFrame.ts index b374e84d211..e7854629179 100644 --- a/packages/grafana-data/src/dataframe/processDataFrame.ts +++ b/packages/grafana-data/src/dataframe/processDataFrame.ts @@ -57,6 +57,13 @@ function convertTableToDataFrame(table: TableData): DataFrame { } function convertTimeSeriesToDataFrame(timeSeries: TimeSeries): DataFrame { + const times: number[] = []; + const values: TimeSeriesValue[] = []; + for (const point of timeSeries.datapoints) { + values.push(point[0]); + times.push(point[1] as number); + } + const fields = [ { name: timeSeries.target || 'Value', @@ -64,30 +71,23 @@ function convertTimeSeriesToDataFrame(timeSeries: TimeSeries): DataFrame { config: { unit: timeSeries.unit, }, - values: new ArrayVector(), + values: new ArrayVector(values), + labels: timeSeries.tags, }, { name: 'Time', type: FieldType.time, - config: { - unit: 'dateTimeAsIso', - }, - values: new ArrayVector(), + config: {}, + values: new ArrayVector(times), }, ]; - for (const point of timeSeries.datapoints) { - fields[0].values.buffer.push(point[0]); - fields[1].values.buffer.push(point[1] as number); - } - return { name: timeSeries.target, - labels: timeSeries.tags, refId: timeSeries.refId, meta: timeSeries.meta, fields, - length: timeSeries.datapoints.length, + length: values.length, }; } @@ -132,6 +132,7 @@ function convertJSONDocumentDataToDataFrame(timeSeries: TimeSeries): DataFrame { { name: timeSeries.target, type: FieldType.other, + labels: timeSeries.tags, config: { unit: timeSeries.unit, filterable: (timeSeries as any).filterable, @@ -146,7 +147,6 @@ function convertJSONDocumentDataToDataFrame(timeSeries: TimeSeries): DataFrame { return { name: timeSeries.target, - labels: timeSeries.tags, refId: timeSeries.target, meta: { json: true }, fields, @@ -445,6 +445,7 @@ export function toDataFrameDTO(data: DataFrame): DataFrameDTO { type: f.type, config: f.config, values: f.values.toArray(), + labels: f.labels, }; }); @@ -453,6 +454,5 @@ export function toDataFrameDTO(data: DataFrame): DataFrameDTO { refId: data.refId, meta: data.meta, name: data.name, - labels: data.labels, }; } diff --git a/packages/grafana-data/src/transformations/transformers/__snapshots__/reduce.test.ts.snap b/packages/grafana-data/src/transformations/transformers/__snapshots__/reduce.test.ts.snap index fab0404045e..edd3cdb78a2 100644 --- a/packages/grafana-data/src/transformations/transformers/__snapshots__/reduce.test.ts.snap +++ b/packages/grafana-data/src/transformations/transformers/__snapshots__/reduce.test.ts.snap @@ -5,6 +5,7 @@ Object { "fields": Array [ Object { "config": Object {}, + "labels": undefined, "name": "Field", "type": "string", "values": Array [ @@ -16,6 +17,7 @@ Object { "config": Object { "title": "First", }, + "labels": undefined, "name": "first", "type": "number", "values": Array [ @@ -27,6 +29,7 @@ Object { "config": Object { "title": "Min", }, + "labels": undefined, "name": "min", "type": "number", "values": Array [ @@ -38,6 +41,7 @@ Object { "config": Object { "title": "Max", }, + "labels": undefined, "name": "max", "type": "number", "values": Array [ @@ -49,6 +53,7 @@ Object { "config": Object { "title": "Delta", }, + "labels": undefined, "name": "delta", "type": "number", "values": Array [ @@ -57,7 +62,6 @@ Object { ], }, ], - "labels": undefined, "meta": Object { "transformations": Array [ "reduce", diff --git a/packages/grafana-data/src/types/dataFrame.ts b/packages/grafana-data/src/types/dataFrame.ts index 60151c8435f..2613b763445 100644 --- a/packages/grafana-data/src/types/dataFrame.ts +++ b/packages/grafana-data/src/types/dataFrame.ts @@ -56,6 +56,8 @@ export interface Field> { type: FieldType; config: FieldConfig; values: V; // The raw field values + labels?: Labels; + /** * Cache of reduced values */ @@ -75,7 +77,6 @@ export interface Field> { export interface DataFrame extends QueryResultBase { name?: string; fields: Field[]; // All fields of equal length - labels?: Labels; // The number of rows length: number; @@ -89,6 +90,7 @@ export interface FieldDTO { type?: FieldType; config?: FieldConfig; values?: Vector | T[]; // toJSON will always be T[], input could be either + labels?: Labels; } /** @@ -96,6 +98,5 @@ export interface FieldDTO { */ export interface DataFrameDTO extends QueryResultBase { name?: string; - labels?: Labels; fields: Array; } diff --git a/packages/grafana-data/src/utils/__snapshots__/csv.test.ts.snap b/packages/grafana-data/src/utils/__snapshots__/csv.test.ts.snap index 550e3629019..9a7360d0150 100644 --- a/packages/grafana-data/src/utils/__snapshots__/csv.test.ts.snap +++ b/packages/grafana-data/src/utils/__snapshots__/csv.test.ts.snap @@ -5,6 +5,7 @@ Object { "fields": Array [ Object { "config": Object {}, + "labels": undefined, "name": "Field 1", "type": "string", "values": Array [ @@ -16,6 +17,7 @@ Object { }, Object { "config": Object {}, + "labels": undefined, "name": "Field 2", "type": "number", "values": Array [ @@ -27,6 +29,7 @@ Object { }, Object { "config": Object {}, + "labels": undefined, "name": "Field 3", "type": "number", "values": Array [ @@ -38,6 +41,7 @@ Object { }, Object { "config": Object {}, + "labels": undefined, "name": "Field 4", "type": "number", "values": Array [ @@ -48,7 +52,6 @@ Object { ], }, ], - "labels": undefined, "meta": undefined, "name": undefined, "refId": undefined, @@ -60,6 +63,7 @@ Object { "fields": Array [ Object { "config": Object {}, + "labels": undefined, "name": "a", "type": "number", "values": Array [ @@ -69,6 +73,7 @@ Object { }, Object { "config": Object {}, + "labels": undefined, "name": "b", "type": "number", "values": Array [ @@ -78,6 +83,7 @@ Object { }, Object { "config": Object {}, + "labels": undefined, "name": "c", "type": "number", "values": Array [ @@ -86,7 +92,6 @@ Object { ], }, ], - "labels": undefined, "meta": undefined, "name": undefined, "refId": undefined, @@ -100,6 +105,7 @@ Object { "config": Object { "unit": "ms", }, + "labels": undefined, "name": "a", "type": "number", "values": Array [ @@ -113,6 +119,7 @@ Object { "config": Object { "unit": "lengthm", }, + "labels": undefined, "name": "b", "type": "number", "values": Array [ @@ -126,6 +133,7 @@ Object { "config": Object { "unit": "s", }, + "labels": undefined, "name": "c", "type": "boolean", "values": Array [ @@ -136,7 +144,6 @@ Object { ], }, ], - "labels": undefined, "meta": undefined, "name": undefined, "refId": undefined, diff --git a/packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.ts b/packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.ts index decf22ee3b9..e9239d36b72 100644 --- a/packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.ts +++ b/packages/grafana-ui/src/components/Logs/LogRowContextProvider.test.ts @@ -6,18 +6,16 @@ describe('getRowContexts', () => { it('then the result should be in correct format', async () => { const firstResult = new MutableDataFrame({ refId: 'B', - labels: {}, fields: [ { name: 'ts', type: FieldType.time, values: [3, 2, 1] }, - { name: 'line', type: FieldType.string, values: ['3', '2', '1'] }, + { name: 'line', type: FieldType.string, values: ['3', '2', '1'], labels: {} }, ], }); const secondResult = new MutableDataFrame({ refId: 'B', - labels: {}, fields: [ { name: 'ts', type: FieldType.time, values: [6, 5, 4] }, - { name: 'line', type: FieldType.string, values: ['6', '5', '4'] }, + { name: 'line', type: FieldType.string, values: ['6', '5', '4'], labels: {} }, ], }); const row: LogRowModel = { diff --git a/public/app/core/logs_model.test.ts b/public/app/core/logs_model.test.ts index 661021ec23d..aa4f5289996 100644 --- a/public/app/core/logs_model.test.ts +++ b/public/app/core/logs_model.test.ts @@ -189,10 +189,6 @@ describe('dataFrameToLogsModel', () => { it('given one series should return expected logs model', () => { const series: DataFrame[] = [ new MutableDataFrame({ - labels: { - filename: '/var/log/grafana/grafana.log', - job: 'grafana', - }, fields: [ { name: 'time', @@ -206,6 +202,10 @@ describe('dataFrameToLogsModel', () => { 't=2019-04-26T11:05:28+0200 lvl=info msg="Initializing DatasourceCacheService" logger=server', 't=2019-04-26T16:42:50+0200 lvl=eror msg="new token…t unhashed token=56d9fdc5c8b7400bd51b060eea8ca9d7', ], + labels: { + filename: '/var/log/grafana/grafana.log', + job: 'grafana', + }, }, { name: 'id', @@ -244,7 +244,7 @@ describe('dataFrameToLogsModel', () => { expect(logsModel.meta).toHaveLength(2); expect(logsModel.meta[0]).toMatchObject({ label: 'Common labels', - value: series[0].labels, + value: series[0].fields[1].labels, kind: LogsMetaKind.LabelsMap, }); expect(logsModel.meta[1]).toMatchObject({ @@ -291,11 +291,6 @@ describe('dataFrameToLogsModel', () => { it('given multiple series with unique times should return expected logs model', () => { const series: DataFrame[] = [ toDataFrame({ - labels: { - foo: 'bar', - baz: '1', - level: 'dbug', - }, fields: [ { name: 'ts', @@ -306,16 +301,16 @@ describe('dataFrameToLogsModel', () => { name: 'line', type: FieldType.string, values: ['WARN boooo'], + labels: { + foo: 'bar', + baz: '1', + level: 'dbug', + }, }, ], }), toDataFrame({ name: 'logs', - labels: { - foo: 'bar', - baz: '2', - level: 'err', - }, fields: [ { name: 'time', @@ -326,6 +321,11 @@ describe('dataFrameToLogsModel', () => { name: 'message', type: FieldType.string, values: ['INFO 1', 'INFO 2'], + labels: { + foo: 'bar', + baz: '2', + level: 'err', + }, }, ], }), @@ -367,11 +367,6 @@ describe('dataFrameToLogsModel', () => { it('given multiple series with equal times should return expected logs model', () => { const series: DataFrame[] = [ toDataFrame({ - labels: { - foo: 'bar', - baz: '1', - level: 'dbug', - }, fields: [ { name: 'ts', @@ -382,15 +377,15 @@ describe('dataFrameToLogsModel', () => { name: 'line', type: FieldType.string, values: ['WARN boooo 1'], + labels: { + foo: 'bar', + baz: '1', + level: 'dbug', + }, }, ], }), toDataFrame({ - labels: { - foo: 'bar', - baz: '2', - level: 'dbug', - }, fields: [ { name: 'ts', @@ -401,16 +396,16 @@ describe('dataFrameToLogsModel', () => { name: 'line', type: FieldType.string, values: ['WARN boooo 2'], + labels: { + foo: 'bar', + baz: '2', + level: 'dbug', + }, }, ], }), toDataFrame({ name: 'logs', - labels: { - foo: 'bar', - baz: '2', - level: 'err', - }, fields: [ { name: 'time', @@ -421,6 +416,11 @@ describe('dataFrameToLogsModel', () => { name: 'message', type: FieldType.string, values: ['INFO 1', 'INFO 2'], + labels: { + foo: 'bar', + baz: '2', + level: 'err', + }, }, ], }), diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index 138641e5d09..cb979c151ef 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -244,6 +244,15 @@ function separateLogsAndMetrics(dataFrame: DataFrame[]) { const logTimeFormat = 'YYYY-MM-DD HH:mm:ss'; +interface LogFields { + series: DataFrame; + + timeField: FieldWithIndex; + stringField: FieldWithIndex; + logLevelField?: FieldWithIndex; + idField?: FieldWithIndex; +} + /** * Converts dataFrames into LogsModel. This involves merging them into one list, sorting them and computing metadata * like common labels. @@ -252,29 +261,43 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi if (logSeries.length === 0) { return undefined; } - const commonLabels = findCommonLabelsFromDataFrames(logSeries); + const allLabels: Labels[] = []; + + // Find the fields we care about and collect all labels + const allSeries: LogFields[] = logSeries.map(series => { + const fieldCache = new FieldCache(series); + + // Assume the first string field in the dataFrame is the message. This was right so far but probably needs some + // more explicit checks. + const stringField = fieldCache.getFirstFieldOfType(FieldType.string); + if (stringField.labels) { + allLabels.push(stringField.labels); + } + return { + series, + timeField: fieldCache.getFirstFieldOfType(FieldType.time), + stringField, + logLevelField: fieldCache.getFieldByName('level'), + idField: getIdField(fieldCache), + }; + }); + + const commonLabels = allLabels.length > 0 ? findCommonLabels(allLabels) : {}; const rows: LogRowModel[] = []; let hasUniqueLabels = false; - for (let i = 0; i < logSeries.length; i++) { - const series = logSeries[i]; - const fieldCache = new FieldCache(series); - const uniqueLabels = findUniqueLabels(series.labels, commonLabels); + for (const info of allSeries) { + const { timeField, stringField, logLevelField, idField, series } = info; + const labels = stringField.labels; + const uniqueLabels = findUniqueLabels(labels, commonLabels); if (Object.keys(uniqueLabels).length > 0) { hasUniqueLabels = true; } - const timeField = fieldCache.getFirstFieldOfType(FieldType.time); - // Assume the first string field in the dataFrame is the message. This was right so far but probably needs some - // more explicit checks. - const stringField = fieldCache.getFirstFieldOfType(FieldType.string); - const logLevelField = fieldCache.getFieldByName('level'); - const idField = getIdField(fieldCache); - let seriesLogLevel: LogLevel | undefined = undefined; - if (series.labels && Object.keys(series.labels).indexOf('level') !== -1) { - seriesLogLevel = getLogLevelFromKey(series.labels['level']); + if (labels && Object.keys(labels).indexOf('level') !== -1) { + seriesLogLevel = getLogLevelFromKey(labels['level']); } for (let j = 0; j < series.length; j++) { @@ -311,7 +334,7 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi searchWords, entry: hasAnsi ? ansicolor.strip(message) : message, raw: message, - labels: series.labels, + labels: stringField.labels, timestamp: ts, uid: idField ? idField.values.get(j) : j.toString(), }); @@ -345,21 +368,6 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel | undefi }; } -function findCommonLabelsFromDataFrames(logSeries: DataFrame[]): Labels { - const allLabels: Labels[] = []; - for (let n = 0; n < logSeries.length; n++) { - const series = logSeries[n]; - if (series.labels) { - allLabels.push(series.labels); - } - } - - if (allLabels.length > 0) { - return findCommonLabels(allLabels); - } - return {}; -} - function getIdField(fieldCache: FieldCache): FieldWithIndex | undefined { const idFieldNames = ['id']; for (const fieldName of idFieldNames) { diff --git a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap index 888c0d1e68d..eb049eb33d4 100644 --- a/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap +++ b/public/app/features/dashboard/containers/__snapshots__/DashboardPage.test.tsx.snap @@ -78,7 +78,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 20, + "schemaVersion": 21, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -191,7 +191,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 20, + "schemaVersion": 21, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -315,7 +315,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti ], "refresh": undefined, "revision": undefined, - "schemaVersion": 20, + "schemaVersion": 21, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -426,7 +426,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti ], "refresh": undefined, "revision": undefined, - "schemaVersion": 20, + "schemaVersion": 21, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -521,7 +521,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti ], "refresh": undefined, "revision": undefined, - "schemaVersion": 20, + "schemaVersion": 21, "snapshot": undefined, "style": "dark", "tags": Array [], diff --git a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap index 271b0f01b92..1bbc17c6073 100644 --- a/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap +++ b/public/app/features/dashboard/dashgrid/__snapshots__/DashboardGrid.test.tsx.snap @@ -232,7 +232,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 20, + "schemaVersion": 21, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -469,7 +469,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 20, + "schemaVersion": 21, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -706,7 +706,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 20, + "schemaVersion": 21, "snapshot": undefined, "style": "dark", "tags": Array [], @@ -943,7 +943,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = ` ], "refresh": undefined, "revision": undefined, - "schemaVersion": 20, + "schemaVersion": 21, "snapshot": undefined, "style": "dark", "tags": Array [], diff --git a/public/app/features/dashboard/state/DashboardMigrator.test.ts b/public/app/features/dashboard/state/DashboardMigrator.test.ts index 00bd5f22299..bb16e688274 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.test.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.test.ts @@ -128,7 +128,7 @@ describe('DashboardModel', () => { }); it('dashboard schema version should be set to latest', () => { - expect(model.schemaVersion).toBe(20); + expect(model.schemaVersion).toBe(21); }); it('graph thresholds should be migrated', () => { @@ -506,6 +506,51 @@ describe('DashboardModel', () => { }); }); }); + + describe('when migrating labels from DataFrame to Field', () => { + let model: any; + beforeEach(() => { + model = new DashboardModel({ + panels: [ + { + //graph panel + options: { + dataLinks: [ + { + url: 'http://mylink.com?series=${__series.labels}&${__series.labels.a}', + }, + ], + }, + }, + { + // panel with field options + options: { + fieldOptions: { + defaults: { + links: [ + { + url: 'http://mylink.com?series=${__series.labels}&${__series.labels.x}', + }, + ], + }, + }, + }, + }, + ], + }); + }); + + describe('data links', () => { + it('should replace __series.label variable with __field.label', () => { + expect(model.panels[0].options.dataLinks[0].url).toBe( + 'http://mylink.com?series=${__field.labels}&${__field.labels.a}' + ); + expect(model.panels[1].options.fieldOptions.defaults.links[0].url).toBe( + 'http://mylink.com?series=${__field.labels}&${__field.labels.x}' + ); + }); + }); + }); }); function createRow(options: any, panelDescriptions: any[]) { diff --git a/public/app/features/dashboard/state/DashboardMigrator.ts b/public/app/features/dashboard/state/DashboardMigrator.ts index 319f8538069..fc1039227db 100644 --- a/public/app/features/dashboard/state/DashboardMigrator.ts +++ b/public/app/features/dashboard/state/DashboardMigrator.ts @@ -33,7 +33,7 @@ export class DashboardMigrator { let i, j, k, n; const oldVersion = this.dashboard.schemaVersion; const panelUpgrades = []; - this.dashboard.schemaVersion = 20; + this.dashboard.schemaVersion = 21; if (oldVersion === this.dashboard.schemaVersion) { return; @@ -463,6 +463,28 @@ export class DashboardMigrator { }); } + if (oldVersion < 21) { + const updateLinks = (link: DataLink) => { + return { + ...link, + url: link.url.replace(/__series.labels/g, '__field.labels'), + }; + }; + panelUpgrades.push((panel: any) => { + // For graph panel + if (panel.options && panel.options.dataLinks && _.isArray(panel.options.dataLinks)) { + panel.options.dataLinks = panel.options.dataLinks.map(updateLinks); + } + + // For panel with fieldOptions + if (panel.options && panel.options.fieldOptions && panel.options.fieldOptions.defaults) { + if (panel.options.fieldOptions.defaults.links && _.isArray(panel.options.fieldOptions.defaults.links)) { + panel.options.fieldOptions.defaults.links = panel.options.fieldOptions.defaults.links.map(updateLinks); + } + } + }); + } + if (panelUpgrades.length === 0) { return; } diff --git a/public/app/features/expressions/__snapshots__/util.test.ts.snap b/public/app/features/expressions/__snapshots__/util.test.ts.snap index 9fb20ca4fd5..f95b3fd883c 100644 --- a/public/app/features/expressions/__snapshots__/util.test.ts.snap +++ b/public/app/features/expressions/__snapshots__/util.test.ts.snap @@ -6,6 +6,7 @@ Array [ "fields": Array [ Object { "config": Object {}, + "labels": undefined, "name": "Time", "type": "time", "values": Int32Array [ @@ -39,6 +40,7 @@ Array [ }, Object { "config": Object {}, + "labels": undefined, "name": "", "type": "number", "values": Float64Array [ @@ -58,7 +60,6 @@ Array [ ], }, ], - "labels": undefined, "meta": undefined, "name": undefined, "refId": undefined, @@ -67,6 +68,7 @@ Array [ "fields": Array [ Object { "config": Object {}, + "labels": undefined, "name": "Time", "type": "time", "values": Int32Array [ @@ -100,6 +102,7 @@ Array [ }, Object { "config": Object {}, + "labels": undefined, "name": "GB-series", "type": "number", "values": Float64Array [ @@ -119,7 +122,6 @@ Array [ ], }, ], - "labels": undefined, "meta": undefined, "name": undefined, "refId": undefined, diff --git a/public/app/features/expressions/util.test.ts b/public/app/features/expressions/util.test.ts index db10c90abb6..030d49d37ff 100644 --- a/public/app/features/expressions/util.test.ts +++ b/public/app/features/expressions/util.test.ts @@ -36,9 +36,10 @@ describe('GEL Utils', () => { test('should parse output with dataframe', () => { const frames = gelResponseToDataFrames(resp); for (const frame of frames) { - console.log('Frame', frame.refId + ' // ' + frame.labels); + console.log('Frame', frame.refId); for (const field of frame.fields) { - console.log(' > ', field.name, field.values.toArray()); + console.log(' > ', field.name, field.labels); + console.log(' (values)= ', field.values.toArray()); } } diff --git a/public/app/features/panel/panellinks/linkSuppliers.ts b/public/app/features/panel/panellinks/linkSuppliers.ts index a6f2d215835..84f0d140be7 100644 --- a/public/app/features/panel/panellinks/linkSuppliers.ts +++ b/public/app/features/panel/panellinks/linkSuppliers.ts @@ -13,12 +13,12 @@ import { getLinkSrv } from './link_srv'; interface SeriesVars { name?: string; - labels?: Labels; refId?: string; } interface FieldVars { name: string; + labels?: Labels; } interface ValueVars { @@ -54,7 +54,6 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier [ ...timeRangeVars, ]; -const getSeriesVars = (dataFrames: DataFrame[]) => { - const labels = _.chain(dataFrames.map(df => Object.keys(df.labels || {}))) +const getFieldVars = (dataFrames: DataFrame[]) => { + const all = []; + for (const df of dataFrames) { + for (const f of df.fields) { + if (f.labels) { + for (const k of Object.keys(f.labels)) { + all.push(k); + } + } + } + } + + const labels = _.chain(all) .flatten() .uniq() .value(); return [ { - value: `${DataLinkBuiltInVars.seriesName}`, + value: `${DataLinkBuiltInVars.fieldName}`, label: 'Name', - documentation: 'Name of the series', - origin: VariableOrigin.Series, + documentation: 'Field name of the clicked datapoint (in ms epoch)', + origin: VariableOrigin.Field, }, ...labels.map(label => ({ - value: `__series.labels${buildLabelPath(label)}`, + value: `__field.labels${buildLabelPath(label)}`, label: `labels.${label}`, documentation: `${label} label value`, - origin: VariableOrigin.Series, + origin: VariableOrigin.Field, })), ]; }; export const getDataLinksVariableSuggestions = (dataFrames: DataFrame[]): VariableSuggestion[] => { - const seriesVars = getSeriesVars(dataFrames); + const fieldVars = getFieldVars(dataFrames); const valueTimeVar = { value: `${DataLinkBuiltInVars.valueTime}`, label: 'Time', documentation: 'Time value of the clicked datapoint (in ms epoch)', origin: VariableOrigin.Value, }; - return [...seriesVars, ...fieldVars, ...valueVars, valueTimeVar, ...getPanelLinksVariableSuggestions()]; }; export const getCalculationValueDataLinksVariableSuggestions = (dataFrames: DataFrame[]): VariableSuggestion[] => { - const seriesVars = getSeriesVars(dataFrames); + const fieldVars = getFieldVars(dataFrames); const valueCalcVar = { value: `${DataLinkBuiltInVars.valueCalc}`, label: 'Calculation name', diff --git a/public/app/plugins/datasource/elasticsearch/elastic_response.ts b/public/app/plugins/datasource/elasticsearch/elastic_response.ts index 32b69c32a56..9ba53f73d12 100644 --- a/public/app/plugins/datasource/elasticsearch/elastic_response.ts +++ b/public/app/plugins/datasource/elasticsearch/elastic_response.ts @@ -530,7 +530,6 @@ export class ElasticResponse { for (let y = 0; y < tmpSeriesList.length; y++) { const series = toDataFrame(tmpSeriesList[y]); - series.labels = {}; dataFrame.push(series); } } diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index f3b771a628b..a7927350499 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -410,7 +410,12 @@ export class LokiDatasource extends DataSourceApi { const annotations: AnnotationEvent[] = []; for (const frame of data) { - const tags = Object.values(frame.labels) as string[]; + const tags: string[] = []; + for (const field of frame.fields) { + if (field.labels) { + tags.push.apply(tags, Object.values(field.labels)); + } + } const view = new DataFrameView<{ ts: string; line: string }>(frame); view.forEachRow(row => { diff --git a/public/app/plugins/datasource/loki/live_streams.test.ts b/public/app/plugins/datasource/loki/live_streams.test.ts index d153ed8dca2..214f2e837b6 100644 --- a/public/app/plugins/datasource/loki/live_streams.test.ts +++ b/public/app/plugins/datasource/loki/live_streams.test.ts @@ -42,7 +42,7 @@ describe('Live Stream Tests', () => { const tests = [ (val: DataFrame[]) => { expect(val[0].length).toEqual(7); - expect(val[0].labels).toEqual(labels); + expect(val[0].fields[1].labels).toEqual(labels); }, (val: DataFrame[]) => { expect(val[0].length).toEqual(8); diff --git a/public/app/plugins/datasource/loki/live_streams.ts b/public/app/plugins/datasource/loki/live_streams.ts index 9c84284ecd9..5417ec31dec 100644 --- a/public/app/plugins/datasource/loki/live_streams.ts +++ b/public/app/plugins/datasource/loki/live_streams.ts @@ -27,10 +27,9 @@ export class LiveStreams { let stream = this.streams[target.url]; if (!stream) { const data = new CircularDataFrame({ capacity: target.size }); - data.labels = parseLabels(target.query); data.addField({ name: 'ts', type: FieldType.time, config: { title: 'Time' } }); - data.addField({ name: 'line', type: FieldType.string }); - data.addField({ name: 'labels', type: FieldType.other }); + data.addField({ name: 'line', type: FieldType.string }).labels = parseLabels(target.query); + data.addField({ name: 'labels', type: FieldType.other }); // The labels for each line data.addField({ name: 'id', type: FieldType.string }); stream = webSocket(target.url).pipe( diff --git a/public/app/plugins/datasource/loki/result_transformer.test.ts b/public/app/plugins/datasource/loki/result_transformer.test.ts index 9cbd62d8eb0..ae809a5d3e6 100644 --- a/public/app/plugins/datasource/loki/result_transformer.test.ts +++ b/public/app/plugins/datasource/loki/result_transformer.test.ts @@ -28,7 +28,7 @@ describe('logStreamToDataFrame', () => { const data = streams.map(stream => logStreamToDataFrame(stream)); expect(data.length).toBe(2); - expect(data[0].labels['foo']).toEqual('bar'); + expect(data[0].fields[1].labels['foo']).toEqual('bar'); expect(data[0].fields[0].values.get(0)).toEqual(streams[0].entries[0].ts); expect(data[0].fields[1].values.get(0)).toEqual(streams[0].entries[0].line); expect(data[0].fields[2].values.get(0)).toEqual('1970-01-01T00:00:00Z_{foo="bar"}'); diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index 209c64836b1..839ccd8cfa5 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -35,10 +35,9 @@ export function logStreamToDataFrame(stream: LokiLogsStream, reverse?: boolean, return { refId, - labels, fields: [ { name: 'ts', type: FieldType.time, config: { title: 'Time' }, values: times }, // Time - { name: 'line', type: FieldType.string, config: {}, values: lines }, // Line + { name: 'line', type: FieldType.string, config: {}, values: lines, labels }, // Line { name: 'id', type: FieldType.string, config: {}, values: uids }, ], length: times.length, @@ -57,18 +56,29 @@ export function appendResponseToBufferedData(response: LokiResponse, data: Mutab const streams: LokiLogsStream[] = response.streams; if (streams && streams.length) { + const { values } = data; + let baseLabels: Labels = {}; + for (const f of data.fields) { + if (f.type === FieldType.string) { + if (f.labels) { + baseLabels = f.labels; + } + break; + } + } + for (const stream of streams) { // Find unique labels const labels = parseLabels(stream.labels); - const unique = findUniqueLabels(labels, data.labels || {}); + const unique = findUniqueLabels(labels, baseLabels); // Add each line for (const entry of stream.entries) { const ts = entry.ts || entry.timestamp; - data.values.ts.add(ts); - data.values.line.add(entry.line); - data.values.labels.add(unique); - data.values.id.add(`${ts}_${stream.labels}`); + values.ts.add(ts); + values.line.add(entry.line); + values.labels.add(unique); + values.id.add(`${ts}_${stream.labels}`); } } }