processSeriesData.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. // Libraries
  2. import isNumber from 'lodash/isNumber';
  3. import isString from 'lodash/isString';
  4. import isBoolean from 'lodash/isBoolean';
  5. import moment from 'moment';
  6. // Types
  7. import { SeriesData, Field, TimeSeries, FieldType, TableData } from '../types/index';
  8. function convertTableToSeriesData(table: TableData): SeriesData {
  9. return {
  10. // rename the 'text' to 'name' field
  11. fields: table.columns.map(c => {
  12. const { text, ...field } = c;
  13. const f = field as Field;
  14. f.name = text;
  15. return f;
  16. }),
  17. rows: table.rows,
  18. refId: table.refId,
  19. meta: table.meta,
  20. };
  21. }
  22. function convertTimeSeriesToSeriesData(timeSeries: TimeSeries): SeriesData {
  23. return {
  24. name: timeSeries.target,
  25. fields: [
  26. {
  27. name: timeSeries.target || 'Value',
  28. unit: timeSeries.unit,
  29. },
  30. {
  31. name: 'Time',
  32. type: FieldType.time,
  33. unit: 'dateTimeAsIso',
  34. },
  35. ],
  36. rows: timeSeries.datapoints,
  37. labels: timeSeries.tags,
  38. refId: timeSeries.refId,
  39. meta: timeSeries.meta,
  40. };
  41. }
  42. export const getFirstTimeField = (series: SeriesData): number => {
  43. const { fields } = series;
  44. for (let i = 0; i < fields.length; i++) {
  45. if (fields[i].type === FieldType.time) {
  46. return i;
  47. }
  48. }
  49. return -1;
  50. };
  51. // PapaParse Dynamic Typing regex:
  52. // https://github.com/mholt/PapaParse/blob/master/papaparse.js#L998
  53. const NUMBER = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i;
  54. /**
  55. * Given a value this will guess the best column type
  56. *
  57. * TODO: better Date/Time support! Look for standard date strings?
  58. */
  59. export function guessFieldTypeFromValue(v: any): FieldType {
  60. if (isNumber(v)) {
  61. return FieldType.number;
  62. }
  63. if (isString(v)) {
  64. if (NUMBER.test(v)) {
  65. return FieldType.number;
  66. }
  67. if (v === 'true' || v === 'TRUE' || v === 'True' || v === 'false' || v === 'FALSE' || v === 'False') {
  68. return FieldType.boolean;
  69. }
  70. return FieldType.string;
  71. }
  72. if (isBoolean(v)) {
  73. return FieldType.boolean;
  74. }
  75. if (v instanceof Date || v instanceof moment) {
  76. return FieldType.time;
  77. }
  78. return FieldType.other;
  79. }
  80. /**
  81. * Looks at the data to guess the column type. This ignores any existing setting
  82. */
  83. export function guessFieldTypeFromSeries(series: SeriesData, index: number): FieldType | undefined {
  84. const column = series.fields[index];
  85. // 1. Use the column name to guess
  86. if (column.name) {
  87. const name = column.name.toLowerCase();
  88. if (name === 'date' || name === 'time') {
  89. return FieldType.time;
  90. }
  91. }
  92. // 2. Check the first non-null value
  93. for (let i = 0; i < series.rows.length; i++) {
  94. const v = series.rows[i][index];
  95. if (v !== null) {
  96. return guessFieldTypeFromValue(v);
  97. }
  98. }
  99. // Could not find anything
  100. return undefined;
  101. }
  102. /**
  103. * @returns a copy of the series with the best guess for each field type
  104. * If the series already has field types defined, they will be used
  105. */
  106. export const guessFieldTypes = (series: SeriesData): SeriesData => {
  107. for (let i = 0; i < series.fields.length; i++) {
  108. if (!series.fields[i].type) {
  109. // Somethign is missing a type return a modified copy
  110. return {
  111. ...series,
  112. fields: series.fields.map((field, index) => {
  113. if (field.type) {
  114. return field;
  115. }
  116. // Replace it with a calculated version
  117. return {
  118. ...field,
  119. type: guessFieldTypeFromSeries(series, index),
  120. };
  121. }),
  122. };
  123. }
  124. }
  125. // No changes necessary
  126. return series;
  127. };
  128. export const isTableData = (data: any): data is SeriesData => data && data.hasOwnProperty('columns');
  129. export const isSeriesData = (data: any): data is SeriesData => data && data.hasOwnProperty('fields');
  130. export const toSeriesData = (data: any): SeriesData => {
  131. if (data.hasOwnProperty('fields')) {
  132. return data as SeriesData;
  133. }
  134. if (data.hasOwnProperty('datapoints')) {
  135. return convertTimeSeriesToSeriesData(data);
  136. }
  137. if (data.hasOwnProperty('columns')) {
  138. return convertTableToSeriesData(data);
  139. }
  140. // TODO, try to convert JSON/Array to seriesta?
  141. console.warn('Can not convert', data);
  142. throw new Error('Unsupported data format');
  143. };
  144. export const toLegacyResponseData = (series: SeriesData): TimeSeries | TableData => {
  145. const { fields, rows } = series;
  146. if (fields.length === 2) {
  147. const type = guessFieldTypeFromSeries(series, 1);
  148. if (type === FieldType.time) {
  149. return {
  150. target: fields[0].name || series.name,
  151. datapoints: rows,
  152. unit: fields[0].unit,
  153. refId: series.refId,
  154. meta: series.meta,
  155. } as TimeSeries;
  156. }
  157. }
  158. return {
  159. columns: fields.map(f => {
  160. return {
  161. text: f.name,
  162. filterable: f.filterable,
  163. unit: f.unit,
  164. refId: series.refId,
  165. meta: series.meta,
  166. };
  167. }),
  168. rows,
  169. };
  170. };
  171. export function sortSeriesData(data: SeriesData, sortIndex?: number, reverse = false): SeriesData {
  172. if (isNumber(sortIndex)) {
  173. const copy = {
  174. ...data,
  175. rows: [...data.rows].sort((a, b) => {
  176. a = a[sortIndex];
  177. b = b[sortIndex];
  178. // Sort null or undefined separately from comparable values
  179. return +(a == null) - +(b == null) || +(a > b) || -(a < b);
  180. }),
  181. };
  182. if (reverse) {
  183. copy.rows.reverse();
  184. }
  185. return copy;
  186. }
  187. return data;
  188. }