| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- // Libraries
- import isNumber from 'lodash/isNumber';
- import isString from 'lodash/isString';
- import isBoolean from 'lodash/isBoolean';
- import moment from 'moment';
- // Types
- import { SeriesData, Field, TimeSeries, FieldType, TableData } from '../types/index';
- function convertTableToSeriesData(table: TableData): SeriesData {
- 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,
- };
- }
- function convertTimeSeriesToSeriesData(timeSeries: TimeSeries): SeriesData {
- return {
- name: timeSeries.target,
- fields: [
- {
- name: timeSeries.target || 'Value',
- unit: timeSeries.unit,
- },
- {
- name: 'Time',
- type: FieldType.time,
- unit: 'dateTimeAsIso',
- },
- ],
- rows: timeSeries.datapoints,
- labels: timeSeries.tags,
- refId: timeSeries.refId,
- meta: timeSeries.meta,
- };
- }
- export const getFirstTimeField = (series: SeriesData): number => {
- const { fields } = series;
- for (let i = 0; i < fields.length; i++) {
- if (fields[i].type === FieldType.time) {
- return i;
- }
- }
- return -1;
- };
- // 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 || v instanceof moment) {
- return FieldType.time;
- }
- return FieldType.other;
- }
- /**
- * Looks at the data to guess the column type. This ignores any existing setting
- */
- export function guessFieldTypeFromSeries(series: SeriesData, 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: SeriesData): SeriesData => {
- 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 SeriesData => data && data.hasOwnProperty('columns');
- export const isSeriesData = (data: any): data is SeriesData => data && data.hasOwnProperty('fields');
- export const toSeriesData = (data: any): SeriesData => {
- if (data.hasOwnProperty('fields')) {
- return data as SeriesData;
- }
- if (data.hasOwnProperty('datapoints')) {
- return convertTimeSeriesToSeriesData(data);
- }
- if (data.hasOwnProperty('columns')) {
- return convertTableToSeriesData(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: SeriesData): TimeSeries | TableData => {
- const { fields, rows } = series;
- if (fields.length === 2) {
- const type = guessFieldTypeFromSeries(series, 1);
- if (type === FieldType.time) {
- return {
- 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 => {
- return {
- text: f.name,
- filterable: f.filterable,
- unit: f.unit,
- refId: series.refId,
- meta: series.meta,
- };
- }),
- rows,
- };
- };
- export function sortSeriesData(data: SeriesData, sortIndex?: number, reverse = false): SeriesData {
- 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;
- }
|