diff --git a/docs/sources/panels/transformations/types-options.md b/docs/sources/panels/transformations/types-options.md index db14bee1251..3042eaf7e37 100644 --- a/docs/sources/panels/transformations/types-options.md +++ b/docs/sources/panels/transformations/types-options.md @@ -10,6 +10,7 @@ Grafana comes with the following transformations: - [Add field from calculation]({{< relref "./types-options.md#add-field-from-calculation" >}}) - [Concatenate fields]({{< relref "./types-options.md#concatenate-fields" >}}) - [Config from query results]({{< relref "./config-from-query.md" >}}) +- [Convert field type]({{< relref "./types-options.md#convert-field-type" >}}) - [Filter data by name]({{< relref "./types-options.md#filter-data-by-name" >}}) - [Filter data by query]({{< relref "./types-options.md#filter-data-by-query" >}}) - [Filter data by value]({{< relref "./types-options.md#filter-data-by-value" >}}) @@ -324,6 +325,36 @@ After you concatenate the fields, the data frame would be: | ---- | ------- | --- | ------ | | 15.4 | 1230233 | 3.2 | 5 | +## Convert field type + +This transformation changes the field type of the specified field. + +- **Field -** Select from available fields +- **as -** Select the FieldType to convert to + - **Numeric -** attempts to make the values numbers + - **String -** will make the values strings + - **Time -** attempts to parse the values as time + - Will show an option to specify a DateFormat as input by a string like yyyy-mm-dd or DD MM YYYY hh:mm:ss + - **Boolean -** will make the values booleans + +For example the following query could be modified by selecting the time field, as Time, and Date Format as YYYY. + +| Time | Mark | Value | +| ---------- | ----- | ----- | +| 2017-07-01 | above | 25 | +| 2018-08-02 | below | 22 | +| 2019-09-02 | below | 29 | +| 2020-10-04 | above | 22 | + +The result: + +| Time | Mark | Value | +| ------------------- | ----- | ----- | +| 2017-01-01 00:00:00 | above | 25 | +| 2018-01-01 00:00:00 | below | 22 | +| 2019-01-01 00:00:00 | below | 29 | +| 2020-01-01 00:00:00 | above | 22 | + ## Series to rows > **Note:** This transformation is available in Grafana 7.1+. diff --git a/packages/grafana-data/src/transformations/transformers/convertFieldType.ts b/packages/grafana-data/src/transformations/transformers/convertFieldType.ts index 277e1db3322..8cb8c95b454 100644 --- a/packages/grafana-data/src/transformations/transformers/convertFieldType.ts +++ b/packages/grafana-data/src/transformations/transformers/convertFieldType.ts @@ -5,20 +5,28 @@ import { DataTransformerID } from './ids'; import { DataFrame, Field, FieldType } from '../../types/dataFrame'; import { dateTimeParse } from '../../datetime'; import { ArrayVector } from '../../vector'; +import { fieldMatchers } from '../matchers'; +import { FieldMatcherID } from '../matchers/ids'; export interface ConvertFieldTypeTransformerOptions { conversions: ConvertFieldTypeOptions[]; } export interface ConvertFieldTypeOptions { + /** + * The field to convert field type + */ targetField?: string; + /** + * The field type to convert to + */ destinationType?: FieldType; + /** + * Date format to parse a string datetime + */ dateFormat?: string; } -/** - * @alpha - */ export const convertFieldTypeTransformer: SynchronousDataTransformerInfo = { id: DataTransformerID.convertFieldType, name: 'Convert field type', @@ -43,32 +51,44 @@ export const convertFieldTypeTransformer: SynchronousDataTransformerInfo ({ ...frame })); - frames.forEach((frame) => { - for (let fieldIdx = 0; fieldIdx < frame.fields.length; fieldIdx++) { - let field = frame.fields[fieldIdx]; - for (let cIdx = 0; cIdx < options.conversions.length; cIdx++) { - if (field.name === options.conversions[cIdx].targetField) { - //check in about matchers with Ryan - const conversion = options.conversions[cIdx]; - frame.fields[fieldIdx] = convertFieldType(field, conversion); - break; - } - } + for (const conversion of options.conversions) { + if (!conversion.targetField) { + continue; } - frameCopy.push(frame); - }); - return frameCopy; + const matches = fieldMatchers.get(FieldMatcherID.byName).get(conversion.targetField); + for (const frame of framesCopy) { + frame.fields = frame.fields.map((field) => { + if (matches(field, frame, framesCopy)) { + return convertFieldType(field, conversion); + } + return field; + }); + } + } + + return framesCopy; } +/** + * Convert a single field type to specifed field type. + * @param field - field to convert + * @param opts - field conversion options + * @returns converted field + * + * @internal + */ export function convertFieldType(field: Field, opts: ConvertFieldTypeOptions): Field { switch (opts.destinationType) { case FieldType.time: @@ -84,6 +104,9 @@ export function convertFieldType(field: Field, opts: ConvertFieldTypeOptions): F } } +/** + * @internal + */ export function fieldToTimeField(field: Field, dateFormat?: string): Field { let opts = dateFormat ? { format: dateFormat } : undefined; @@ -109,12 +132,8 @@ function fieldToNumberField(field: Field): Field { const numValues = field.values.toArray().slice(); for (let n = 0; n < numValues.length; n++) { - if (numValues[n]) { - let number = +numValues[n]; - numValues[n] = Number.isFinite(number) ? number : null; - } else { - numValues[n] = null; - } + const number = +numValues[n]; + numValues[n] = Number.isFinite(number) ? number : null; } return { @@ -128,7 +147,7 @@ function fieldToBooleanField(field: Field): Field { const booleanValues = field.values.toArray().slice(); for (let b = 0; b < booleanValues.length; b++) { - booleanValues[b] = Boolean(booleanValues[b]); + booleanValues[b] = Boolean(!!booleanValues[b]); } return { @@ -153,7 +172,12 @@ function fieldToStringField(field: Field): Field { } /** - * @alpha + * Checks the first value. Assumes any number should be time fieldtype. Otherwise attempts to make the fieldtype time. + * @param field - field to ensure is a time fieldtype + * @param dateFormat - date format used to parse a string datetime + * @returns field as time + * + * @public */ export function ensureTimeField(field: Field, dateFormat?: string): Field { const firstValueTypeIsNumber = typeof field.values.get(0) === 'number'; diff --git a/public/app/core/components/TransformersUI/ConvertFieldTypeTransformerEditor.tsx b/public/app/core/components/TransformersUI/ConvertFieldTypeTransformerEditor.tsx index 217e5e95f3c..1086ac74d18 100644 --- a/public/app/core/components/TransformersUI/ConvertFieldTypeTransformerEditor.tsx +++ b/public/app/core/components/TransformersUI/ConvertFieldTypeTransformerEditor.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { ChangeEvent, useCallback } from 'react'; import { DataTransformerID, FieldNamePickerConfigSettings, @@ -56,9 +56,9 @@ export const ConvertFieldTypeTransformerEditor: React.FC (value: SelectableValue) => { + (idx) => (e: ChangeEvent) => { const conversions = options.conversions; - conversions[idx] = { ...conversions[idx], dateFormat: value.value }; + conversions[idx] = { ...conversions[idx], dateFormat: e.currentTarget.value }; onChange({ ...options, conversions: conversions,