mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DataFrame: move labels to field (#19926)
This commit is contained in:
@@ -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) {
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
@@ -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",
|
||||
|
@@ -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>;
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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 = {
|
||||
|
@@ -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',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
@@ -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) {
|
||||
|
@@ -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 [],
|
||||
|
@@ -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 [],
|
||||
|
@@ -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[]) {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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',
|
||||
};
|
||||
|
@@ -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',
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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 => {
|
||||
|
@@ -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);
|
||||
|
@@ -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(
|
||||
|
@@ -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"}');
|
||||
|
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user