query_ctrl.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. import './add_graphite_func';
  2. import './func_editor';
  3. import _ from 'lodash';
  4. import GraphiteQuery from './graphite_query';
  5. import { QueryCtrl } from 'app/plugins/sdk';
  6. import appEvents from 'app/core/app_events';
  7. import { auto } from 'angular';
  8. import { TemplateSrv } from 'app/features/templating/template_srv';
  9. const GRAPHITE_TAG_OPERATORS = ['=', '!=', '=~', '!=~'];
  10. const TAG_PREFIX = 'tag: ';
  11. export class GraphiteQueryCtrl extends QueryCtrl {
  12. static templateUrl = 'partials/query.editor.html';
  13. queryModel: GraphiteQuery;
  14. segments: any[];
  15. addTagSegments: any[];
  16. removeTagValue: string;
  17. supportsTags: boolean;
  18. paused: boolean;
  19. /** @ngInject */
  20. constructor(
  21. $scope: any,
  22. $injector: auto.IInjectorService,
  23. private uiSegmentSrv: any,
  24. private templateSrv: TemplateSrv,
  25. $timeout: any
  26. ) {
  27. super($scope, $injector);
  28. this.supportsTags = this.datasource.supportsTags;
  29. this.paused = false;
  30. this.target.target = this.target.target || '';
  31. this.datasource.waitForFuncDefsLoaded().then(() => {
  32. this.queryModel = new GraphiteQuery(this.datasource, this.target, templateSrv);
  33. this.buildSegments();
  34. });
  35. this.removeTagValue = '-- remove tag --';
  36. }
  37. parseTarget() {
  38. this.queryModel.parseTarget();
  39. this.buildSegments();
  40. }
  41. toggleEditorMode() {
  42. this.target.textEditor = !this.target.textEditor;
  43. this.parseTarget();
  44. }
  45. buildSegments() {
  46. this.segments = _.map(this.queryModel.segments, segment => {
  47. return this.uiSegmentSrv.newSegment(segment);
  48. });
  49. const checkOtherSegmentsIndex = this.queryModel.checkOtherSegmentsIndex || 0;
  50. this.checkOtherSegments(checkOtherSegmentsIndex);
  51. if (this.queryModel.seriesByTagUsed) {
  52. this.fixTagSegments();
  53. }
  54. }
  55. addSelectMetricSegment() {
  56. this.queryModel.addSelectMetricSegment();
  57. this.segments.push(this.uiSegmentSrv.newSelectMetric());
  58. }
  59. checkOtherSegments(fromIndex: number) {
  60. if (this.queryModel.segments.length === 1 && this.queryModel.segments[0].type === 'series-ref') {
  61. return;
  62. }
  63. if (fromIndex === 0) {
  64. this.addSelectMetricSegment();
  65. return;
  66. }
  67. const path = this.queryModel.getSegmentPathUpTo(fromIndex + 1);
  68. if (path === '') {
  69. return Promise.resolve();
  70. }
  71. return this.datasource
  72. .metricFindQuery(path)
  73. .then((segments: any) => {
  74. if (segments.length === 0) {
  75. if (path !== '') {
  76. this.queryModel.segments = this.queryModel.segments.splice(0, fromIndex);
  77. this.segments = this.segments.splice(0, fromIndex);
  78. this.addSelectMetricSegment();
  79. }
  80. } else if (segments[0].expandable) {
  81. if (this.segments.length === fromIndex) {
  82. this.addSelectMetricSegment();
  83. } else {
  84. return this.checkOtherSegments(fromIndex + 1);
  85. }
  86. }
  87. })
  88. .catch((err: any) => {
  89. appEvents.emit('alert-error', ['Error', err]);
  90. });
  91. }
  92. setSegmentFocus(segmentIndex: any) {
  93. _.each(this.segments, (segment, index) => {
  94. segment.focus = segmentIndex === index;
  95. });
  96. }
  97. getAltSegments(index: number, prefix: string) {
  98. let query = prefix && prefix.length > 0 ? '*' + prefix + '*' : '*';
  99. if (index > 0) {
  100. query = this.queryModel.getSegmentPathUpTo(index) + '.' + query;
  101. }
  102. const options = {
  103. range: this.panelCtrl.range,
  104. requestId: 'get-alt-segments',
  105. };
  106. return this.datasource
  107. .metricFindQuery(query, options)
  108. .then((segments: any[]) => {
  109. const altSegments = _.map(segments, segment => {
  110. return this.uiSegmentSrv.newSegment({
  111. value: segment.text,
  112. expandable: segment.expandable,
  113. });
  114. });
  115. if (index > 0 && altSegments.length === 0) {
  116. return altSegments;
  117. }
  118. // add query references
  119. if (index === 0) {
  120. _.eachRight(this.panelCtrl.panel.targets, target => {
  121. if (target.refId === this.queryModel.target.refId) {
  122. return;
  123. }
  124. altSegments.unshift(
  125. this.uiSegmentSrv.newSegment({
  126. type: 'series-ref',
  127. value: '#' + target.refId,
  128. expandable: false,
  129. })
  130. );
  131. });
  132. }
  133. // add template variables
  134. _.eachRight(this.templateSrv.variables, variable => {
  135. altSegments.unshift(
  136. this.uiSegmentSrv.newSegment({
  137. type: 'template',
  138. value: '$' + variable.name,
  139. expandable: true,
  140. })
  141. );
  142. });
  143. // add wildcard option
  144. altSegments.unshift(this.uiSegmentSrv.newSegment('*'));
  145. if (this.supportsTags && index === 0) {
  146. this.removeTaggedEntry(altSegments);
  147. return this.addAltTagSegments(prefix, altSegments);
  148. } else {
  149. return altSegments;
  150. }
  151. })
  152. .catch(
  153. (err: any): any[] => {
  154. return [];
  155. }
  156. );
  157. }
  158. addAltTagSegments(prefix: string, altSegments: any[]) {
  159. return this.getTagsAsSegments(prefix).then((tagSegments: any[]) => {
  160. tagSegments = _.map(tagSegments, segment => {
  161. segment.value = TAG_PREFIX + segment.value;
  162. return segment;
  163. });
  164. return altSegments.concat(...tagSegments);
  165. });
  166. }
  167. removeTaggedEntry(altSegments: any[]) {
  168. altSegments = _.remove(altSegments, s => s.value === '_tagged');
  169. }
  170. segmentValueChanged(segment: { type: string; value: string; expandable: any }, segmentIndex: number) {
  171. this.error = null;
  172. this.queryModel.updateSegmentValue(segment, segmentIndex);
  173. if (this.queryModel.functions.length > 0 && this.queryModel.functions[0].def.fake) {
  174. this.queryModel.functions = [];
  175. }
  176. if (segment.type === 'tag') {
  177. const tag = removeTagPrefix(segment.value);
  178. this.pause();
  179. this.addSeriesByTagFunc(tag);
  180. return;
  181. }
  182. if (segment.expandable) {
  183. return this.checkOtherSegments(segmentIndex + 1).then(() => {
  184. this.setSegmentFocus(segmentIndex + 1);
  185. this.targetChanged();
  186. });
  187. } else {
  188. this.spliceSegments(segmentIndex + 1);
  189. }
  190. this.setSegmentFocus(segmentIndex + 1);
  191. this.targetChanged();
  192. }
  193. spliceSegments(index: any) {
  194. this.segments = this.segments.splice(0, index);
  195. this.queryModel.segments = this.queryModel.segments.splice(0, index);
  196. }
  197. emptySegments() {
  198. this.queryModel.segments = [];
  199. this.segments = [];
  200. }
  201. targetTextChanged() {
  202. this.updateModelTarget();
  203. this.refresh();
  204. }
  205. updateModelTarget() {
  206. this.queryModel.updateModelTarget(this.panelCtrl.panel.targets);
  207. }
  208. targetChanged() {
  209. if (this.queryModel.error) {
  210. return;
  211. }
  212. const oldTarget = this.queryModel.target.target;
  213. this.updateModelTarget();
  214. if (this.queryModel.target !== oldTarget && !this.paused) {
  215. this.panelCtrl.refresh();
  216. }
  217. }
  218. addFunction(funcDef: any) {
  219. const newFunc = this.datasource.createFuncInstance(funcDef, {
  220. withDefaultParams: true,
  221. });
  222. newFunc.added = true;
  223. this.queryModel.addFunction(newFunc);
  224. this.smartlyHandleNewAliasByNode(newFunc);
  225. if (this.segments.length === 1 && this.segments[0].fake) {
  226. this.emptySegments();
  227. }
  228. if (!newFunc.params.length && newFunc.added) {
  229. this.targetChanged();
  230. }
  231. if (newFunc.def.name === 'seriesByTag') {
  232. this.parseTarget();
  233. }
  234. }
  235. removeFunction(func: any) {
  236. this.queryModel.removeFunction(func);
  237. this.targetChanged();
  238. }
  239. moveFunction(func: any, offset: any) {
  240. this.queryModel.moveFunction(func, offset);
  241. this.targetChanged();
  242. }
  243. addSeriesByTagFunc(tag: string) {
  244. const newFunc = this.datasource.createFuncInstance('seriesByTag', {
  245. withDefaultParams: false,
  246. });
  247. const tagParam = `${tag}=`;
  248. newFunc.params = [tagParam];
  249. this.queryModel.addFunction(newFunc);
  250. newFunc.added = true;
  251. this.emptySegments();
  252. this.targetChanged();
  253. this.parseTarget();
  254. }
  255. smartlyHandleNewAliasByNode(func: { def: { name: string }; params: number[]; added: boolean }) {
  256. if (func.def.name !== 'aliasByNode') {
  257. return;
  258. }
  259. for (let i = 0; i < this.segments.length; i++) {
  260. if (this.segments[i].value.indexOf('*') >= 0) {
  261. func.params[0] = i;
  262. func.added = false;
  263. this.targetChanged();
  264. return;
  265. }
  266. }
  267. }
  268. getAllTags() {
  269. return this.datasource.getTags().then((values: any[]) => {
  270. const altTags = _.map(values, 'text');
  271. altTags.splice(0, 0, this.removeTagValue);
  272. return mapToDropdownOptions(altTags);
  273. });
  274. }
  275. getTags(index: number, tagPrefix: any) {
  276. const tagExpressions = this.queryModel.renderTagExpressions(index);
  277. return this.datasource.getTagsAutoComplete(tagExpressions, tagPrefix).then((values: any) => {
  278. const altTags = _.map(values, 'text');
  279. altTags.splice(0, 0, this.removeTagValue);
  280. return mapToDropdownOptions(altTags);
  281. });
  282. }
  283. getTagsAsSegments(tagPrefix: string) {
  284. const tagExpressions = this.queryModel.renderTagExpressions();
  285. return this.datasource.getTagsAutoComplete(tagExpressions, tagPrefix).then((values: any) => {
  286. return _.map(values, val => {
  287. return this.uiSegmentSrv.newSegment({
  288. value: val.text,
  289. type: 'tag',
  290. expandable: false,
  291. });
  292. });
  293. });
  294. }
  295. getTagOperators() {
  296. return mapToDropdownOptions(GRAPHITE_TAG_OPERATORS);
  297. }
  298. getAllTagValues(tag: { key: any }) {
  299. const tagKey = tag.key;
  300. return this.datasource.getTagValues(tagKey).then((values: any[]) => {
  301. const altValues = _.map(values, 'text');
  302. return mapToDropdownOptions(altValues);
  303. });
  304. }
  305. getTagValues(tag: { key: any }, index: number, valuePrefix: any) {
  306. const tagExpressions = this.queryModel.renderTagExpressions(index);
  307. const tagKey = tag.key;
  308. return this.datasource.getTagValuesAutoComplete(tagExpressions, tagKey, valuePrefix).then((values: any[]) => {
  309. const altValues = _.map(values, 'text');
  310. // Add template variables as additional values
  311. _.eachRight(this.templateSrv.variables, variable => {
  312. altValues.push('${' + variable.name + ':regex}');
  313. });
  314. return mapToDropdownOptions(altValues);
  315. });
  316. }
  317. tagChanged(tag: any, tagIndex: any) {
  318. this.queryModel.updateTag(tag, tagIndex);
  319. this.targetChanged();
  320. }
  321. addNewTag(segment: { value: any }) {
  322. const newTagKey = segment.value;
  323. const newTag = { key: newTagKey, operator: '=', value: '' };
  324. this.queryModel.addTag(newTag);
  325. this.targetChanged();
  326. this.fixTagSegments();
  327. }
  328. removeTag(index: any) {
  329. this.queryModel.removeTag(index);
  330. this.targetChanged();
  331. }
  332. fixTagSegments() {
  333. // Adding tag with the same name as just removed works incorrectly if single segment is used (instead of array)
  334. this.addTagSegments = [this.uiSegmentSrv.newPlusButton()];
  335. }
  336. showDelimiter(index: number) {
  337. return index !== this.queryModel.tags.length - 1;
  338. }
  339. pause() {
  340. this.paused = true;
  341. }
  342. unpause() {
  343. this.paused = false;
  344. this.panelCtrl.refresh();
  345. }
  346. getCollapsedText() {
  347. return this.target.target;
  348. }
  349. }
  350. function mapToDropdownOptions(results: any[]) {
  351. return _.map(results, value => {
  352. return { text: value, value: value };
  353. });
  354. }
  355. function removeTagPrefix(value: string): string {
  356. return value.replace(TAG_PREFIX, '');
  357. }