Ver Fonte

Display graphite function name editor in a tooltip

Dominik Prokop há 6 anos atrás
pai
commit
1069d7f5b1

+ 2 - 2
packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx

@@ -1,6 +1,6 @@
 import React, { Component, createRef } from 'react';
 import React, { Component, createRef } from 'react';
-import PopperController from '../Tooltip/PopperController';
-import Popper from '../Tooltip/Popper';
+import { PopperController } from '../Tooltip/PopperController';
+import { Popper } from '../Tooltip/Popper';
 import { ColorPickerPopover } from './ColorPickerPopover';
 import { ColorPickerPopover } from './ColorPickerPopover';
 import { Themeable } from '../../types';
 import { Themeable } from '../../types';
 import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';
 import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette';

+ 8 - 3
packages/grafana-ui/src/components/Tooltip/Popper.tsx

@@ -58,7 +58,7 @@ class Popper extends PureComponent<Props> {
                   // TODO: move modifiers config to popper controller
                   // TODO: move modifiers config to popper controller
                   modifiers={{ preventOverflow: { enabled: true, boundariesElement: 'window' } }}
                   modifiers={{ preventOverflow: { enabled: true, boundariesElement: 'window' } }}
                 >
                 >
-                  {({ ref, style, placement, arrowProps }) => {
+                  {({ ref, style, placement, arrowProps, scheduleUpdate }) => {
                     return (
                     return (
                       <div
                       <div
                         onMouseEnter={onMouseEnter}
                         onMouseEnter={onMouseEnter}
@@ -73,7 +73,12 @@ class Popper extends PureComponent<Props> {
                         className={`${wrapperClassName}`}
                         className={`${wrapperClassName}`}
                       >
                       >
                         <div className={className}>
                         <div className={className}>
-                          {typeof content === 'string' ? content : React.cloneElement(content)}
+                          {typeof content === 'string' && content}
+                          {React.isValidElement(content) && React.cloneElement(content)}
+                          {typeof content === 'function' &&
+                            content({
+                              updatePopperPosition: scheduleUpdate,
+                            })}
                           {renderArrow &&
                           {renderArrow &&
                             renderArrow({
                             renderArrow({
                               arrowProps,
                               arrowProps,
@@ -93,4 +98,4 @@ class Popper extends PureComponent<Props> {
   }
   }
 }
 }
 
 
-export default Popper;
+export { Popper };

+ 2 - 2
packages/grafana-ui/src/components/Tooltip/PopperController.tsx

@@ -7,7 +7,7 @@ export interface PopperContentProps {
   updatePopperPosition?: () => void;
   updatePopperPosition?: () => void;
 }
 }
 
 
-export type PopperContent<T extends PopperContentProps> = string | React.ReactElement<T>;
+export type PopperContent<T extends PopperContentProps> = string | React.ReactElement<T> | ((props: T) => JSX.Element);
 
 
 export interface UsingPopperProps {
 export interface UsingPopperProps {
   show?: boolean;
   show?: boolean;
@@ -101,4 +101,4 @@ class PopperController extends React.Component<Props, State> {
   }
   }
 }
 }
 
 
-export default PopperController;
+export { PopperController };

+ 2 - 2
packages/grafana-ui/src/components/Tooltip/Tooltip.tsx

@@ -1,7 +1,7 @@
 import React, { createRef } from 'react';
 import React, { createRef } from 'react';
 import * as PopperJS from 'popper.js';
 import * as PopperJS from 'popper.js';
-import Popper from './Popper';
-import PopperController, { UsingPopperProps } from './PopperController';
+import { Popper } from './Popper';
+import { PopperController, UsingPopperProps } from './PopperController';
 
 
 interface TooltipProps extends UsingPopperProps {
 interface TooltipProps extends UsingPopperProps {
   theme?: 'info' | 'error';
   theme?: 'info' | 'error';

+ 2 - 0
packages/grafana-ui/src/components/index.ts

@@ -1,5 +1,7 @@
 export { DeleteButton } from './DeleteButton/DeleteButton';
 export { DeleteButton } from './DeleteButton/DeleteButton';
 export { Tooltip } from './Tooltip/Tooltip';
 export { Tooltip } from './Tooltip/Tooltip';
+export { PopperController } from './Tooltip/PopperController';
+export { Popper } from './Tooltip/Popper';
 export { Portal } from './Portal/Portal';
 export { Portal } from './Portal/Portal';
 export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
 export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
 
 

+ 2 - 0
public/app/core/angular_wrappers.ts

@@ -10,10 +10,12 @@ import { SideMenu } from './components/sidemenu/SideMenu';
 import { MetricSelect } from './components/Select/MetricSelect';
 import { MetricSelect } from './components/Select/MetricSelect';
 import AppNotificationList from './components/AppNotifications/AppNotificationList';
 import AppNotificationList from './components/AppNotifications/AppNotificationList';
 import { ColorPicker, SeriesColorPickerPopoverWithTheme } from '@grafana/ui';
 import { ColorPicker, SeriesColorPickerPopoverWithTheme } from '@grafana/ui';
+import { FunctionEditor } from 'app/plugins/datasource/graphite/FunctionEditor';
 
 
 export function registerAngularDirectives() {
 export function registerAngularDirectives() {
   react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
   react2AngularDirective('passwordStrength', PasswordStrength, ['password']);
   react2AngularDirective('sidemenu', SideMenu, []);
   react2AngularDirective('sidemenu', SideMenu, []);
+  react2AngularDirective('functionEditor', FunctionEditor, ['func', 'onRemove', 'onMoveLeft', 'onMoveRight']);
   react2AngularDirective('appNotificationsList', AppNotificationList, []);
   react2AngularDirective('appNotificationsList', AppNotificationList, []);
   react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']);
   react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']);
   react2AngularDirective('emptyListCta', EmptyListCTA, ['model']);
   react2AngularDirective('emptyListCta', EmptyListCTA, ['model']);

+ 104 - 0
public/app/plugins/datasource/graphite/FunctionEditor.tsx

@@ -0,0 +1,104 @@
+import React from 'react';
+import { PopperController, Popper } from '@grafana/ui';
+import rst2html from 'rst2html';
+import { FunctionDescriptor, FunctionEditorControlsProps, FunctionEditorControls } from './FunctionEditorControls';
+
+interface FunctionEditorProps extends FunctionEditorControlsProps {
+  func: FunctionDescriptor;
+}
+
+interface FunctionEditorState {
+  showingDescription: boolean;
+}
+
+class FunctionEditor extends React.PureComponent<FunctionEditorProps, FunctionEditorState> {
+  private triggerRef = React.createRef<HTMLSpanElement>();
+
+  constructor(props: FunctionEditorProps) {
+    super(props);
+
+    this.state = {
+      showingDescription: false,
+    };
+  }
+
+  renderContent = ({ updatePopperPosition }) => {
+    const { onMoveLeft, onMoveRight, func: { def: { name, description } } } = this.props;
+    const { showingDescription } = this.state;
+
+    if (showingDescription) {
+      return (
+        <div style={{ overflow: 'auto', maxHeight: '30rem', textAlign: 'left', fontWeight: 'normal' }}>
+          <h4 style={{ color: 'white' }}> {name} </h4>
+          <div
+            dangerouslySetInnerHTML={{
+              __html: rst2html(description),
+            }}
+          />
+        </div>
+      );
+    }
+
+    return (
+      <FunctionEditorControls
+        {...this.props}
+        onMoveLeft={() => {
+          onMoveLeft(this.props.func);
+          updatePopperPosition();
+        }}
+        onMoveRight={() => {
+          onMoveRight(this.props.func);
+          updatePopperPosition();
+        }}
+        onDescriptionShow={() => {
+          this.setState({ showingDescription: true }, () => {
+            updatePopperPosition();
+          });
+        }}
+      />
+    );
+  };
+
+  render() {
+    return (
+      <PopperController content={this.renderContent} placement="top" hideAfter={300}>
+        {(showPopper, hidePopper, popperProps) => {
+          return (
+            <>
+              {this.triggerRef && (
+                <Popper
+                  {...popperProps}
+                  referenceElement={this.triggerRef.current}
+                  wrapperClassName="popper"
+                  className="popper__background"
+                  onMouseLeave={() => {
+                    this.setState({ showingDescription: false });
+                    hidePopper();
+                  }}
+                  onMouseEnter={showPopper}
+                  renderArrow={({ arrowProps, placement }) => (
+                    <div className="popper__arrow" data-placement={placement} {...arrowProps} />
+                  )}
+                />
+              )}
+
+              <span
+                ref={this.triggerRef}
+                onClick={popperProps.show ? hidePopper : showPopper}
+                onMouseLeave={() => {
+                  hidePopper();
+                  this.setState({ showingDescription: false });
+                }}
+                style={{ cursor: 'pointer' }}
+              >
+                {this.props.func.def.name}
+              </span>
+            </>
+          );
+        }}
+      </PopperController>
+    );
+  }
+}
+
+export { FunctionEditor };

+ 65 - 0
public/app/plugins/datasource/graphite/FunctionEditorControls.tsx

@@ -0,0 +1,65 @@
+import React from 'react';
+
+export interface FunctionDescriptor {
+  text: string;
+  params: string[];
+  def: {
+    category: string;
+    defaultParams: string[];
+    description?: string;
+    fake: boolean;
+    name: string;
+    params: string[];
+  };
+}
+
+export interface FunctionEditorControlsProps {
+  onMoveLeft: (func: FunctionDescriptor) => void;
+  onMoveRight: (func: FunctionDescriptor) => void;
+  onRemove: (func: FunctionDescriptor) => void;
+}
+
+const FunctionHelpButton = (props: { description: string; name: string; onDescriptionShow: () => void }) => {
+  if (props.description) {
+    return <span className="pointer fa fa-question-circle" onClick={props.onDescriptionShow} />;
+  }
+
+  return (
+    <span
+      className="pointer fa fa-question-circle"
+      onClick={() => {
+        window.open(
+          'http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions.' + props.name,
+          '_blank'
+        );
+      }}
+    />
+  );
+};
+
+export const FunctionEditorControls = (
+  props: FunctionEditorControlsProps & {
+    func: FunctionDescriptor;
+    onDescriptionShow: () => void;
+  }
+) => {
+  const { func, onMoveLeft, onMoveRight, onRemove, onDescriptionShow } = props;
+  return (
+    <div
+      style={{
+        display: 'flex',
+        width: '60px',
+        justifyContent: 'space-between',
+      }}
+    >
+      <span className="pointer fa fa-arrow-left" onClick={() => onMoveLeft(func)} />
+      <FunctionHelpButton
+        name={func.def.name}
+        description={func.def.description}
+        onDescriptionShow={onDescriptionShow}
+      />
+      <span className="pointer fa fa-remove" onClick={() => onRemove(func)} />
+      <span className="pointer fa fa-arrow-right" onClick={() => onMoveRight(func)} />
+    </div>
+  );
+};

+ 20 - 87
public/app/plugins/datasource/graphite/func_editor.ts

@@ -1,33 +1,42 @@
 import _ from 'lodash';
 import _ from 'lodash';
 import $ from 'jquery';
 import $ from 'jquery';
-import rst2html from 'rst2html';
 import coreModule from 'app/core/core_module';
 import coreModule from 'app/core/core_module';
 
 
 /** @ngInject */
 /** @ngInject */
 export function graphiteFuncEditor($compile, templateSrv, popoverSrv) {
 export function graphiteFuncEditor($compile, templateSrv, popoverSrv) {
-  const funcSpanTemplate = '<a ng-click="">{{func.def.name}}</a><span>(</span>';
+  const funcSpanTemplate = `
+    <function-editor
+      func="func"
+      onRemove="ctrl.handleRemoveFunction"
+      onMoveLeft="ctrl.handleMoveLeft"
+      onMoveRight="ctrl.handleMoveRight"
+    /><span>(</span>
+  `;
   const paramTemplate =
   const paramTemplate =
     '<input type="text" style="display:none"' + ' class="input-small tight-form-func-param"></input>';
     '<input type="text" style="display:none"' + ' class="input-small tight-form-func-param"></input>';
 
 
-  const funcControlsTemplate = `
-    <div class="tight-form-func-controls">
-      <span class="pointer fa fa-arrow-left"></span>
-      <span class="pointer fa fa-question-circle"></span>
-      <span class="pointer fa fa-remove" ></span>
-      <span class="pointer fa fa-arrow-right"></span>
-    </div>`;
-
   return {
   return {
     restrict: 'A',
     restrict: 'A',
     link: function postLink($scope, elem) {
     link: function postLink($scope, elem) {
       const $funcLink = $(funcSpanTemplate);
       const $funcLink = $(funcSpanTemplate);
-      const $funcControls = $(funcControlsTemplate);
       const ctrl = $scope.ctrl;
       const ctrl = $scope.ctrl;
       const func = $scope.func;
       const func = $scope.func;
       let scheduledRelink = false;
       let scheduledRelink = false;
       let paramCountAtLink = 0;
       let paramCountAtLink = 0;
       let cancelBlur = null;
       let cancelBlur = null;
 
 
+      ctrl.handleRemoveFunction = func => {
+        ctrl.removeFunction(func);
+      };
+
+      ctrl.handleMoveLeft = func => {
+        ctrl.moveFunction(func, -1);
+      };
+
+      ctrl.handleMoveRight = func => {
+        ctrl.moveFunction(func, 1);
+      };
+
       function clickFuncParam(this: any, paramIndex) {
       function clickFuncParam(this: any, paramIndex) {
         /*jshint validthis:true */
         /*jshint validthis:true */
 
 
@@ -158,24 +167,7 @@ export function graphiteFuncEditor($compile, templateSrv, popoverSrv) {
         };
         };
       }
       }
 
 
-      function toggleFuncControls() {
-        const targetDiv = elem.closest('.tight-form');
-
-        if (elem.hasClass('show-function-controls')) {
-          elem.removeClass('show-function-controls');
-          targetDiv.removeClass('has-open-function');
-          $funcControls.hide();
-          return;
-        }
-
-        elem.addClass('show-function-controls');
-        targetDiv.addClass('has-open-function');
-
-        $funcControls.show();
-      }
-
       function addElementsAndCompile() {
       function addElementsAndCompile() {
-        $funcControls.appendTo(elem);
         $funcLink.appendTo(elem);
         $funcLink.appendTo(elem);
 
 
         const defParams = _.clone(func.def.params);
         const defParams = _.clone(func.def.params);
@@ -245,69 +237,10 @@ export function graphiteFuncEditor($compile, templateSrv, popoverSrv) {
         }
         }
       }
       }
 
 
-      function registerFuncControlsToggle() {
-        $funcLink.click(toggleFuncControls);
-      }
-
-      function registerFuncControlsActions() {
-        $funcControls.click(e => {
-          const $target = $(e.target);
-          if ($target.hasClass('fa-remove')) {
-            toggleFuncControls();
-            $scope.$apply(() => {
-              ctrl.removeFunction($scope.func);
-            });
-            return;
-          }
-
-          if ($target.hasClass('fa-arrow-left')) {
-            $scope.$apply(() => {
-              _.move(ctrl.queryModel.functions, $scope.$index, $scope.$index - 1);
-              ctrl.targetChanged();
-            });
-            return;
-          }
-
-          if ($target.hasClass('fa-arrow-right')) {
-            $scope.$apply(() => {
-              _.move(ctrl.queryModel.functions, $scope.$index, $scope.$index + 1);
-              ctrl.targetChanged();
-            });
-            return;
-          }
-
-          if ($target.hasClass('fa-question-circle')) {
-            const funcDef = ctrl.datasource.getFuncDef(func.def.name);
-            if (funcDef && funcDef.description) {
-              popoverSrv.show({
-                element: e.target,
-                position: 'bottom left',
-                classNames: 'drop-popover drop-function-def',
-                template: `
-                  <div style="overflow:auto;max-height:30rem;">
-                    <h4> ${funcDef.name} </h4>
-                    ${rst2html(funcDef.description)}
-                  </div>`,
-                openOn: 'click',
-              });
-            } else {
-              window.open(
-                'http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions.' + func.def.name,
-                '_blank'
-              );
-            }
-            return;
-          }
-        });
-      }
-
       function relink() {
       function relink() {
         elem.children().remove();
         elem.children().remove();
-
         addElementsAndCompile();
         addElementsAndCompile();
         ifJustAddedFocusFirstParam();
         ifJustAddedFocusFirstParam();
-        registerFuncControlsToggle();
-        registerFuncControlsActions();
       }
       }
 
 
       relink();
       relink();

+ 5 - 0
public/app/plugins/datasource/graphite/graphite_query.ts

@@ -154,6 +154,11 @@ export default class GraphiteQuery {
     this.functions = _.without(this.functions, func);
     this.functions = _.without(this.functions, func);
   }
   }
 
 
+  moveFunction(func, offset) {
+    const index = this.functions.indexOf(func);
+    _.move(this.functions, index, index + offset);
+  }
+
   updateModelTarget(targets) {
   updateModelTarget(targets) {
     // render query
     // render query
     if (!this.target.textEditor) {
     if (!this.target.textEditor) {

+ 5 - 0
public/app/plugins/datasource/graphite/query_ctrl.ts

@@ -272,6 +272,11 @@ export class GraphiteQueryCtrl extends QueryCtrl {
     this.targetChanged();
     this.targetChanged();
   }
   }
 
 
+  moveFunction(func, offset) {
+    this.queryModel.moveFunction(func, offset);
+    this.targetChanged();
+  }
+
   addSeriesByTagFunc(tag) {
   addSeriesByTagFunc(tag) {
     const newFunc = this.datasource.createFuncInstance('seriesByTag', {
     const newFunc = this.datasource.createFuncInstance('seriesByTag', {
       withDefaultParams: false,
       withDefaultParams: false,

+ 0 - 1
public/sass/components/_query_editor.scss

@@ -50,7 +50,6 @@ input[type='text'].tight-form-func-param {
 }
 }
 
 
 .tight-form-func-controls {
 .tight-form-func-controls {
-  display: none;
   text-align: center;
   text-align: center;
 
 
   .fa-arrow-left {
   .fa-arrow-left {