add_graphite_func.ts 4.3 KB

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