9d97f48374
* assume number for TimeSeries types
* use const
(cherry picked from commit 0ba07720df)
206 lines
5.1 KiB
TypeScript
206 lines
5.1 KiB
TypeScript
// Libraries
|
|
import isNumber from 'lodash/isNumber';
|
|
import isString from 'lodash/isString';
|
|
import isBoolean from 'lodash/isBoolean';
|
|
|
|
// Types
|
|
import { DataFrame, Field, TimeSeries, FieldType, TableData, Column } from '../types/index';
|
|
import { isDateTime } from './moment_wrapper';
|
|
|
|
function convertTableToDataFrame(table: TableData): DataFrame {
|
|
return {
|
|
// rename the 'text' to 'name' field
|
|
fields: table.columns.map(c => {
|
|
const { text, ...field } = c;
|
|
const f = field as Field;
|
|
f.name = text;
|
|
return f;
|
|
}),
|
|
rows: table.rows,
|
|
refId: table.refId,
|
|
meta: table.meta,
|
|
name: table.name,
|
|
};
|
|
}
|
|
|
|
function convertTimeSeriesToDataFrame(timeSeries: TimeSeries): DataFrame {
|
|
return {
|
|
name: timeSeries.target,
|
|
fields: [
|
|
{
|
|
name: timeSeries.target || 'Value',
|
|
type: FieldType.number,
|
|
unit: timeSeries.unit,
|
|
},
|
|
{
|
|
name: 'Time',
|
|
type: FieldType.time,
|
|
unit: 'dateTimeAsIso',
|
|
},
|
|
],
|
|
rows: timeSeries.datapoints,
|
|
labels: timeSeries.tags,
|
|
refId: timeSeries.refId,
|
|
meta: timeSeries.meta,
|
|
};
|
|
}
|
|
|
|
// PapaParse Dynamic Typing regex:
|
|
// https://github.com/mholt/PapaParse/blob/master/papaparse.js#L998
|
|
const NUMBER = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i;
|
|
|
|
/**
|
|
* Given a value this will guess the best column type
|
|
*
|
|
* TODO: better Date/Time support! Look for standard date strings?
|
|
*/
|
|
export function guessFieldTypeFromValue(v: any): FieldType {
|
|
if (isNumber(v)) {
|
|
return FieldType.number;
|
|
}
|
|
|
|
if (isString(v)) {
|
|
if (NUMBER.test(v)) {
|
|
return FieldType.number;
|
|
}
|
|
|
|
if (v === 'true' || v === 'TRUE' || v === 'True' || v === 'false' || v === 'FALSE' || v === 'False') {
|
|
return FieldType.boolean;
|
|
}
|
|
|
|
return FieldType.string;
|
|
}
|
|
|
|
if (isBoolean(v)) {
|
|
return FieldType.boolean;
|
|
}
|
|
|
|
if (v instanceof Date || isDateTime(v)) {
|
|
return FieldType.time;
|
|
}
|
|
|
|
return FieldType.other;
|
|
}
|
|
|
|
/**
|
|
* Looks at the data to guess the column type. This ignores any existing setting
|
|
*/
|
|
export function guessFieldTypeFromSeries(series: DataFrame, index: number): FieldType | undefined {
|
|
const column = series.fields[index];
|
|
|
|
// 1. Use the column name to guess
|
|
if (column.name) {
|
|
const name = column.name.toLowerCase();
|
|
if (name === 'date' || name === 'time') {
|
|
return FieldType.time;
|
|
}
|
|
}
|
|
|
|
// 2. Check the first non-null value
|
|
for (let i = 0; i < series.rows.length; i++) {
|
|
const v = series.rows[i][index];
|
|
if (v !== null) {
|
|
return guessFieldTypeFromValue(v);
|
|
}
|
|
}
|
|
|
|
// Could not find anything
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* @returns a copy of the series with the best guess for each field type
|
|
* If the series already has field types defined, they will be used
|
|
*/
|
|
export const guessFieldTypes = (series: DataFrame): DataFrame => {
|
|
for (let i = 0; i < series.fields.length; i++) {
|
|
if (!series.fields[i].type) {
|
|
// Somethign is missing a type return a modified copy
|
|
return {
|
|
...series,
|
|
fields: series.fields.map((field, index) => {
|
|
if (field.type) {
|
|
return field;
|
|
}
|
|
// Replace it with a calculated version
|
|
return {
|
|
...field,
|
|
type: guessFieldTypeFromSeries(series, index),
|
|
};
|
|
}),
|
|
};
|
|
}
|
|
}
|
|
// No changes necessary
|
|
return series;
|
|
};
|
|
|
|
export const isTableData = (data: any): data is DataFrame => data && data.hasOwnProperty('columns');
|
|
|
|
export const isDataFrame = (data: any): data is DataFrame => data && data.hasOwnProperty('fields');
|
|
|
|
export const toDataFrame = (data: any): DataFrame => {
|
|
if (data.hasOwnProperty('fields')) {
|
|
return data as DataFrame;
|
|
}
|
|
if (data.hasOwnProperty('datapoints')) {
|
|
return convertTimeSeriesToDataFrame(data);
|
|
}
|
|
if (data.hasOwnProperty('columns')) {
|
|
return convertTableToDataFrame(data);
|
|
}
|
|
// TODO, try to convert JSON/Array to seriesta?
|
|
console.warn('Can not convert', data);
|
|
throw new Error('Unsupported data format');
|
|
};
|
|
|
|
export const toLegacyResponseData = (series: DataFrame): TimeSeries | TableData => {
|
|
const { fields, rows } = series;
|
|
|
|
if (fields.length === 2) {
|
|
const type = guessFieldTypeFromSeries(series, 1);
|
|
if (type === FieldType.time) {
|
|
return {
|
|
alias: fields[0].name || series.name,
|
|
target: fields[0].name || series.name,
|
|
datapoints: rows,
|
|
unit: fields[0].unit,
|
|
refId: series.refId,
|
|
meta: series.meta,
|
|
} as TimeSeries;
|
|
}
|
|
}
|
|
|
|
return {
|
|
columns: fields.map(f => {
|
|
const { name, ...column } = f;
|
|
(column as Column).text = name;
|
|
return column as Column;
|
|
}),
|
|
refId: series.refId,
|
|
meta: series.meta,
|
|
rows,
|
|
};
|
|
};
|
|
|
|
export function sortDataFrame(data: DataFrame, sortIndex?: number, reverse = false): DataFrame {
|
|
if (isNumber(sortIndex)) {
|
|
const copy = {
|
|
...data,
|
|
rows: [...data.rows].sort((a, b) => {
|
|
a = a[sortIndex];
|
|
b = b[sortIndex];
|
|
// Sort null or undefined separately from comparable values
|
|
return +(a == null) - +(b == null) || +(a > b) || -(a < b);
|
|
}),
|
|
};
|
|
|
|
if (reverse) {
|
|
copy.rows.reverse();
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
return data;
|
|
}
|