transformers.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import { findIndex, isObject, map } from 'lodash';
  2. import { Column, TableData } from '@grafana/data';
  3. import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
  4. import TimeSeries from 'app/core/time_series2';
  5. import flatten from 'app/core/utils/flatten';
  6. import { TableTransform } from './types';
  7. const transformers: { [key: string]: TableTransform } = {};
  8. export const timeSeriesFormatFilterer = (data: any): any[] => {
  9. if (!Array.isArray(data)) {
  10. return data.datapoints ? [data] : [];
  11. }
  12. return data.reduce((acc, series) => {
  13. if (!series.datapoints) {
  14. return acc;
  15. }
  16. return acc.concat(series);
  17. }, []);
  18. };
  19. export const tableDataFormatFilterer = (data: any): any[] => {
  20. if (!Array.isArray(data)) {
  21. return data.columns ? [data] : [];
  22. }
  23. return data.reduce((acc, series) => {
  24. if (!series.columns) {
  25. return acc;
  26. }
  27. return acc.concat(series);
  28. }, []);
  29. };
  30. transformers['timeseries_to_rows'] = {
  31. description: 'Time series to rows',
  32. getColumns: () => {
  33. return [];
  34. },
  35. transform: (data, panel, model) => {
  36. model.columns = [{ text: 'Time', type: 'date' }, { text: 'Metric' }, { text: 'Value' }];
  37. const filteredData = timeSeriesFormatFilterer(data);
  38. for (let i = 0; i < filteredData.length; i++) {
  39. const series = filteredData[i];
  40. for (let y = 0; y < series.datapoints.length; y++) {
  41. const dp = series.datapoints[y];
  42. model.rows.push([dp[1], series.target, dp[0]]);
  43. }
  44. }
  45. },
  46. };
  47. transformers['timeseries_to_columns'] = {
  48. description: 'Time series to columns',
  49. getColumns: () => {
  50. return [];
  51. },
  52. transform: (data, panel, model) => {
  53. model.columns.push({ text: 'Time', type: 'date' });
  54. // group by time
  55. const points: any = {};
  56. const filteredData = timeSeriesFormatFilterer(data);
  57. for (let i = 0; i < filteredData.length; i++) {
  58. const series = filteredData[i];
  59. model.columns.push({ text: series.target });
  60. for (let y = 0; y < series.datapoints.length; y++) {
  61. const dp = series.datapoints[y];
  62. const timeKey = dp[1].toString();
  63. if (!points[timeKey]) {
  64. points[timeKey] = { time: dp[1] };
  65. points[timeKey][i] = dp[0];
  66. } else {
  67. points[timeKey][i] = dp[0];
  68. }
  69. }
  70. }
  71. for (const time in points) {
  72. const point = points[time];
  73. const values = [point.time];
  74. for (let i = 0; i < filteredData.length; i++) {
  75. const value = point[i];
  76. values.push(value);
  77. }
  78. model.rows.push(values);
  79. }
  80. },
  81. };
  82. transformers['timeseries_aggregations'] = {
  83. description: 'Time series aggregations',
  84. getColumns: () => {
  85. return [
  86. { text: 'Avg', value: 'avg' },
  87. { text: 'Min', value: 'min' },
  88. { text: 'Max', value: 'max' },
  89. { text: 'Total', value: 'total' },
  90. { text: 'Current', value: 'current' },
  91. { text: 'Count', value: 'count' },
  92. ];
  93. },
  94. transform: (data, panel, model) => {
  95. let i, y;
  96. model.columns.push({ text: 'Metric' });
  97. for (i = 0; i < panel.columns.length; i++) {
  98. model.columns.push({ text: panel.columns[i].text });
  99. }
  100. const filteredData = timeSeriesFormatFilterer(data);
  101. for (i = 0; i < filteredData.length; i++) {
  102. const series = new TimeSeries({
  103. datapoints: filteredData[i].datapoints,
  104. alias: filteredData[i].target,
  105. });
  106. series.getFlotPairs('connected');
  107. const cells = [series.alias];
  108. for (y = 0; y < panel.columns.length; y++) {
  109. cells.push(series.stats[panel.columns[y].value]);
  110. }
  111. model.rows.push(cells);
  112. }
  113. },
  114. };
  115. transformers['annotations'] = {
  116. description: 'Annotations',
  117. getColumns: () => {
  118. return [];
  119. },
  120. transform: (data, panel, model) => {
  121. model.columns.push({ text: 'Time', type: 'date' });
  122. model.columns.push({ text: 'Title' });
  123. model.columns.push({ text: 'Text' });
  124. model.columns.push({ text: 'Tags' });
  125. if (!data || !data.annotations || data.annotations.length === 0) {
  126. return;
  127. }
  128. for (let i = 0; i < data.annotations.length; i++) {
  129. const evt = data.annotations[i];
  130. model.rows.push([evt.time, evt.title, evt.text, evt.tags]);
  131. }
  132. },
  133. };
  134. transformers['table'] = {
  135. description: 'Table',
  136. getColumns: (data) => {
  137. if (!data || data.length === 0) {
  138. return [];
  139. }
  140. // Single query returns data columns as is
  141. if (data.length === 1) {
  142. return [...data[0].columns];
  143. }
  144. const filteredData = tableDataFormatFilterer(data);
  145. // Track column indexes: name -> index
  146. const columnNames: any = {};
  147. // Union of all columns
  148. const columns = filteredData.reduce((acc: Column[], series: TableData) => {
  149. series.columns.forEach((col) => {
  150. const { text } = col;
  151. if (columnNames[text] === undefined) {
  152. columnNames[text] = acc.length;
  153. acc.push(col);
  154. }
  155. });
  156. return acc;
  157. }, []);
  158. return columns;
  159. },
  160. transform: (data: any[], panel, model) => {
  161. if (!data || data.length === 0) {
  162. return;
  163. }
  164. const filteredData = tableDataFormatFilterer(data);
  165. const noTableIndex = findIndex(filteredData, (d) => 'columns' in d && 'rows' in d);
  166. if (noTableIndex < 0) {
  167. throw {
  168. message: `Result of query #${String.fromCharCode(
  169. 65 + noTableIndex
  170. )} is not in table format, try using another transform.`,
  171. };
  172. }
  173. mergeTablesIntoModel(model, ...filteredData);
  174. },
  175. };
  176. transformers['json'] = {
  177. description: 'JSON Data',
  178. getColumns: (data) => {
  179. if (!data || data.length === 0) {
  180. return [];
  181. }
  182. const names: any = {};
  183. for (let i = 0; i < data.length; i++) {
  184. const series = data[i];
  185. if (series.type !== 'docs') {
  186. continue;
  187. }
  188. // only look at 100 docs
  189. const maxDocs = Math.min(series.datapoints.length, 100);
  190. for (let y = 0; y < maxDocs; y++) {
  191. const doc = series.datapoints[y];
  192. const flattened = flatten(doc, {});
  193. for (const propName in flattened) {
  194. names[propName] = true;
  195. }
  196. }
  197. }
  198. return map(names, (value, key) => {
  199. return { text: key, value: key };
  200. });
  201. },
  202. transform: (data, panel, model) => {
  203. let i, y, z;
  204. for (const column of panel.columns) {
  205. const tableCol: any = { text: column.text };
  206. // if filterable data then set columns to filterable
  207. if (data.length > 0 && data[0].filterable) {
  208. tableCol.filterable = true;
  209. }
  210. model.columns.push(tableCol);
  211. }
  212. if (model.columns.length === 0) {
  213. model.columns.push({ text: 'JSON' });
  214. }
  215. for (i = 0; i < data.length; i++) {
  216. const series = data[i];
  217. for (y = 0; y < series.datapoints.length; y++) {
  218. const dp = series.datapoints[y];
  219. const values = [];
  220. if (isObject(dp) && panel.columns.length > 0) {
  221. const flattened = flatten(dp);
  222. for (z = 0; z < panel.columns.length; z++) {
  223. values.push(flattened[panel.columns[z].value]);
  224. }
  225. } else {
  226. values.push(JSON.stringify(dp));
  227. }
  228. model.rows.push(values);
  229. }
  230. }
  231. },
  232. };
  233. function transformDataToTable(data: any, panel: any) {
  234. const model = new TableModel();
  235. if (!data || data.length === 0) {
  236. return model;
  237. }
  238. const transformer = transformers[panel.transform];
  239. if (!transformer) {
  240. throw { message: 'Transformer ' + panel.transform + ' not found' };
  241. }
  242. transformer.transform(data, panel, model);
  243. return model;
  244. }
  245. export { transformers, transformDataToTable };