add_graphite_func.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import _ from 'lodash';
  2. import $ from 'jquery';
  3. import rst2html from 'rst2html';
  4. import Drop from 'tether-drop';
  5. import coreModule from 'app/core/core_module';
  6. /** @ngInject */
  7. export function graphiteAddFunc($compile) {
  8. const inputTemplate =
  9. '<input type="text"' + ' class="gf-form-input"' + ' spellcheck="false" style="display:none"></input>';
  10. const buttonTemplate =
  11. '<a class="gf-form-label query-part dropdown-toggle"' +
  12. ' tabindex="1" gf-dropdown="functionMenu" data-toggle="dropdown">' +
  13. '<i class="fa fa-plus"></i></a>';
  14. return {
  15. link: function($scope, elem) {
  16. const ctrl = $scope.ctrl;
  17. const $input = $(inputTemplate);
  18. const $button = $(buttonTemplate);
  19. $input.appendTo(elem);
  20. $button.appendTo(elem);
  21. ctrl.datasource.getFuncDefs().then(funcDefs => {
  22. const allFunctions = _.map(funcDefs, 'name').sort();
  23. $scope.functionMenu = createFunctionDropDownMenu(funcDefs);
  24. $input.attr('data-provide', 'typeahead');
  25. $input.typeahead({
  26. source: allFunctions,
  27. minLength: 1,
  28. items: 10,
  29. updater: value => {
  30. let funcDef: any = ctrl.datasource.getFuncDef(value);
  31. if (!funcDef) {
  32. // try find close match
  33. value = value.toLowerCase();
  34. funcDef = _.find(allFunctions, funcName => {
  35. return funcName.toLowerCase().indexOf(value) === 0;
  36. });
  37. if (!funcDef) {
  38. return '';
  39. }
  40. }
  41. $scope.$apply(() => {
  42. ctrl.addFunction(funcDef);
  43. });
  44. $input.trigger('blur');
  45. return '';
  46. },
  47. });
  48. $button.click(() => {
  49. $button.hide();
  50. $input.show();
  51. $input.focus();
  52. });
  53. $input.keyup(() => {
  54. elem.toggleClass('open', $input.val() === '');
  55. });
  56. $input.blur(() => {
  57. // clicking the function dropdown menu won't
  58. // work if you remove class at once
  59. setTimeout(() => {
  60. $input.val('');
  61. $input.hide();
  62. $button.show();
  63. elem.removeClass('open');
  64. }, 200);
  65. });
  66. $compile(elem.contents())($scope);
  67. });
  68. let drop;
  69. const cleanUpDrop = () => {
  70. if (drop) {
  71. drop.destroy();
  72. drop = null;
  73. }
  74. };
  75. $(elem)
  76. .on('mouseenter', 'ul.dropdown-menu li', () => {
  77. cleanUpDrop();
  78. let funcDef;
  79. try {
  80. funcDef = ctrl.datasource.getFuncDef($('a', this).text());
  81. } catch (e) {
  82. // ignore
  83. }
  84. if (funcDef && funcDef.description) {
  85. let shortDesc = funcDef.description;
  86. if (shortDesc.length > 500) {
  87. shortDesc = shortDesc.substring(0, 497) + '...';
  88. }
  89. const contentElement = document.createElement('div');
  90. contentElement.innerHTML = '<h4>' + funcDef.name + '</h4>' + rst2html(shortDesc);
  91. drop = new Drop({
  92. target: this,
  93. content: contentElement,
  94. classes: 'drop-popover',
  95. openOn: 'always',
  96. tetherOptions: {
  97. attachment: 'bottom left',
  98. targetAttachment: 'bottom right',
  99. },
  100. });
  101. }
  102. })
  103. .on('mouseout', 'ul.dropdown-menu li', () => {
  104. cleanUpDrop();
  105. });
  106. $scope.$on('$destroy', cleanUpDrop);
  107. },
  108. };
  109. }
  110. coreModule.directive('graphiteAddFunc', graphiteAddFunc);
  111. function createFunctionDropDownMenu(funcDefs) {
  112. const categories = {};
  113. _.forEach(funcDefs, funcDef => {
  114. if (!funcDef.category) {
  115. return;
  116. }
  117. if (!categories[funcDef.category]) {
  118. categories[funcDef.category] = [];
  119. }
  120. categories[funcDef.category].push({
  121. text: funcDef.name,
  122. click: "ctrl.addFunction('" + funcDef.name + "')",
  123. });
  124. });
  125. return _.sortBy(
  126. _.map(categories, (submenu, category) => {
  127. return {
  128. text: category,
  129. submenu: _.sortBy(submenu, 'text'),
  130. };
  131. }),
  132. 'text'
  133. );
  134. }