response_parser.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import { each, flatten, groupBy, isArray } from 'lodash';
  2. import { AnnotationEvent, DataFrame, DataQuery, FieldType, QueryResultMeta } from '@grafana/data';
  3. import { toDataQueryResponse } from '@grafana/runtime';
  4. import TableModel from 'app/core/table_model';
  5. import { InfluxQuery } from './types';
  6. export default class ResponseParser {
  7. parse(query: string, results: { results: any }) {
  8. if (!results?.results || results.results.length === 0) {
  9. return [];
  10. }
  11. const influxResults = results.results[0];
  12. if (!influxResults.series) {
  13. return [];
  14. }
  15. const normalizedQuery = query.toLowerCase();
  16. const isValueFirst =
  17. normalizedQuery.indexOf('show field keys') >= 0 || normalizedQuery.indexOf('show retention policies') >= 0;
  18. const res = new Set<string>();
  19. each(influxResults.series, (serie) => {
  20. each(serie.values, (value) => {
  21. if (isArray(value)) {
  22. // In general, there are 2 possible shapes for the returned value.
  23. // The first one is a two-element array,
  24. // where the first element is somewhat a metadata value:
  25. // the tag name for SHOW TAG VALUES queries,
  26. // the time field for SELECT queries, etc.
  27. // The second shape is an one-element array,
  28. // that is containing an immediate value.
  29. // For example, SHOW FIELD KEYS queries return such shape.
  30. // Note, pre-0.11 versions return
  31. // the second shape for SHOW TAG VALUES queries
  32. // (while the newer versions—first).
  33. if (isValueFirst) {
  34. addUnique(res, value[0]);
  35. } else if (value[1] !== undefined) {
  36. addUnique(res, value[1]);
  37. } else {
  38. addUnique(res, value[0]);
  39. }
  40. } else {
  41. addUnique(res, value);
  42. }
  43. });
  44. });
  45. // NOTE: it is important to keep the order of items in the parsed output
  46. // the same as it was in the influxdb-response.
  47. // we use a `Set` to collect the unique-results, and `Set` iteration
  48. // order is insertion-order, so this should be ok.
  49. return Array.from(res).map((v) => ({ text: v }));
  50. }
  51. getTable(dfs: DataFrame[], target: InfluxQuery, meta: QueryResultMeta): TableModel {
  52. let table = new TableModel();
  53. if (dfs.length > 0) {
  54. table.meta = {
  55. ...meta,
  56. executedQueryString: dfs[0].meta?.executedQueryString,
  57. };
  58. table.refId = target.refId;
  59. table = getTableCols(dfs, table, target);
  60. // if group by tag(s) added
  61. if (dfs[0].fields[1] && dfs[0].fields[1].labels) {
  62. let dfsByLabels: any = groupBy(dfs, (df: DataFrame) =>
  63. df.fields[1].labels ? Object.values(df.fields[1].labels!) : null
  64. );
  65. const labels = Object.keys(dfsByLabels);
  66. dfsByLabels = Object.values(dfsByLabels);
  67. for (let i = 0; i < dfsByLabels.length; i++) {
  68. table = getTableRows(dfsByLabels[i], table, [...labels[i].split(',')]);
  69. }
  70. } else {
  71. table = getTableRows(dfs, table, []);
  72. }
  73. }
  74. return table;
  75. }
  76. async transformAnnotationResponse(options: any, data: any, target: InfluxQuery): Promise<AnnotationEvent[]> {
  77. const rsp = toDataQueryResponse(data, [target] as DataQuery[]);
  78. if (rsp) {
  79. const table = this.getTable(rsp.data, target, {});
  80. const list: any[] = [];
  81. let titleCol: any = null;
  82. let timeCol: any = null;
  83. let timeEndCol: any = null;
  84. const tagsCol: any = [];
  85. let textCol: any = null;
  86. each(table.columns, (column, index) => {
  87. if (column.text.toLowerCase() === 'time') {
  88. timeCol = index;
  89. return;
  90. }
  91. if (column.text === options.annotation.titleColumn) {
  92. titleCol = index;
  93. return;
  94. }
  95. if (colContainsTag(column.text, options.annotation.tagsColumn)) {
  96. tagsCol.push(index);
  97. return;
  98. }
  99. if (column.text.includes(options.annotation.textColumn)) {
  100. textCol = index;
  101. return;
  102. }
  103. if (column.text === options.annotation.timeEndColumn) {
  104. timeEndCol = index;
  105. return;
  106. }
  107. // legacy case
  108. if (!titleCol && textCol !== index) {
  109. titleCol = index;
  110. }
  111. });
  112. each(table.rows, (value) => {
  113. const data = {
  114. annotation: options.annotation,
  115. time: +new Date(value[timeCol]),
  116. title: value[titleCol],
  117. timeEnd: value[timeEndCol],
  118. // Remove empty values, then split in different tags for comma separated values
  119. tags: flatten(
  120. tagsCol
  121. .filter((t: any) => {
  122. return value[t];
  123. })
  124. .map((t: any) => {
  125. return value[t].split(',');
  126. })
  127. ),
  128. text: value[textCol],
  129. };
  130. list.push(data);
  131. });
  132. return list;
  133. }
  134. return [];
  135. }
  136. }
  137. function colContainsTag(colText: string, tagsColumn: string): boolean {
  138. const tags = (tagsColumn || '').replace(' ', '').split(',');
  139. for (var tag of tags) {
  140. if (colText.includes(tag)) {
  141. return true;
  142. }
  143. }
  144. return false;
  145. }
  146. function getTableCols(dfs: DataFrame[], table: TableModel, target: InfluxQuery): TableModel {
  147. const selectedParams = getSelectedParams(target);
  148. dfs[0].fields.forEach((field) => {
  149. // Time col
  150. if (field.name === 'time') {
  151. table.columns.push({ text: 'Time', type: FieldType.time });
  152. }
  153. // Group by (label) column(s)
  154. else if (field.name === 'value') {
  155. if (field.labels) {
  156. Object.keys(field.labels).forEach((key) => {
  157. table.columns.push({ text: key });
  158. });
  159. }
  160. }
  161. });
  162. // Get cols for annotationQuery
  163. if (dfs[0].refId === 'metricFindQuery') {
  164. dfs.forEach((field) => {
  165. if (field.name) {
  166. table.columns.push({ text: field.name });
  167. }
  168. });
  169. }
  170. // Select (metric) column(s)
  171. for (let i = 0; i < selectedParams.length; i++) {
  172. table.columns.push({ text: selectedParams[i] });
  173. }
  174. return table;
  175. }
  176. function getTableRows(dfs: DataFrame[], table: TableModel, labels: string[]): TableModel {
  177. const values = dfs[0].fields[0].values.toArray();
  178. for (let i = 0; i < values.length; i++) {
  179. const time = values[i];
  180. const metrics = dfs.map((df: DataFrame) => {
  181. return df.fields[1] ? df.fields[1].values.toArray()[i] : null;
  182. });
  183. if (metrics.indexOf(null) < 0) {
  184. table.rows.push([time, ...labels, ...metrics]);
  185. }
  186. }
  187. return table;
  188. }
  189. export function getSelectedParams(target: InfluxQuery): string[] {
  190. let allParams: string[] = [];
  191. target.select?.forEach((select) => {
  192. const selector = select.filter((x) => x.type !== 'field');
  193. if (selector.length > 0) {
  194. allParams.push(selector[0].type);
  195. } else {
  196. if (select[0] && select[0].params && select[0].params[0]) {
  197. allParams.push(select[0].params[0].toString());
  198. }
  199. }
  200. });
  201. let uniqueParams: string[] = [];
  202. allParams.forEach((param) => {
  203. uniqueParams.push(incrementName(param, param, uniqueParams, 0));
  204. });
  205. return uniqueParams;
  206. }
  207. function incrementName(name: string, nameIncremenet: string, params: string[], index: number): string {
  208. if (params.indexOf(nameIncremenet) > -1) {
  209. index++;
  210. return incrementName(name, name + '_' + index, params, index);
  211. }
  212. return nameIncremenet;
  213. }
  214. function addUnique(s: Set<string>, value: string | number) {
  215. s.add(value.toString());
  216. }