diff --git a/devenv/dev-dashboards/transforms/join-by-field.json b/devenv/dev-dashboards/transforms/join-by-field.json index 907cee7e5d1..a86b3b89a89 100644 --- a/devenv/dev-dashboards/transforms/join-by-field.json +++ b/devenv/dev-dashboards/transforms/join-by-field.json @@ -24,6 +24,7 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, + "id": 1351, "links": [], "liveNow": false, "panels": [ @@ -37,7 +38,7 @@ }, "id": 9, "panels": [], - "title": "Input", + "title": "Join by time", "type": "row" }, { @@ -48,225 +49,37 @@ "fieldConfig": { "defaults": { "color": { - "mode": "thresholds" + "mode": "palette-classic" }, "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 8, - "x": 0, - "y": 1 - }, - "id": 11, - "options": { - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true - }, - "pluginVersion": "9.4.0-pre", - "targets": [ - { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, - "rawFrameContent": "[{\r\n \"name\": \"tags\",\r\n \"fields\": [\r\n { \"name\": \"tags__time\", \"values\": [100, 101, 200] },\r\n { \"name\": \"tags__name\", \"values\": [\"v1.2\", \"v1.2b\", \"v1.3\"] }\r\n ]\r\n}]", - "refId": "tags", - "scenarioId": "raw_frame" - } - ], - "title": "tags", - "type": "table" - }, - { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 8, - "x": 8, - "y": 1 - }, - "id": 13, - "options": { - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true - }, - "pluginVersion": "9.4.0-pre", - "targets": [ - { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, - "rawFrameContent": "[{\r\n \"name\": \"releases\",\r\n\"fields\": [\r\n { \"name\": \"releases__time\", \"values\": [150, 250] },\r\n { \"name\": \"releases__tag\", \"values\": [\"v1.2\", \"v1.3\"] }\r\n]}]", - "refId": "releases", - "scenarioId": "raw_frame" - } - ], - "title": "releases", - "type": "table" - }, - { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 8, - "x": 16, - "y": 1 - }, - "id": 19, - "options": { - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true - }, - "pluginVersion": "9.4.0-pre", - "targets": [ - { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, - "rawFrameContent": "[{\r\n \"name\": \"features\",\r\n\"fields\": [\r\n { \"name\": \"features__name\", \"values\": [\"A\", \"B\", \"C\", \"D\", \"E\"] },\r\n { \"name\": \"features__tag\", \"values\": [\"v1.2\", \"v1.3\", \"v1.2b\", \"v1.3\", \"v1.2\"] }\r\n]}]", - "refId": "features", - "scenarioId": "raw_frame" - } - ], - "title": "features", - "type": "table" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 9 - }, - "id": 21, - "panels": [], - "title": "Output", - "type": "row" - }, - { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" - }, - "inspect": false + "thresholdsStyle": { + "mode": "off" + } }, "mappings": [], "thresholds": { @@ -288,71 +101,39 @@ "h": 8, "w": 12, "x": 0, - "y": 10 + "y": 1 }, - "id": 23, + "id": 11, "options": { - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, - "showHeader": true + "tooltip": { + "mode": "single", + "sort": "none" + } }, - "pluginVersion": "9.4.0-pre", "targets": [ { "datasource": { "type": "testdata", "uid": "PD8C576611E62080A" }, - "rawFrameContent": "[{\r\n \"name\": \"tags\",\r\n \"fields\": [\r\n { \"name\": \"tags__time\", \"values\": [100, 101, 200] },\r\n { \"name\": \"tags__name\", \"values\": [\"v1.2\", \"v1.2b\", \"v1.3\"] }\r\n ]\r\n}]", - "refId": "tags", - "scenarioId": "raw_frame" - }, - { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, - "rawFrameContent": "[{\r\n \"name\": \"releases\",\r\n \"fields\": [\r\n { \"name\": \"releases__time\", \"values\": [150, 250] },\r\n { \"name\": \"releases__tag\", \"values\": [\"v1.2\", \"v1.3\"] }\r\n]}]", - "refId": "releases", - "scenarioId": "raw_frame" - }, - { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, - "rawFrameContent": "[{\r\n \"name\": \"features\",\r\n \"fields\": [\r\n { \"name\": \"features__name\", \"values\": [\"A\", \"B\", \"C\", \"D\", \"E\"] },\r\n { \"name\": \"features__tag\", \"values\": [\"v1.2\", \"v1.3\", \"v1.2b\", \"v1.3\", \"v1.2\"] }\r\n]}]", - "refId": "features", - "scenarioId": "raw_frame" + "refId": "A", + "scenarioId": "random_walk", + "seriesCount": 4 } ], - "title": "OUTER JOIN", - "transformations": [ - { - "id": "joinByField", - "options": { - "fields": { - "A": "features__name", - "features": "features__tag", - "releases": "releases__tag", - "tags": "tags__name" - }, - "mode": "outer" - } - } - ], - "type": "table" + "title": "Timeseries data", + "type": "timeseries" }, { "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" + "type": "datasource", + "uid": "-- Dashboard --" }, "fieldConfig": { "defaults": { @@ -361,9 +142,7 @@ }, "custom": { "align": "auto", - "cellOptions": { - "type": "auto" - }, + "displayMode": "auto", "inspect": false }, "mappings": [], @@ -386,12 +165,11 @@ "h": 8, "w": 12, "x": 12, - "y": 10 + "y": 1 }, - "id": 24, + "id": 13, "options": { "footer": { - "countRows": false, "fields": "", "reducer": [ "sum" @@ -400,47 +178,430 @@ }, "showHeader": true }, - "pluginVersion": "9.4.0-pre", + "pluginVersion": "9.2.0-pre", "targets": [ { "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" + "type": "datasource", + "uid": "-- Dashboard --" }, - "rawFrameContent": "[{\r\n \"name\": \"tags\",\r\n \"fields\": [\r\n { \"name\": \"tags__time\", \"values\": [100, 101, 200] },\r\n { \"name\": \"tags__name\", \"values\": [\"v1.2\", \"v1.2b\", \"v1.3\"] }\r\n ]\r\n}]", - "refId": "tags", - "scenarioId": "raw_frame" - }, - { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, - "rawFrameContent": "[{\r\n \"name\": \"releases\",\r\n \"fields\": [\r\n { \"name\": \"releases__time\", \"values\": [150, 250] },\r\n { \"name\": \"releases__tag\", \"values\": [\"v1.2\", \"v1.3\"] }\r\n]}]", - "refId": "releases", - "scenarioId": "raw_frame" - }, - { - "datasource": { - "type": "testdata", - "uid": "PD8C576611E62080A" - }, - "rawFrameContent": "[{\r\n \"name\": \"features\",\r\n \"fields\": [\r\n { \"name\": \"features__name\", \"values\": [\"A\", \"B\", \"C\", \"D\", \"E\"] },\r\n { \"name\": \"features__tag\", \"values\": [\"v1.2\", \"v1.3\", \"v1.2b\", \"v1.3\", \"v1.2\"] }\r\n]}]", - "refId": "features", - "scenarioId": "raw_frame" + "panelId": 11, + "refId": "A" } ], - "title": "INNER JOIN", + "title": "Same data (as a table)", + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 16, + "options": { + "footer": { + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.2.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 11, + "refId": "A" + } + ], + "title": "OUTER join on time (default)", + "transformations": [ + { + "id": "joinByField", + "options": {} + } + ], + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 5, + "panels": [], + "title": "Join by string field", + "type": "row" + }, + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 15 + }, + "id": 2, + "options": { + "footer": { + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true + }, + "pluginVersion": "9.2.0-pre", + "targets": [ + { + "csvContent": "OrderID,CustomerID,Time\n100,A,10000\n101,B,20000\n102,C,30000", + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "Orders", + "scenarioId": "csv_content" + }, + { + "csvContent": "CustomerID,Name,Country\nA,Customer A,USA\nB,Customer B,Germany\nC,Customer C,Spain\nD,Customer D,Canada", + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "hide": false, + "refId": "Customers", + "scenarioId": "csv_content" + } + ], + "title": "Orders", + "transformations": [], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 15 + }, + "id": 3, + "options": { + "footer": { + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 1, + "showHeader": true + }, + "pluginVersion": "9.2.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "Customers", + "transformations": [], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "CustomerID" + }, + "properties": [ + { + "id": "custom.width", + "value": 101 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "OrderID" + }, + "properties": [ + { + "id": "custom.width", + "value": 89 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 23 + }, + "id": 6, + "options": { + "footer": { + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.2.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "OUTER join on CustomerID (keeps missing values)", "transformations": [ { "id": "joinByField", "options": { - "fields": { - "A": "features__name", - "features": "features__tag", - "releases": "releases__tag", - "tags": "tags__name" + "byField": "CustomerID", + "mode": "outer" + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "CustomerID" }, + "properties": [ + { + "id": "custom.width", + "value": 101 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "OrderID" + }, + "properties": [ + { + "id": "custom.width", + "value": 89 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 23 + }, + "id": 7, + "options": { + "footer": { + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.2.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "INNER join on CustomerID ", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "CustomerID", "mode": "inner" } } @@ -448,8 +609,7 @@ "type": "table" } ], - "revision": 1, - "schemaVersion": 38, + "schemaVersion": 37, "style": "dark", "tags": [ "gdev", @@ -466,6 +626,6 @@ "timezone": "", "title": "Join by field", "uid": "gw0K4rmVz", - "version": 1, + "version": 6, "weekStart": "" -} \ No newline at end of file +} diff --git a/packages/grafana-data/src/transformations/transformers/ensureColumns.ts b/packages/grafana-data/src/transformations/transformers/ensureColumns.ts index 9cca6fead37..4968336c236 100644 --- a/packages/grafana-data/src/transformations/transformers/ensureColumns.ts +++ b/packages/grafana-data/src/transformations/transformers/ensureColumns.ts @@ -20,13 +20,12 @@ export const ensureColumnsTransformer: SynchronousDataTransformerInfo = { const timeFieldName = findConsistentTimeFieldName(frames); if (frames.length > 1 && timeFieldName) { - const fields: { [key: string]: string } = {}; - for (const frame of frames) { - if (frame.refId) { - fields[frame.refId] = timeFieldName; - } - } - return joinByFieldTransformer.transformer({ fields }, ctx)(frames); + return joinByFieldTransformer.transformer( + { + byField: timeFieldName, + }, + ctx + )(frames); } return frames; }, diff --git a/packages/grafana-data/src/transformations/transformers/joinByField.test.ts b/packages/grafana-data/src/transformations/transformers/joinByField.test.ts index 5326e86da46..1a1026017b3 100644 --- a/packages/grafana-data/src/transformations/transformers/joinByField.test.ts +++ b/packages/grafana-data/src/transformations/transformers/joinByField.test.ts @@ -15,7 +15,6 @@ describe('JOIN Transformer', () => { describe('outer join', () => { const everySecondSeries = toDataFrame({ name: 'even', - refId: 'even', fields: [ { name: 'time', type: FieldType.time, values: [3000, 4000, 5000, 6000] }, { name: 'temperature', type: FieldType.number, values: [10.3, 10.4, 10.5, 10.6] }, @@ -25,7 +24,6 @@ describe('JOIN Transformer', () => { const everyOtherSecondSeries = toDataFrame({ name: 'odd', - refId: 'odd', fields: [ { name: 'time', type: FieldType.time, values: [1000, 3000, 5000, 7000] }, { name: 'temperature', type: FieldType.number, values: [11.1, 11.3, 11.5, 11.7] }, @@ -35,12 +33,9 @@ describe('JOIN Transformer', () => { it('joins by time field', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - even: 'time', - odd: 'time', - }, + byField: 'time', }, }; @@ -140,12 +135,9 @@ describe('JOIN Transformer', () => { it('joins by temperature field', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - even: 'temperature', - odd: 'temperature', - }, + byField: 'temperature', }, }; @@ -153,7 +145,6 @@ describe('JOIN Transformer', () => { (received) => { const data = received[0]; const filtered = data[0]; - expect(filtered.fields).toMatchInlineSnapshot(` [ { @@ -260,12 +251,9 @@ describe('JOIN Transformer', () => { it('joins by time field in reverse order', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - even: 'time', - odd: 'time', - }, + byField: 'time', }, }; @@ -277,7 +265,6 @@ describe('JOIN Transformer', () => { (received) => { const data = received[0]; const filtered = data[0]; - expect(filtered.fields).toMatchInlineSnapshot(` [ { @@ -389,12 +376,9 @@ describe('JOIN Transformer', () => { it('when dataframe and field share the same name then use the field name', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - even: 'time', - odd: 'time', - }, + byField: 'time', }, }; @@ -455,12 +439,9 @@ describe('JOIN Transformer', () => { it('joins if fields are missing', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - even: 'time', - odd: 'time', - }, + byField: 'time', }, }; @@ -536,12 +517,9 @@ describe('JOIN Transformer', () => { it('handles duplicate field name', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - even: 'time', - odd: 'time', - }, + byField: 'time', }, }; @@ -602,7 +580,6 @@ describe('JOIN Transformer', () => { describe('inner join', () => { const seriesA = toDataFrame({ name: 'A', - refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: [3000, 4000, 5000, 6000] }, { name: 'temperature', type: FieldType.number, values: [10.3, 10.4, 10.5, 10.6] }, @@ -612,7 +589,6 @@ describe('JOIN Transformer', () => { const seriesB = toDataFrame({ name: 'B', - refId: 'B', fields: [ { name: 'time', type: FieldType.time, values: [1000, 3000, 5000, 7000] }, { name: 'temperature', type: FieldType.number, values: [11.1, 10.3, 10.5, 11.7] }, @@ -622,12 +598,9 @@ describe('JOIN Transformer', () => { it('inner joins by time field', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - A: 'time', - B: 'time', - }, + byField: 'time', mode: JoinMode.inner, }, }; @@ -706,12 +679,9 @@ describe('JOIN Transformer', () => { it('inner joins by temperature field', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - A: 'temperature', - B: 'temperature', - }, + byField: 'temperature', mode: JoinMode.inner, }, }; @@ -794,12 +764,9 @@ describe('JOIN Transformer', () => { it('inner joins by time field in reverse order', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - A: 'time', - B: 'time', - }, + byField: 'time', mode: JoinMode.inner, }, }; @@ -885,7 +852,6 @@ describe('JOIN Transformer', () => { describe('Field names', () => { const seriesWithSameFieldAndDataFrameName = toDataFrame({ name: 'temperature', - refId: 'temperature', fields: [ { name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] }, { name: 'temperature', type: FieldType.number, values: [1, 3, 5, 7] }, @@ -894,7 +860,6 @@ describe('JOIN Transformer', () => { const seriesB = toDataFrame({ name: 'B', - refId: 'B', fields: [ { name: 'time', type: FieldType.time, values: [1000, 2000, 3000, 4000] }, { name: 'temperature', type: FieldType.number, values: [2, 4, 6, 8] }, @@ -903,12 +868,9 @@ describe('JOIN Transformer', () => { it('when dataframe and field share the same name then use the field name', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - temperature: 'time', - B: 'time', - }, + byField: 'time', mode: JoinMode.inner, }, }; @@ -970,20 +932,15 @@ describe('JOIN Transformer', () => { it('joins if fields are missing', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - A: 'time', - B: 'time', - C: 'time', - }, + byField: 'time', mode: JoinMode.inner, }, }; const frame1 = toDataFrame({ name: 'A', - refId: 'A', fields: [ { name: 'time', type: FieldType.time, values: [1, 2, 3] }, { name: 'temperature', type: FieldType.number, values: [10, 11, 12] }, @@ -992,13 +949,11 @@ describe('JOIN Transformer', () => { const frame2 = toDataFrame({ name: 'B', - refId: 'B', fields: [], }); const frame3 = toDataFrame({ name: 'C', - refId: 'C', fields: [ { name: 'time', type: FieldType.time, values: [1, 2, 3] }, { name: 'temperature', type: FieldType.number, values: [20, 22, 24] }, @@ -1056,18 +1011,14 @@ describe('JOIN Transformer', () => { it('handles duplicate field name', async () => { const cfg: DataTransformerConfig = { - id: DataTransformerID.joinByField, + id: DataTransformerID.seriesToColumns, options: { - fields: { - frame1: 'time', - frame2: 'time', - }, + byField: 'time', mode: JoinMode.inner, }, }; const frame1 = toDataFrame({ - refId: 'frame1', fields: [ { name: 'time', type: FieldType.time, values: [1] }, { name: 'temperature', type: FieldType.number, values: [10] }, @@ -1075,7 +1026,6 @@ describe('JOIN Transformer', () => { }); const frame2 = toDataFrame({ - refId: 'frame2', fields: [ { name: 'time', type: FieldType.time, values: [1] }, { name: 'temperature', type: FieldType.number, values: [20] }, diff --git a/packages/grafana-data/src/transformations/transformers/joinByField.ts b/packages/grafana-data/src/transformations/transformers/joinByField.ts index 2946dd4125b..e6c1d386fa6 100644 --- a/packages/grafana-data/src/transformations/transformers/joinByField.ts +++ b/packages/grafana-data/src/transformations/transformers/joinByField.ts @@ -1,6 +1,8 @@ import { map } from 'rxjs/operators'; -import { DataFrame, SynchronousDataTransformerInfo } from '../../types'; +import { DataFrame, SynchronousDataTransformerInfo, FieldMatcher } from '../../types'; +import { fieldMatchers } from '../matchers'; +import { FieldMatcherID } from '../matchers/ids'; import { DataTransformerID } from './ids'; import { joinDataFrames } from './joinDataFrames'; @@ -11,7 +13,7 @@ export enum JoinMode { } export interface JoinByFieldOptions { - fields?: { [key: string]: string }; // empty will pick the field automatically + byField?: string; // empty will pick the field automatically mode?: JoinMode; } @@ -22,7 +24,7 @@ export const joinByFieldTransformer: SynchronousDataTransformerInfo joinByFieldTransformer.transformer(options, ctx)(data))), transformer: (options: JoinByFieldOptions) => { + let joinBy: FieldMatcher | undefined = undefined; return (data: DataFrame[]) => { if (data.length > 1) { - const joined = joinDataFrames({ frames: data, mode: options.mode, fields: options.fields }); + if (options.byField && !joinBy) { + joinBy = fieldMatchers.get(FieldMatcherID.byName).get(options.byField); + } + const joined = joinDataFrames({ frames: data, joinBy, mode: options.mode }); if (joined) { return [joined]; } } - return data; }; }, diff --git a/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts b/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts index 83dd9469f6b..5f4ef23427b 100644 --- a/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts +++ b/packages/grafana-data/src/transformations/transformers/joinDataFrames.test.ts @@ -359,103 +359,4 @@ describe('align frames', () => { expect(isLikelyAscendingVector(new ArrayVector([null, 1, null]), 3)).toBeTruthy(); }); }); - - describe('should perform a join on custom fields', () => { - const tags = toDataFrame({ - refId: 'tags', - fields: [ - { name: 'tags__time', type: FieldType.time, values: [100, 101, 200] }, - { name: 'tags__name', type: FieldType.string, values: ['v1.2', 'v1.2b', 'v1.3'] }, - ], - }); - - const releases = toDataFrame({ - refId: 'releases', - fields: [ - { name: 'releases__time', type: FieldType.time, values: [150, 250] }, - { name: 'releases__tag', type: FieldType.string, values: ['v1.2', 'v1.3'] }, - ], - }); - - const features = toDataFrame({ - refId: 'features', - fields: [ - { name: 'features__name', type: FieldType.string, values: ['A', 'B', 'C', 'D', 'E'] }, - { name: 'features__tag', type: FieldType.time, values: ['v1.2', 'v1.3', 'v1.2b', 'v1.3', 'v1.2'] }, - ], - }); - - it('should perform an outer join', () => { - const out = joinDataFrames({ - frames: [tags, releases, features], - fields: { - tags: 'tags__name', - releases: 'releases__tag', - features: 'features__tag', - }, - })!; - - expect( - out.fields.map((f) => ({ - name: f.name, - values: f.values.toArray(), - })) - ).toEqual([ - { - name: 'tags__name', - values: ['v1.2', 'v1.2b', 'v1.3'], - }, - { - name: 'tags__time', - values: [100, 101, 200], - }, - { - name: 'releases__time', - values: [150, undefined, 250], - }, - { - name: 'features__name', - values: ['E', 'C', 'D'], - }, - ]); - }); - - it('should perform an inner join', () => { - const out = joinDataFrames({ - frames: [tags, releases, features], - fields: { - tags: 'tags__name', - releases: 'releases__tag', - features: 'features__tag', - }, - mode: JoinMode.inner, - })!; - - const mappedOut = out.fields.map((f) => ({ - name: f.name, - values: f.values.toArray(), - })); - - const expected = [ - { - name: 'tags__name', - values: ['v1.2', 'v1.3'], - }, - { - name: 'tags__time', - values: [100, 200], - }, - { - name: 'releases__time', - values: [150, 250], - }, - { - name: 'features__name', - values: ['E', 'D'], - }, - ]; - - expect(JSON.stringify(mappedOut)).toEqual(JSON.stringify(expected)); - }); - }); }); diff --git a/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts b/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts index dd09c373bfb..767161d2f9a 100644 --- a/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts +++ b/packages/grafana-data/src/transformations/transformers/joinDataFrames.ts @@ -47,11 +47,6 @@ export interface JoinOptions { */ joinBy?: FieldMatcher; - /** - * The fields to join on - */ - fields?: { [key: string]: string }; - /** * Optionally filter the non-join fields */ @@ -68,16 +63,8 @@ export interface JoinOptions { mode?: JoinMode; } -function getJoinMatcher(options: JoinOptions, refId: string | undefined): FieldMatcher { - if (options.joinBy) { - return options.joinBy; - } - - if (!options.fields || !refId) { - return pickBestJoinField(options.frames); - } - - return fieldMatchers.get(FieldMatcherID.byName).get(options.fields[refId]); +function getJoinMatcher(options: JoinOptions): FieldMatcher { + return options.joinBy ?? pickBestJoinField(options.frames); } /** @@ -108,7 +95,7 @@ export function joinDataFrames(options: JoinOptions): DataFrame | undefined { let frame = options.frames[0]; let frameCopy = frame; - const joinFieldMatcher = getJoinMatcher(options, frame.refId); + const joinFieldMatcher = getJoinMatcher(options); let joinIndex = frameCopy.fields.findIndex((f) => joinFieldMatcher(f, frameCopy, options.frames)); if (options.keepOriginIndices) { @@ -165,10 +152,10 @@ export function joinDataFrames(options: JoinOptions): DataFrame | undefined { const nullModes: JoinNullMode[][] = []; const allData: AlignedData[] = []; const originalFields: Field[] = []; + const joinFieldMatcher = getJoinMatcher(options); for (let frameIndex = 0; frameIndex < options.frames.length; frameIndex++) { const frame = options.frames[frameIndex]; - const joinFieldMatcher = getJoinMatcher(options, frame.refId); if (!frame || !frame.fields?.length) { continue; // skip the frame diff --git a/public/app/features/transformers/editors/JoinByFieldTransformerEditor.tsx b/public/app/features/transformers/editors/JoinByFieldTransformerEditor.tsx index 1b0977455d6..cffa84015e0 100644 --- a/public/app/features/transformers/editors/JoinByFieldTransformerEditor.tsx +++ b/public/app/features/transformers/editors/JoinByFieldTransformerEditor.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback } from 'react'; import { DataTransformerID, @@ -6,10 +6,11 @@ import { standardTransformers, TransformerRegistryItem, TransformerUIProps, - DataFrame, } from '@grafana/data'; import { JoinByFieldOptions, JoinMode } from '@grafana/data/src/transformations/transformers/joinByField'; -import { Select, InlineFieldRow, InlineField, Checkbox, HorizontalGroup } from '@grafana/ui'; +import { Select, InlineFieldRow, InlineField } from '@grafana/ui'; + +import { useAllFieldNamesFromDataFrames } from '../utils'; const modes = [ { value: JoinMode.outer, label: 'OUTER', description: 'Keep all rows from any table with a value' }, @@ -17,44 +18,14 @@ const modes = [ ]; export function SeriesToFieldsTransformerEditor({ input, options, onChange }: TransformerUIProps) { - useEffect(() => { - if (options.fields && !Object.keys(options.fields).length && input.length && input[0].refId) { - options.fields[input[0].refId] = input[0].fields[0].name; - onChange({ ...options }); - } - }, [onChange, options, input]); - - const onToggleDataFrame = useCallback( - (dataFrame: DataFrame) => { - if (!dataFrame.refId) { - return; - } - - if (options.fields) { - if (dataFrame.refId in options.fields) { - if (Object.keys(options.fields).length === 1) { - return; - } - - delete options.fields[dataFrame.refId]; - } else { - options.fields[dataFrame.refId] = dataFrame.fields[0].name; - } - } - - onChange({ ...options }); - }, - [onChange, options] - ); + const fieldNames = useAllFieldNamesFromDataFrames(input).map((item: string) => ({ label: item, value: item })); const onSelectField = useCallback( - (queryRefId: string | undefined, fieldName: SelectableValue) => { - if (queryRefId && fieldName.value) { - onChange({ - ...options, - fields: { ...options.fields, [queryRefId]: fieldName.value }, - }); - } + (value: SelectableValue) => { + onChange({ + ...options, + byField: value?.value, + }); }, [onChange, options] ); @@ -63,7 +34,7 @@ export function SeriesToFieldsTransformerEditor({ input, options, onChange }: Tr (value: SelectableValue) => { onChange({ ...options, - mode: value?.value || JoinMode.outer, + mode: value?.value, }); }, [onChange, options] @@ -73,31 +44,20 @@ export function SeriesToFieldsTransformerEditor({ input, options, onChange }: Tr <> - + + + + + ({ label: field.name, value: field.name }))} - value={dataFrame.refId ? (options.fields || {})[dataFrame.refId] : dataFrame.fields[0].name} - onChange={(fieldName) => onSelectField(dataFrame.refId, fieldName)} - /> - - - - ))} ); }