From ddabf4ade1db0c4491aff227d5aa22b7dca7a0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 25 Aug 2020 09:40:49 +0200 Subject: [PATCH] Transforms: Labels to fields rewrite that uses merge transform inside it (#27125) * testing things * POC: Simplify labels to fields by using merge transform inside it * removed old code * Fixed test ts issues * Added valueLabel field option * Updated merge transform tests to not expect a sort * fixed type * refactoring to minimize nesting --- .../transformers/labelsToFields.test.ts | 266 +++++------------- .../transformers/labelsToFields.ts | 203 ++++--------- .../transformers/merge.test.ts | 38 +-- .../src/transformations/transformers/merge.ts | 12 +- .../LabelsToFieldsTransformerEditor.tsx | 51 +++- 5 files changed, 191 insertions(+), 379 deletions(-) diff --git a/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts b/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts index 264997c79d6..aa2452f4f8d 100644 --- a/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts +++ b/packages/grafana-data/src/transformations/transformers/labelsToFields.test.ts @@ -1,17 +1,75 @@ import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; import { LabelsToFieldsOptions, labelsToFieldsTransformer } from './labelsToFields'; -import { DataTransformerConfig, Field, FieldType } from '../../types'; +import { DataTransformerConfig, FieldType, FieldDTO } from '../../types'; import { DataTransformerID } from './ids'; -import { toDataFrame } from '../../dataframe'; +import { toDataFrame, toDataFrameDTO } from '../../dataframe'; import { transformDataFrame } from '../transformDataFrame'; -import { ArrayVector } from '../../vector'; describe('Labels as Columns', () => { beforeAll(() => { mockTransformationsRegistry([labelsToFieldsTransformer]); }); - it('data frame with 1 value and 1 label', () => { + it('data frame with two labels', () => { + const cfg: DataTransformerConfig = { + id: DataTransformerID.labelsToFields, + options: {}, + }; + + const source = toDataFrame({ + name: 'A', + fields: [ + { name: 'time', type: FieldType.time, values: [1000, 2000] }, + { name: 'Value', type: FieldType.number, values: [1, 2], labels: { location: 'inside', feelsLike: 'ok' } }, + ], + }); + + const result = toDataFrameDTO(transformDataFrame([cfg], [source])[0]); + const expected: FieldDTO[] = [ + { name: 'time', type: FieldType.time, values: [1000, 2000], config: {} }, + { + name: 'location', + type: FieldType.string, + values: ['inside', 'inside'], + config: {}, + }, + { name: 'feelsLike', type: FieldType.string, values: ['ok', 'ok'], config: {} }, + { name: 'Value', type: FieldType.number, values: [1, 2], config: {} }, + ]; + + expect(result.fields).toEqual(expected); + }); + + it('data frame with two labels and valueLabel option', () => { + const cfg: DataTransformerConfig = { + id: DataTransformerID.labelsToFields, + options: { valueLabel: 'name' }, + }; + + const source = toDataFrame({ + name: 'A', + fields: [ + { name: 'time', type: FieldType.time, values: [1000, 2000] }, + { name: 'Value', type: FieldType.number, values: [1, 2], labels: { location: 'inside', name: 'Request' } }, + ], + }); + + const result = toDataFrameDTO(transformDataFrame([cfg], [source])[0]); + const expected: FieldDTO[] = [ + { name: 'time', type: FieldType.time, values: [1000, 2000], config: {} }, + { + name: 'location', + type: FieldType.string, + values: ['inside', 'inside'], + config: {}, + }, + { name: 'Request', type: FieldType.number, values: [1, 2], config: {} }, + ]; + + expect(result.fields).toEqual(expected); + }); + + it('two data frames with 1 value and 1 label', () => { const cfg: DataTransformerConfig = { id: DataTransformerID.labelsToFields, options: {}, @@ -33,200 +91,14 @@ describe('Labels as Columns', () => { ], }); - const result = transformDataFrame([cfg], [oneValueOneLabelA, oneValueOneLabelB]); - const expected: Field[] = [ - { name: 'time', type: FieldType.time, values: new ArrayVector([1000, 2000]), config: {} }, - { name: 'location', type: FieldType.string, values: new ArrayVector(['inside', 'outside']), config: {} }, - { name: 'temp', type: FieldType.number, values: new ArrayVector([1, -1]), config: {} }, + const result = toDataFrameDTO(transformDataFrame([cfg], [oneValueOneLabelA, oneValueOneLabelB])[0]); + + const expected: FieldDTO[] = [ + { name: 'time', type: FieldType.time, values: [1000, 2000], config: {} }, + { name: 'location', type: FieldType.string, values: ['inside', 'outside'], config: {} }, + { name: 'temp', type: FieldType.number, values: [1, -1], config: {} }, ]; - expect(result[0].fields).toEqual(expected); - }); - - it('data frame with 2 values and 1 label', () => { - const cfg: DataTransformerConfig = { - id: DataTransformerID.labelsToFields, - options: {}, - }; - - const twoValuesOneLabelA = toDataFrame({ - name: 'A', - fields: [ - { name: 'time', type: FieldType.time, values: [1000] }, - { name: 'temp', type: FieldType.number, values: [1], labels: { location: 'inside' } }, - { name: 'humidity', type: FieldType.number, values: [10000], labels: { location: 'inside' } }, - ], - }); - - const twoValuesOneLabelB = toDataFrame({ - name: 'B', - fields: [ - { name: 'time', type: FieldType.time, values: [2000] }, - { name: 'temp', type: FieldType.number, values: [-1], labels: { location: 'outside' } }, - { name: 'humidity', type: FieldType.number, values: [11000], labels: { location: 'outside' } }, - ], - }); - - const result = transformDataFrame([cfg], [twoValuesOneLabelA, twoValuesOneLabelB]); - const expected: Field[] = [ - { name: 'time', type: FieldType.time, values: new ArrayVector([1000, 2000]), config: {} }, - { name: 'location', type: FieldType.string, values: new ArrayVector(['inside', 'outside']), config: {} }, - { name: 'temp', type: FieldType.number, values: new ArrayVector([1, -1]), config: {} }, - { name: 'humidity', type: FieldType.number, values: new ArrayVector([10000, 11000]), config: {} }, - ]; - - expect(result[0].fields).toEqual(expected); - }); - - it('data frame with 1 value and 2 labels', () => { - const cfg: DataTransformerConfig = { - id: DataTransformerID.labelsToFields, - options: {}, - }; - - const oneValueTwoLabelsA = toDataFrame({ - name: 'A', - fields: [ - { name: 'time', type: FieldType.time, values: [1000] }, - { name: 'temp', type: FieldType.number, values: [1], labels: { location: 'inside', area: 'living room' } }, - ], - }); - - const oneValueTwoLabelsB = toDataFrame({ - name: 'B', - fields: [ - { name: 'time', type: FieldType.time, values: [2000] }, - { name: 'temp', type: FieldType.number, values: [-1], labels: { location: 'outside', area: 'backyard' } }, - ], - }); - - const result = transformDataFrame([cfg], [oneValueTwoLabelsA, oneValueTwoLabelsB]); - const expected: Field[] = [ - { name: 'time', type: FieldType.time, values: new ArrayVector([1000, 2000]), config: {} }, - { name: 'location', type: FieldType.string, values: new ArrayVector(['inside', 'outside']), config: {} }, - { name: 'area', type: FieldType.string, values: new ArrayVector(['living room', 'backyard']), config: {} }, - { name: 'temp', type: FieldType.number, values: new ArrayVector([1, -1]), config: {} }, - ]; - - expect(result[0].fields).toEqual(expected); - }); - - it('data frame with 2 values and 2 labels', () => { - const cfg: DataTransformerConfig = { - id: DataTransformerID.labelsToFields, - options: {}, - }; - - const twoValuesTwoLabelsA = toDataFrame({ - name: 'A', - fields: [ - { name: 'time', type: FieldType.time, values: [1000] }, - { name: 'temp', type: FieldType.number, values: [1], labels: { location: 'inside', area: 'living room' } }, - { - name: 'humidity', - type: FieldType.number, - values: [10000], - labels: { location: 'inside', area: 'living room' }, - }, - ], - }); - - const twoValuesTwoLabelsB = toDataFrame({ - name: 'B', - fields: [ - { name: 'time', type: FieldType.time, values: [2000] }, - { name: 'temp', type: FieldType.number, values: [-1], labels: { location: 'outside', area: 'backyard' } }, - { - name: 'humidity', - type: FieldType.number, - values: [11000], - labels: { location: 'outside', area: 'backyard' }, - }, - ], - }); - - const result = transformDataFrame([cfg], [twoValuesTwoLabelsA, twoValuesTwoLabelsB]); - const expected: Field[] = [ - { name: 'time', type: FieldType.time, values: new ArrayVector([1000, 2000]), config: {} }, - { name: 'location', type: FieldType.string, values: new ArrayVector(['inside', 'outside']), config: {} }, - { name: 'area', type: FieldType.string, values: new ArrayVector(['living room', 'backyard']), config: {} }, - { name: 'temp', type: FieldType.number, values: new ArrayVector([1, -1]), config: {} }, - { name: 'humidity', type: FieldType.number, values: new ArrayVector([10000, 11000]), config: {} }, - ]; - - expect(result[0].fields).toEqual(expected); - }); - - it('data frames with different labels', () => { - const cfg: DataTransformerConfig = { - id: DataTransformerID.labelsToFields, - options: {}, - }; - - const oneValueDifferentLabelsA = toDataFrame({ - name: 'A', - fields: [ - { name: 'time', type: FieldType.time, values: [1000] }, - { name: 'temp', type: FieldType.number, values: [1], labels: { location: 'inside', feelsLike: 'ok' } }, - ], - }); - - const oneValueDifferentLabelsB = toDataFrame({ - name: 'B', - fields: [ - { name: 'time', type: FieldType.time, values: [2000] }, - { name: 'temp', type: FieldType.number, values: [-1], labels: { location: 'outside', sky: 'cloudy' } }, - ], - }); - - const result = transformDataFrame([cfg], [oneValueDifferentLabelsA, oneValueDifferentLabelsB]); - const expected: Field[] = [ - { name: 'time', type: FieldType.time, values: new ArrayVector([1000, 2000]), config: {} }, - { name: 'location', type: FieldType.string, values: new ArrayVector(['inside', 'outside']), config: {} }, - { name: 'feelsLike', type: FieldType.string, values: new ArrayVector(['ok', null]), config: {} }, - { name: 'sky', type: FieldType.string, values: new ArrayVector([null, 'cloudy']), config: {} }, - { name: 'temp', type: FieldType.number, values: new ArrayVector([1, -1]), config: {} }, - ]; - - expect(result[0].fields).toEqual(expected); - }); - - it('data frames with same timestamp and different labels', () => { - const cfg: DataTransformerConfig = { - id: DataTransformerID.labelsToFields, - options: {}, - }; - - const oneValueDifferentLabelsA = toDataFrame({ - name: 'A', - fields: [ - { name: 'time', type: FieldType.time, values: [1000, 2000] }, - { name: 'temp', type: FieldType.number, values: [1, 2], labels: { location: 'inside', feelsLike: 'ok' } }, - ], - }); - - const oneValueDifferentLabelsB = toDataFrame({ - name: 'B', - fields: [ - { name: 'time', type: FieldType.time, values: [1000, 2000] }, - { name: 'temp', type: FieldType.number, values: [-1, -2], labels: { location: 'outside', sky: 'cloudy' } }, - ], - }); - - const result = transformDataFrame([cfg], [oneValueDifferentLabelsA, oneValueDifferentLabelsB]); - const expected: Field[] = [ - { name: 'time', type: FieldType.time, values: new ArrayVector([1000, 1000, 2000, 2000]), config: {} }, - { - name: 'location', - type: FieldType.string, - values: new ArrayVector(['inside', 'outside', 'inside', 'outside']), - config: {}, - }, - { name: 'feelsLike', type: FieldType.string, values: new ArrayVector(['ok', null, 'ok', null]), config: {} }, - { name: 'sky', type: FieldType.string, values: new ArrayVector([null, 'cloudy', null, 'cloudy']), config: {} }, - { name: 'temp', type: FieldType.number, values: new ArrayVector([1, -1, 2, -2]), config: {} }, - ]; - - expect(result[0].fields).toEqual(expected); + expect(result.fields).toEqual(expected); }); }); diff --git a/packages/grafana-data/src/transformations/transformers/labelsToFields.ts b/packages/grafana-data/src/transformations/transformers/labelsToFields.ts index 5b61f47b72f..58500b9b298 100644 --- a/packages/grafana-data/src/transformations/transformers/labelsToFields.ts +++ b/packages/grafana-data/src/transformations/transformers/labelsToFields.ts @@ -1,173 +1,68 @@ import { DataFrame, DataTransformerInfo, FieldType, Field } from '../../types'; import { DataTransformerID } from './ids'; import { ArrayVector } from '../../vector'; -import { filterFieldsTransformer } from './filter'; -import { FieldMatcherID } from '..'; -import { MutableField } from '../../dataframe'; +import { mergeTransformer } from './merge'; -export interface LabelsToFieldsOptions {} -type MapItem = { type: FieldType; values: Record; isValue: boolean }; -type SeriesMapItem = Record; -type Map = Record; +export interface LabelsToFieldsOptions { + /* + * If set this will use this label's value as the value field name. + */ + valueLabel?: string; +} export const labelsToFieldsTransformer: DataTransformerInfo = { id: DataTransformerID.labelsToFields, name: 'Labels to fields', - description: 'Groups series by time and return labels as columns', + description: 'Extract time series labels to fields (columns)', defaultOptions: {}, transformer: options => (data: DataFrame[]) => { - const framesWithTimeField = filterFieldsTransformer.transformer({ include: { id: FieldMatcherID.time } })(data); - if (!framesWithTimeField.length || !framesWithTimeField[0].fields.length) { - return data; - } + const result: DataFrame[] = []; - // get frames with only value fields - const framesWithoutTimeField = getFramesWithOnlyValueFields(data); - if (!framesWithoutTimeField.length || !framesWithoutTimeField[0].fields.length) { - return data; - } + for (const frame of data) { + const newFields: Field[] = []; - const columnsMap = createColumnsMap(framesWithTimeField, framesWithoutTimeField); - const fields = createFields(columnsMap); - const values: Record = {}; - - const timeColumnItem = columnsMap[fields[0].name]; - const seriesIndexStrings = Object.keys(timeColumnItem); - for (const seriesIndexString of seriesIndexStrings) { - const seriesItem = timeColumnItem[seriesIndexString]; - const timeValueStrings = Object.keys(seriesItem.values); - - for (const timeValueString of timeValueStrings) { - if (!values[timeValueString]) { - values[timeValueString] = []; + for (const field of frame.fields) { + if (!field.labels) { + newFields.push(field); + continue; } - let row = new Array(fields.length); - for (let index = 0; index < fields.length; index++) { - const field = fields[index]; - const valueItem = columnsMap[field.name][seriesIndexString]; - const value = valueItem ? valueItem.values[timeValueString] ?? null : null; - row[index] = value; + + let name = field.name; + + for (const labelName of Object.keys(field.labels)) { + // if we should use this label as the value field name store it and skip adding this as a seperate field + if (options.valueLabel === labelName) { + name = field.labels[labelName]; + continue; + } + + const values = new Array(frame.length).fill(field.labels[labelName]); + newFields.push({ + name: labelName, + type: FieldType.string, + values: new ArrayVector(values), + config: {}, + }); } - values[timeValueString].push(row); + + // add the value field but clear out any labels or displayName + newFields.push({ + ...field, + name, + config: { + ...field.config, + displayName: undefined, + }, + labels: undefined, + }); } + + result.push({ + fields: newFields, + length: frame.length, + }); } - const timestamps = Object.values(values); - for (const timestamp of timestamps) { - for (const row of timestamp) { - for (let fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) { - fields[fieldIndex].values.add(row[fieldIndex]); - } - } - } - - return [ - { - fields, - length: fields[0].values.length, - }, - ]; + return mergeTransformer.transformer({})(result); }, }; - -function getFramesWithOnlyValueFields(data: DataFrame[]): DataFrame[] { - const processed: DataFrame[] = []; - - for (const series of data) { - const fields: Field[] = []; - - for (let i = 0; i < series.fields.length; i++) { - const field = series.fields[i]; - - if (field.type !== FieldType.number) { - continue; - } - - fields.push(field); - } - - if (!fields.length) { - continue; - } - - const copy = { - ...series, // all the other properties - fields, // but a different set of fields - }; - - processed.push(copy); - } - - return processed; -} - -function addOrAppendMapItem(args: { map: Map; series: number; column: string; type: FieldType; isValue?: boolean }) { - const { map, column, type, series, isValue = false } = args; - // we're using the fact that the series (number) will automatically become a string prop on the object - const seriesMapItem: SeriesMapItem = { [series]: { type, values: {}, isValue } }; - if (!map[column]) { - map[column] = seriesMapItem; - } - - if (!map[column][series]) { - map[column] = { ...map[column], ...seriesMapItem }; - } -} - -// this is a naive implementation that does the job, not optimized for performance or speed -function createColumnsMap(framesWithTimeField: DataFrame[], framesWithoutTimeField: DataFrame[]) { - const map: Map = {}; - - for (let frameIndex = 0; frameIndex < framesWithTimeField.length; frameIndex++) { - const timeFrame = framesWithTimeField[frameIndex]; - const otherFrame = framesWithoutTimeField[frameIndex]; - const timeField = timeFrame.fields[0]; - - addOrAppendMapItem({ map, column: timeField.name, series: frameIndex, type: timeField.type }); - - for (let valueIndex = 0; valueIndex < timeFrame.length; valueIndex++) { - const timeFieldValue = timeField.values.get(valueIndex); - map[timeField.name][frameIndex].values[timeFieldValue] = timeFieldValue; - - for (const field of otherFrame.fields) { - if (field.labels) { - const labels = Object.keys(field.labels); - for (const label of labels) { - addOrAppendMapItem({ map, column: label, series: frameIndex, type: FieldType.string }); - - map[label][frameIndex].values[timeFieldValue] = field.labels[label]; - } - } - - const otherFieldValue = field.values.get(valueIndex); - addOrAppendMapItem({ map, column: field.name, series: frameIndex, type: field.type, isValue: true }); - - map[field.name][frameIndex].values[timeFieldValue] = otherFieldValue; - } - } - } - - return map; -} - -function createFields(columnsMap: Map): MutableField[] { - const columns = Object.keys(columnsMap); - const fields: MutableField[] = []; - const valueColumns: string[] = []; - - for (const column of columns) { - const columnItem = Object.values(columnsMap[column])[0]; - if (columnItem.isValue) { - valueColumns.push(column); - continue; - } - fields.push({ type: columnItem.type, values: new ArrayVector(), name: column, config: {} }); - } - - for (const column of valueColumns) { - const columnItem = Object.values(columnsMap[column])[0]; - fields.push({ type: columnItem.type, values: new ArrayVector(), name: column, config: {} }); - } - - return fields; -} diff --git a/packages/grafana-data/src/transformations/transformers/merge.test.ts b/packages/grafana-data/src/transformations/transformers/merge.test.ts index 171eb5cc992..cb1b17c0548 100644 --- a/packages/grafana-data/src/transformations/transformers/merge.test.ts +++ b/packages/grafana-data/src/transformations/transformers/merge.test.ts @@ -35,8 +35,8 @@ describe('Merge multipe to single', () => { const result = transformDataFrame([cfg], [seriesA, seriesB]); const expected: Field[] = [ - createField('Time', FieldType.time, [2000, 1000]), - createField('Temp', FieldType.number, [-1, 1]), + createField('Time', FieldType.time, [1000, 2000]), + createField('Temp', FieldType.number, [1, -1]), ]; expect(unwrap(result[0].fields)).toEqual(expected); @@ -66,8 +66,8 @@ describe('Merge multipe to single', () => { const result = transformDataFrame([cfg], [seriesA, seriesB]); const expected: Field[] = [ - createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), - createField('Temp', FieldType.number, [5, 4, 3, 2, 1, -1]), + createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), + createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3]), ]; expect(unwrap(result[0].fields)).toEqual(expected); @@ -105,8 +105,8 @@ describe('Merge multipe to single', () => { const result = transformDataFrame([cfg], [seriesA, seriesB, seriesC]); const expected: Field[] = [ - createField('Time', FieldType.time, [2000, 1000, 500]), - createField('Temp', FieldType.number, [-1, 1, 2]), + createField('Time', FieldType.time, [1000, 2000, 500]), + createField('Temp', FieldType.number, [1, -1, 2]), ]; expect(unwrap(result[0].fields)).toEqual(expected); @@ -188,9 +188,9 @@ describe('Merge multipe to single', () => { const result = transformDataFrame([cfg], [tableA, seriesB, tableC]); const expected: Field[] = [ - createField('Time', FieldType.time, ['2019-11-01T11:10:23Z', '2019-10-01T11:10:23Z', '2019-09-01T11:10:23Z']), - createField('Temp', FieldType.number, [2, 1, -1]), - createField('Humidity', FieldType.number, [5, 10, null]), + createField('Time', FieldType.time, ['2019-10-01T11:10:23Z', '2019-09-01T11:10:23Z', '2019-11-01T11:10:23Z']), + createField('Temp', FieldType.number, [1, -1, 2]), + createField('Humidity', FieldType.number, [10, null, 5]), ]; expect(unwrap(result[0].fields)).toEqual(expected); @@ -232,10 +232,10 @@ describe('Merge multipe to single', () => { const result = transformDataFrame([cfg], [tableA, tableB, tableC]); const expected: Field[] = [ - createField('Time', FieldType.time, [200, 150, 149, 126, 125, 124, 100, 100, 100]), - createField('Temp', FieldType.number, [5, 4, 5, 3, 2, 4, 1, -1, 1]), - createField('Humidity', FieldType.number, [55, 14, 30, null, null, 25, 10, null, 22]), - createField('Enabled', FieldType.boolean, [null, null, null, true, false, null, null, true, null]), + createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126, 100, 124, 149]), + createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3, 1, 4, 5]), + createField('Humidity', FieldType.number, [10, 14, 55, null, null, null, 22, 25, 30]), + createField('Enabled', FieldType.boolean, [null, null, null, true, false, true, null, null, null]), ]; expect(unwrap(result[0].fields)).toEqual(expected); @@ -265,8 +265,8 @@ describe('Merge multipe to single', () => { const result = transformDataFrame([cfg], [serieA, serieB]); const expected: Field[] = [ - createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), - createField('Temp', FieldType.number, [5, 4, 3, 2, 1, -1]), + createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), + createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3]), ]; const fields = unwrap(result[0].fields); @@ -299,8 +299,8 @@ describe('Merge multipe to single', () => { const result = transformDataFrame([cfg], [serieA, serieB]); const expected: Field[] = [ - createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), - createField('Temp', FieldType.number, [5, 4, 3, 2, 1, -1], { units: 'celsius' }), + createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), + createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3], { units: 'celsius' }), ]; const fields = unwrap(result[0].fields); @@ -333,8 +333,8 @@ describe('Merge multipe to single', () => { const result = transformDataFrame([cfg], [serieA, serieB]); const expected: Field[] = [ - createField('Time', FieldType.time, [200, 150, 126, 125, 100, 100]), - createField('Temp', FieldType.number, [5, 4, 3, 2, 1, -1]), + createField('Time', FieldType.time, [100, 150, 200, 100, 125, 126]), + createField('Temp', FieldType.number, [1, 4, 5, -1, 2, 3]), ]; const fields = unwrap(result[0].fields); diff --git a/packages/grafana-data/src/transformations/transformers/merge.ts b/packages/grafana-data/src/transformations/transformers/merge.ts index aa623d6601d..e0921c289d5 100644 --- a/packages/grafana-data/src/transformations/transformers/merge.ts +++ b/packages/grafana-data/src/transformations/transformers/merge.ts @@ -1,9 +1,9 @@ import { DataTransformerID } from './ids'; import { DataTransformerInfo } from '../../types/transformations'; -import { DataFrame, Field, FieldType } from '../../types/dataFrame'; +import { DataFrame, Field } from '../../types/dataFrame'; import { omit } from 'lodash'; import { ArrayVector } from '../../vector/ArrayVector'; -import { MutableDataFrame, sortDataFrame } from '../../dataframe'; +import { MutableDataFrame } from '../../dataframe'; type MergeDetailsKeyFactory = (existing: Record, value: Record) => string; @@ -68,10 +68,10 @@ export const mergeTransformer: DataTransformerInfo = { } } - const timeIndex = dataFrame.fields.findIndex(field => field.type === FieldType.time); - if (typeof timeIndex === 'number') { - return [sortDataFrame(dataFrame, timeIndex, true)]; - } + // const timeIndex = dataFrame.fields.findIndex(field => field.type === FieldType.time); + // if (typeof timeIndex === 'number') { + // return [sortDataFrame(dataFrame, timeIndex, true)]; + // } return [dataFrame]; }; }, diff --git a/public/app/core/components/TransformersUI/LabelsToFieldsTransformerEditor.tsx b/public/app/core/components/TransformersUI/LabelsToFieldsTransformerEditor.tsx index b8fae7ca852..b88b81dae58 100644 --- a/public/app/core/components/TransformersUI/LabelsToFieldsTransformerEditor.tsx +++ b/public/app/core/components/TransformersUI/LabelsToFieldsTransformerEditor.tsx @@ -1,5 +1,12 @@ import React from 'react'; -import { DataTransformerID, standardTransformers, TransformerRegistyItem, TransformerUIProps } from '@grafana/data'; +import { + DataTransformerID, + standardTransformers, + TransformerRegistyItem, + TransformerUIProps, + SelectableValue, +} from '@grafana/data'; +import { Select } from '@grafana/ui'; import { LabelsToFieldsOptions } from '@grafana/data/src/transformations/transformers/labelsToFields'; @@ -8,7 +15,45 @@ export const LabelsAsFieldsTransformerEditor: React.FC { - return null; + let labelNames: Array> = []; + let uniqueLabels: Record = {}; + + for (const frame of input) { + for (const field of frame.fields) { + if (!field.labels) { + continue; + } + + for (const labelName of Object.keys(field.labels)) { + if (!uniqueLabels[labelName]) { + labelNames.push({ value: labelName, label: labelName }); + uniqueLabels[labelName] = true; + } + } + } + } + + const onValueLabelChange = (value: SelectableValue | null) => { + onChange({ valueLabel: value?.value }); + }; + + return ( +
+
+
Value field name
+