Transformations: Use fieldMatchers for convertFieldType and add doc (#38769)

This commit is contained in:
nikki-kiga 2021-09-02 19:18:06 -07:00 committed by GitHub
parent caef39b6a4
commit 2a2f10da7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 85 additions and 30 deletions

View File

@ -10,6 +10,7 @@ Grafana comes with the following transformations:
- [Add field from calculation]({{< relref "./types-options.md#add-field-from-calculation" >}}) - [Add field from calculation]({{< relref "./types-options.md#add-field-from-calculation" >}})
- [Concatenate fields]({{< relref "./types-options.md#concatenate-fields" >}}) - [Concatenate fields]({{< relref "./types-options.md#concatenate-fields" >}})
- [Config from query results]({{< relref "./config-from-query.md" >}}) - [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 name]({{< relref "./types-options.md#filter-data-by-name" >}})
- [Filter data by query]({{< relref "./types-options.md#filter-data-by-query" >}}) - [Filter data by query]({{< relref "./types-options.md#filter-data-by-query" >}})
- [Filter data by value]({{< relref "./types-options.md#filter-data-by-value" >}}) - [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 | | 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 ## Series to rows
> **Note:** This transformation is available in Grafana 7.1+. > **Note:** This transformation is available in Grafana 7.1+.

View File

@ -5,20 +5,28 @@ import { DataTransformerID } from './ids';
import { DataFrame, Field, FieldType } from '../../types/dataFrame'; import { DataFrame, Field, FieldType } from '../../types/dataFrame';
import { dateTimeParse } from '../../datetime'; import { dateTimeParse } from '../../datetime';
import { ArrayVector } from '../../vector'; import { ArrayVector } from '../../vector';
import { fieldMatchers } from '../matchers';
import { FieldMatcherID } from '../matchers/ids';
export interface ConvertFieldTypeTransformerOptions { export interface ConvertFieldTypeTransformerOptions {
conversions: ConvertFieldTypeOptions[]; conversions: ConvertFieldTypeOptions[];
} }
export interface ConvertFieldTypeOptions { export interface ConvertFieldTypeOptions {
/**
* The field to convert field type
*/
targetField?: string; targetField?: string;
/**
* The field type to convert to
*/
destinationType?: FieldType; destinationType?: FieldType;
/**
* Date format to parse a string datetime
*/
dateFormat?: string; dateFormat?: string;
} }
/**
* @alpha
*/
export const convertFieldTypeTransformer: SynchronousDataTransformerInfo<ConvertFieldTypeTransformerOptions> = { export const convertFieldTypeTransformer: SynchronousDataTransformerInfo<ConvertFieldTypeTransformerOptions> = {
id: DataTransformerID.convertFieldType, id: DataTransformerID.convertFieldType,
name: 'Convert field type', name: 'Convert field type',
@ -43,32 +51,44 @@ export const convertFieldTypeTransformer: SynchronousDataTransformerInfo<Convert
}; };
/** /**
* @alpha * Convert field types for dataframe(s)
* @param options - field type conversion options
* @param frames - dataframe(s) with field types to convert
* @returns dataframe(s) with converted field types
*/ */
export function convertFieldTypes(options: ConvertFieldTypeTransformerOptions, frames: DataFrame[]): DataFrame[] { export function convertFieldTypes(options: ConvertFieldTypeTransformerOptions, frames: DataFrame[]): DataFrame[] {
if (!options.conversions.length) { if (!options.conversions.length) {
return frames; return frames;
} }
const frameCopy: DataFrame[] = []; const framesCopy = frames.map((frame) => ({ ...frame }));
frames.forEach((frame) => { for (const conversion of options.conversions) {
for (let fieldIdx = 0; fieldIdx < frame.fields.length; fieldIdx++) { if (!conversion.targetField) {
let field = frame.fields[fieldIdx]; continue;
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;
}
}
} }
frameCopy.push(frame); const matches = fieldMatchers.get(FieldMatcherID.byName).get(conversion.targetField);
}); for (const frame of framesCopy) {
return frameCopy; 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 { export function convertFieldType(field: Field, opts: ConvertFieldTypeOptions): Field {
switch (opts.destinationType) { switch (opts.destinationType) {
case FieldType.time: case FieldType.time:
@ -84,6 +104,9 @@ export function convertFieldType(field: Field, opts: ConvertFieldTypeOptions): F
} }
} }
/**
* @internal
*/
export function fieldToTimeField(field: Field, dateFormat?: string): Field { export function fieldToTimeField(field: Field, dateFormat?: string): Field {
let opts = dateFormat ? { format: dateFormat } : undefined; let opts = dateFormat ? { format: dateFormat } : undefined;
@ -109,12 +132,8 @@ function fieldToNumberField(field: Field): Field {
const numValues = field.values.toArray().slice(); const numValues = field.values.toArray().slice();
for (let n = 0; n < numValues.length; n++) { for (let n = 0; n < numValues.length; n++) {
if (numValues[n]) { const number = +numValues[n];
let number = +numValues[n]; numValues[n] = Number.isFinite(number) ? number : null;
numValues[n] = Number.isFinite(number) ? number : null;
} else {
numValues[n] = null;
}
} }
return { return {
@ -128,7 +147,7 @@ function fieldToBooleanField(field: Field): Field {
const booleanValues = field.values.toArray().slice(); const booleanValues = field.values.toArray().slice();
for (let b = 0; b < booleanValues.length; b++) { for (let b = 0; b < booleanValues.length; b++) {
booleanValues[b] = Boolean(booleanValues[b]); booleanValues[b] = Boolean(!!booleanValues[b]);
} }
return { 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 { export function ensureTimeField(field: Field, dateFormat?: string): Field {
const firstValueTypeIsNumber = typeof field.values.get(0) === 'number'; const firstValueTypeIsNumber = typeof field.values.get(0) === 'number';

View File

@ -1,4 +1,4 @@
import React, { useCallback } from 'react'; import React, { ChangeEvent, useCallback } from 'react';
import { import {
DataTransformerID, DataTransformerID,
FieldNamePickerConfigSettings, FieldNamePickerConfigSettings,
@ -56,9 +56,9 @@ export const ConvertFieldTypeTransformerEditor: React.FC<TransformerUIProps<Conv
); );
const onInputFormat = useCallback( const onInputFormat = useCallback(
(idx) => (value: SelectableValue<string>) => { (idx) => (e: ChangeEvent<HTMLInputElement>) => {
const conversions = options.conversions; const conversions = options.conversions;
conversions[idx] = { ...conversions[idx], dateFormat: value.value }; conversions[idx] = { ...conversions[idx], dateFormat: e.currentTarget.value };
onChange({ onChange({
...options, ...options,
conversions: conversions, conversions: conversions,