DataFrame: move labels to field (#19926)

This commit is contained in:
Ryan McKinley
2019-11-07 07:50:45 -08:00
committed by GitHub
parent e487c68156
commit 818aa8eefa
22 changed files with 243 additions and 135 deletions

View File

@@ -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<T = any> implements DataFrame, MutableVector<T> {
name?: string;
labels?: Labels;
refId?: string;
meta?: QueryResultMeta;
@@ -36,13 +35,10 @@ export class MutableDataFrame<T = any> implements DataFrame, MutableVector<T> {
// 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<T = any> implements DataFrame, MutableVector<T> {
type,
config: f.config || {},
values: this.creator(buffer),
labels: f.labels,
};
if (type === FieldType.other) {

View File

@@ -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<TimeSeriesValue>(),
values: new ArrayVector<TimeSeriesValue>(values),
labels: timeSeries.tags,
},
{
name: 'Time',
type: FieldType.time,
config: {
unit: 'dateTimeAsIso',
},
values: new ArrayVector<number>(),
config: {},
values: new ArrayVector<number>(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,
};
}

View File

@@ -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",

View File

@@ -56,6 +56,8 @@ export interface Field<T = any, V = Vector<T>> {
type: FieldType;
config: FieldConfig;
values: V; // The raw field values
labels?: Labels;
/**
* Cache of reduced values
*/
@@ -75,7 +77,6 @@ export interface Field<T = any, V = Vector<T>> {
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<T = any> {
type?: FieldType;
config?: FieldConfig;
values?: Vector<T> | T[]; // toJSON will always be T[], input could be either
labels?: Labels;
}
/**
@@ -96,6 +98,5 @@ export interface FieldDTO<T = any> {
*/
export interface DataFrameDTO extends QueryResultBase {
name?: string;
labels?: Labels;
fields: Array<FieldDTO | Field>;
}

View File

@@ -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,

View File

@@ -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 = {

View File

@@ -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',
},
},
],
}),

View File

@@ -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) {

View File

@@ -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 [],

View File

@@ -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 [],

View File

@@ -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[]) {

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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());
}
}

View File

@@ -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<Fi
scopedVars['__series'] = {
value: {
name: dataFrame.name,
labels: dataFrame.labels,
refId: dataFrame.refId,
},
text: 'Series',
@@ -66,6 +65,7 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
scopedVars['__field'] = {
value: {
name: field.name,
labels: field.labels,
},
text: 'Field',
};

View File

@@ -27,12 +27,12 @@ const timeRangeVars = [
},
];
const fieldVars = [
const seriesVars = [
{
value: `${DataLinkBuiltInVars.fieldName}`,
value: `${DataLinkBuiltInVars.seriesName}`,
label: 'Name',
documentation: 'Field name of the clicked datapoint (in ms epoch)',
origin: VariableOrigin.Field,
documentation: 'Name of the series',
origin: VariableOrigin.Series,
},
];
@@ -76,41 +76,51 @@ export const getPanelLinksVariableSuggestions = (): VariableSuggestion[] => [
...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',

View File

@@ -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);
}
}

View File

@@ -410,7 +410,12 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
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 => {

View File

@@ -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);

View File

@@ -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(

View File

@@ -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"}');

View File

@@ -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}`);
}
}
}