query_part.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. ///<reference path="../../../headers/common.d.ts" />
  2. import _ from 'lodash';
  3. var index = [];
  4. var categories = {
  5. Aggregations: [],
  6. Selectors: [],
  7. Transformations: [],
  8. Math: [],
  9. Aliasing: [],
  10. Fields: [],
  11. };
  12. var groupByTimeFunctions = [];
  13. class QueryPartDef {
  14. type: string;
  15. params: any[];
  16. defaultParams: any[];
  17. renderer: any;
  18. category: any;
  19. addStrategy: any;
  20. constructor(options: any) {
  21. this.type = options.type;
  22. this.params = options.params;
  23. this.defaultParams = options.defaultParams;
  24. this.renderer = options.renderer;
  25. this.category = options.category;
  26. this.addStrategy = options.addStrategy;
  27. }
  28. static register(options: any) {
  29. index[options.type] = new QueryPartDef(options);
  30. options.category.push(index[options.type]);
  31. }
  32. }
  33. function functionRenderer(part, innerExpr) {
  34. var str = part.def.type + '(';
  35. var parameters = _.map(part.params, (value, index) => {
  36. var paramType = part.def.params[index];
  37. if (paramType.type === 'time') {
  38. if (value === 'auto') {
  39. value = '$interval';
  40. }
  41. }
  42. if (paramType.quote === 'single') {
  43. return "'" + value + "'";
  44. } else if (paramType.quote === 'double') {
  45. return '"' + value + '"';
  46. }
  47. return value;
  48. });
  49. if (innerExpr) {
  50. parameters.unshift(innerExpr);
  51. }
  52. return str + parameters.join(', ') + ')';
  53. }
  54. function aliasRenderer(part, innerExpr) {
  55. return innerExpr + ' AS ' + '"' + part.params[0] + '"';
  56. }
  57. function suffixRenderer(part, innerExpr) {
  58. return innerExpr + ' ' + part.params[0];
  59. }
  60. function identityRenderer(part, innerExpr) {
  61. return part.params[0];
  62. }
  63. function quotedIdentityRenderer(part, innerExpr) {
  64. return '"' + part.params[0] + '"';
  65. }
  66. function fieldRenderer(part, innerExpr) {
  67. if (part.params[0] === '*') {
  68. return '*';
  69. }
  70. return '"' + part.params[0] + '"';
  71. }
  72. function replaceAggregationAddStrategy(selectParts, partModel) {
  73. // look for existing aggregation
  74. for (var i = 0; i < selectParts.length; i++) {
  75. var part = selectParts[i];
  76. if (part.def.category === categories.Aggregations) {
  77. selectParts[i] = partModel;
  78. return;
  79. }
  80. if (part.def.category === categories.Selectors) {
  81. selectParts[i] = partModel;
  82. return;
  83. }
  84. }
  85. selectParts.splice(1, 0, partModel);
  86. }
  87. function addTransformationStrategy(selectParts, partModel) {
  88. var i;
  89. // look for index to add transformation
  90. for (i = 0; i < selectParts.length; i++) {
  91. var part = selectParts[i];
  92. if (part.def.category === categories.Math || part.def.category === categories.Aliasing) {
  93. break;
  94. }
  95. }
  96. selectParts.splice(i, 0, partModel);
  97. }
  98. function addMathStrategy(selectParts, partModel) {
  99. var partCount = selectParts.length;
  100. if (partCount > 0) {
  101. // if last is math, replace it
  102. if (selectParts[partCount-1].def.type === 'math') {
  103. selectParts[partCount-1] = partModel;
  104. return;
  105. }
  106. // if next to last is math, replace it
  107. if (selectParts[partCount-2].def.type === 'math') {
  108. selectParts[partCount-2] = partModel;
  109. return;
  110. } else if (selectParts[partCount-1].def.type === 'alias') { // if last is alias add it before
  111. selectParts.splice(partCount-1, 0, partModel);
  112. return;
  113. }
  114. }
  115. selectParts.push(partModel);
  116. }
  117. function addAliasStrategy(selectParts, partModel) {
  118. var partCount = selectParts.length;
  119. if (partCount > 0) {
  120. // if last is alias, replace it
  121. if (selectParts[partCount-1].def.type === 'alias') {
  122. selectParts[partCount-1] = partModel;
  123. return;
  124. }
  125. }
  126. selectParts.push(partModel);
  127. }
  128. function addFieldStrategy(selectParts, partModel, query) {
  129. // copy all parts
  130. var parts = _.map(selectParts, function(part: any) {
  131. return new QueryPart({type: part.def.type, params: _.clone(part.params)});
  132. });
  133. query.selectModels.push(parts);
  134. }
  135. QueryPartDef.register({
  136. type: 'field',
  137. addStrategy: addFieldStrategy,
  138. category: categories.Fields,
  139. params: [{type: 'field', dynamicLookup: true}],
  140. defaultParams: ['value'],
  141. renderer: fieldRenderer,
  142. });
  143. // Aggregations
  144. QueryPartDef.register({
  145. type: 'count',
  146. addStrategy: replaceAggregationAddStrategy,
  147. category: categories.Aggregations,
  148. params: [],
  149. defaultParams: [],
  150. renderer: functionRenderer,
  151. });
  152. QueryPartDef.register({
  153. type: 'distinct',
  154. addStrategy: replaceAggregationAddStrategy,
  155. category: categories.Aggregations,
  156. params: [],
  157. defaultParams: [],
  158. renderer: functionRenderer,
  159. });
  160. QueryPartDef.register({
  161. type: 'integral',
  162. addStrategy: replaceAggregationAddStrategy,
  163. category: categories.Aggregations,
  164. params: [],
  165. defaultParams: [],
  166. renderer: functionRenderer,
  167. });
  168. QueryPartDef.register({
  169. type: 'mean',
  170. addStrategy: replaceAggregationAddStrategy,
  171. category: categories.Aggregations,
  172. params: [],
  173. defaultParams: [],
  174. renderer: functionRenderer,
  175. });
  176. QueryPartDef.register({
  177. type: 'median',
  178. addStrategy: replaceAggregationAddStrategy,
  179. category: categories.Aggregations,
  180. params: [],
  181. defaultParams: [],
  182. renderer: functionRenderer,
  183. });
  184. QueryPartDef.register({
  185. type: 'sum',
  186. addStrategy: replaceAggregationAddStrategy,
  187. category: categories.Aggregations,
  188. params: [],
  189. defaultParams: [],
  190. renderer: functionRenderer,
  191. });
  192. // transformations
  193. QueryPartDef.register({
  194. type: 'derivative',
  195. addStrategy: addTransformationStrategy,
  196. category: categories.Transformations,
  197. params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']}],
  198. defaultParams: ['10s'],
  199. renderer: functionRenderer,
  200. });
  201. QueryPartDef.register({
  202. type: 'spread',
  203. addStrategy: addTransformationStrategy,
  204. category: categories.Transformations,
  205. params: [],
  206. defaultParams: [],
  207. renderer: functionRenderer,
  208. });
  209. QueryPartDef.register({
  210. type: 'non_negative_derivative',
  211. addStrategy: addTransformationStrategy,
  212. category: categories.Transformations,
  213. params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']}],
  214. defaultParams: ['10s'],
  215. renderer: functionRenderer,
  216. });
  217. QueryPartDef.register({
  218. type: 'difference',
  219. addStrategy: addTransformationStrategy,
  220. category: categories.Transformations,
  221. params: [],
  222. defaultParams: [],
  223. renderer: functionRenderer,
  224. });
  225. QueryPartDef.register({
  226. type: 'moving_average',
  227. addStrategy: addTransformationStrategy,
  228. category: categories.Transformations,
  229. params: [{ name: "window", type: "number", options: [5, 10, 20, 30, 40]}],
  230. defaultParams: [10],
  231. renderer: functionRenderer,
  232. });
  233. QueryPartDef.register({
  234. type: 'stddev',
  235. addStrategy: addTransformationStrategy,
  236. category: categories.Transformations,
  237. params: [],
  238. defaultParams: [],
  239. renderer: functionRenderer,
  240. });
  241. QueryPartDef.register({
  242. type: 'time',
  243. category: groupByTimeFunctions,
  244. params: [{ name: "interval", type: "time", options: ['auto', '1s', '10s', '1m', '5m', '10m', '15m', '1h'] }],
  245. defaultParams: ['auto'],
  246. renderer: functionRenderer,
  247. });
  248. QueryPartDef.register({
  249. type: 'fill',
  250. category: groupByTimeFunctions,
  251. params: [{ name: "fill", type: "string", options: ['none', 'null', '0', 'previous'] }],
  252. defaultParams: ['null'],
  253. renderer: functionRenderer,
  254. });
  255. // Selectors
  256. QueryPartDef.register({
  257. type: 'bottom',
  258. addStrategy: replaceAggregationAddStrategy,
  259. category: categories.Selectors,
  260. params: [{name: 'count', type: 'int'}],
  261. defaultParams: [3],
  262. renderer: functionRenderer,
  263. });
  264. QueryPartDef.register({
  265. type: 'first',
  266. addStrategy: replaceAggregationAddStrategy,
  267. category: categories.Selectors,
  268. params: [],
  269. defaultParams: [],
  270. renderer: functionRenderer,
  271. });
  272. QueryPartDef.register({
  273. type: 'last',
  274. addStrategy: replaceAggregationAddStrategy,
  275. category: categories.Selectors,
  276. params: [],
  277. defaultParams: [],
  278. renderer: functionRenderer,
  279. });
  280. QueryPartDef.register({
  281. type: 'max',
  282. addStrategy: replaceAggregationAddStrategy,
  283. category: categories.Selectors,
  284. params: [],
  285. defaultParams: [],
  286. renderer: functionRenderer,
  287. });
  288. QueryPartDef.register({
  289. type: 'min',
  290. addStrategy: replaceAggregationAddStrategy,
  291. category: categories.Selectors,
  292. params: [],
  293. defaultParams: [],
  294. renderer: functionRenderer,
  295. });
  296. QueryPartDef.register({
  297. type: 'percentile',
  298. addStrategy: replaceAggregationAddStrategy,
  299. category: categories.Selectors,
  300. params: [{name: 'nth', type: 'int'}],
  301. defaultParams: [95],
  302. renderer: functionRenderer,
  303. });
  304. QueryPartDef.register({
  305. type: 'top',
  306. addStrategy: replaceAggregationAddStrategy,
  307. category: categories.Selectors,
  308. params: [{name: 'count', type: 'int'}],
  309. defaultParams: [3],
  310. renderer: functionRenderer,
  311. });
  312. QueryPartDef.register({
  313. type: 'tag',
  314. category: groupByTimeFunctions,
  315. params: [{name: 'tag', type: 'string', dynamicLookup: true}],
  316. defaultParams: ['tag'],
  317. renderer: fieldRenderer,
  318. });
  319. QueryPartDef.register({
  320. type: 'math',
  321. addStrategy: addMathStrategy,
  322. category: categories.Math,
  323. params: [{ name: "expr", type: "string"}],
  324. defaultParams: [' / 100'],
  325. renderer: suffixRenderer,
  326. });
  327. QueryPartDef.register({
  328. type: 'alias',
  329. addStrategy: addAliasStrategy,
  330. category: categories.Aliasing,
  331. params: [{ name: "name", type: "string", quote: 'double'}],
  332. defaultParams: ['alias'],
  333. renderMode: 'suffix',
  334. renderer: aliasRenderer,
  335. });
  336. class QueryPart {
  337. part: any;
  338. def: QueryPartDef;
  339. params: any[];
  340. text: string;
  341. constructor(part: any) {
  342. this.part = part;
  343. this.def = index[part.type];
  344. if (!this.def) {
  345. throw {message: 'Could not find query part ' + part.type};
  346. }
  347. part.params = part.params || _.clone(this.def.defaultParams);
  348. this.params = part.params;
  349. this.updateText();
  350. }
  351. render(innerExpr: string) {
  352. return this.def.renderer(this, innerExpr);
  353. }
  354. hasMultipleParamsInString (strValue, index) {
  355. if (strValue.indexOf(',') === -1) {
  356. return false;
  357. }
  358. return this.def.params[index + 1] && this.def.params[index + 1].optional;
  359. }
  360. updateParam (strValue, index) {
  361. // handle optional parameters
  362. // if string contains ',' and next param is optional, split and update both
  363. if (this.hasMultipleParamsInString(strValue, index)) {
  364. _.each(strValue.split(','), function(partVal: string, idx) {
  365. this.updateParam(partVal.trim(), idx);
  366. }, this);
  367. return;
  368. }
  369. if (strValue === '' && this.def.params[index].optional) {
  370. this.params.splice(index, 1);
  371. } else {
  372. this.params[index] = strValue;
  373. }
  374. this.part.params = this.params;
  375. this.updateText();
  376. }
  377. updateText() {
  378. if (this.params.length === 0) {
  379. this.text = this.def.type + '()';
  380. return;
  381. }
  382. var text = this.def.type + '(';
  383. text += this.params.join(', ');
  384. text += ')';
  385. this.text = text;
  386. }
  387. }
  388. export default {
  389. create: function(part): any {
  390. return new QueryPart(part);
  391. },
  392. getCategories: function() {
  393. return categories;
  394. }
  395. };