Ver código fonte

Merge branch 'master' into grafana-ui/tooltip

Dominik Prokop 7 anos atrás
pai
commit
1e7f3f2892

+ 6 - 0
docs/sources/installation/debian.md

@@ -47,6 +47,12 @@ Create a file `/etc/apt/sources.list.d/grafana.list` and add the following to it
 deb https://packages.grafana.com/oss/deb stable main
 ```
 
+There is a separate repository if you want beta releases.
+
+```bash
+deb https://packages.grafana.com/oss/deb beta main
+```
+
 Use the above line even if you are on Ubuntu or another Debian version. Then add our gpg key. This allows you to install signed packages.
 
 ```bash

+ 14 - 0
docs/sources/installation/rpm.md

@@ -76,6 +76,20 @@ sslverify=1
 sslcacert=/etc/pki/tls/certs/ca-bundle.crt
 ```
 
+There is a separate repository if you want beta releases.
+
+```bash
+[grafana]
+name=grafana
+baseurl=https://packages.grafana.com/oss/rpm-beta
+repo_gpgcheck=1
+enabled=1
+gpgcheck=1
+gpgkey=https://packages.grafana.com/gpg.key
+sslverify=1
+sslcacert=/etc/pki/tls/certs/ca-bundle.crt
+```
+
 Then install Grafana via the `yum` command.
 
 ```bash

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

@@ -4,6 +4,11 @@ import { Manager, Popper as ReactPopper } from 'react-popper';
 import Portal from 'app/core/components/Portal/Portal';
 import Transition from 'react-transition-group/Transition';
 
+export enum Themes {
+  Default = 'popper__background--default',
+  Error = 'popper__background--error',
+}
+
 const defaultTransitionStyles = {
   transition: 'opacity 200ms linear',
   opacity: 0,
@@ -23,13 +28,16 @@ interface Props extends React.DOMAttributes<HTMLDivElement> {
   content: string | ((props: any) => JSX.Element);
   refClassName?: string;
   referenceElement: PopperJS.ReferenceObject;
+  theme?: Themes;
 }
 
 class Popper extends PureComponent<Props> {
   render() {
-    const { renderContent, show, placement, onMouseEnter, onMouseLeave } = this.props;
+    const { renderContent, show, placement, onMouseEnter, onMouseLeave, theme } = this.props;
     const { content } = this.props;
 
+    const popperBackgroundClassName = 'popper__background' + (theme ? ' ' + theme : '');
+
     return (
       <Manager>
         <Transition in={show} timeout={100} mountOnEnter={true} unmountOnExit={true}>
@@ -50,7 +58,7 @@ class Popper extends PureComponent<Props> {
                       data-placement={placement}
                       className="popper"
                     >
-                      <div className="popper__background">
+                      <div className={popperBackgroundClassName}>
                         {renderContent(content)}
                         <div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" />
                       </div>

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

@@ -1,5 +1,6 @@
 import React from 'react';
 import * as PopperJS from 'popper.js';
+import { Themes } from './Popper';
 
 type PopperContent = string | (() => JSX.Element);
 
@@ -9,6 +10,7 @@ export interface UsingPopperProps {
   content: PopperContent;
   children: JSX.Element;
   renderContent?: (content: PopperContent) => JSX.Element;
+  theme?: Themes;
 }
 
 type PopperControllerRenderProp = (
@@ -19,6 +21,7 @@ type PopperControllerRenderProp = (
     placement: PopperJS.Placement;
     content: string | ((props: any) => JSX.Element);
     renderContent: (content: any) => any;
+    theme?: Themes;
   }
 ) => JSX.Element;
 
@@ -27,6 +30,7 @@ interface Props {
   content: PopperContent;
   className?: string;
   children: PopperControllerRenderProp;
+  theme?: Themes;
 }
 
 interface State {
@@ -79,7 +83,7 @@ class PopperController extends React.Component<Props, State> {
   }
 
   render() {
-    const { children, content } = this.props;
+    const { children, content, theme } = this.props;
     const { show, placement } = this.state;
 
     return children(this.showPopper, this.hidePopper, {
@@ -87,6 +91,7 @@ class PopperController extends React.Component<Props, State> {
       placement,
       content,
       renderContent: this.renderContent,
+      theme,
     });
   }
 }

+ 17 - 9
packages/grafana-ui/src/components/Tooltip/_Tooltip.scss

@@ -8,7 +8,22 @@ $popper-margin-from-ref: 5px;
   text-align: center;
 }
 
-.popper .popper__arrow {
+.popper__background {
+  background: $tooltipBackground;
+  border-radius: $border-radius;
+  box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
+  padding: 10px;
+
+  // Themes
+  &.popper__background--error {
+    background: $tooltipBackgroundError;
+    .popper__arrow {
+      border-color: $tooltipBackgroundError;
+    }
+  }
+}
+
+.popper__arrow {
   width: 0;
   height: 0;
   border-style: solid;
@@ -16,17 +31,10 @@ $popper-margin-from-ref: 5px;
   margin: 0px;
 }
 
-.popper .popper__arrow {
+.popper__arrow {
   border-color: $tooltipBackground;
 }
 
-.popper__background {
-  background: $tooltipBackground;
-  border-radius: $border-radius;
-  box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
-  padding: 10px;
-}
-
 // Top
 .popper[data-placement^='top'] {
   padding-bottom: $popper-margin-from-ref;

+ 1 - 0
packages/grafana-ui/src/visualizations/Graph/Graph.tsx

@@ -98,6 +98,7 @@ export class Graph extends PureComponent<GraphProps> {
       $.plot(this.element, timeSeries, flotOptions);
     } catch (err) {
       console.log('Graph rendering error', err, flotOptions, timeSeries);
+      throw new Error('Error rendering panel');
     }
   }
 

+ 44 - 0
public/app/core/components/ErrorBoundary/ErrorBoundary.tsx

@@ -0,0 +1,44 @@
+import { Component } from 'react';
+
+interface ErrorInfo {
+  componentStack: string;
+}
+
+interface RenderProps {
+  error: Error;
+  errorInfo: ErrorInfo;
+}
+
+interface Props {
+  children: (r: RenderProps) => JSX.Element;
+}
+
+interface State {
+  error: Error;
+  errorInfo: ErrorInfo;
+}
+
+class ErrorBoundary extends Component<Props, State> {
+  readonly state: State = {
+    error: null,
+    errorInfo: null,
+  };
+
+  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+    this.setState({
+      error: error,
+      errorInfo: errorInfo
+    });
+  }
+
+  render() {
+    const { children } = this.props;
+    const { error, errorInfo } = this.state;
+    return children({
+      error,
+      errorInfo,
+    });
+  }
+}
+
+export default ErrorBoundary;

+ 3 - 3
public/app/features/dashboard/dashgrid/AlertTab.tsx → public/app/features/alerting/AlertTab.tsx

@@ -6,14 +6,14 @@ import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoa
 import appEvents from 'app/core/app_events';
 
 // Components
-import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
+import { EditorTabBody, EditorToolbarView } from '../dashboard/dashgrid/EditorTabBody';
 import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
 import StateHistory from './StateHistory';
 import 'app/features/alerting/AlertTabCtrl';
 
 // Types
-import { DashboardModel } from '../dashboard_model';
-import { PanelModel } from '../panel_model';
+import { DashboardModel } from '../dashboard/dashboard_model';
+import { PanelModel } from '../dashboard/panel_model';
 
 interface Props {
   angularPanel?: AngularComponent;

+ 3 - 3
public/app/features/dashboard/dashgrid/StateHistory.tsx → public/app/features/alerting/StateHistory.tsx

@@ -1,8 +1,8 @@
 import React, { PureComponent } from 'react';
-import alertDef from '../../alerting/state/alertDef';
+import alertDef from './state/alertDef';
 import { getBackendSrv } from 'app/core/services/backend_srv';
-import { DashboardModel } from '../dashboard_model';
-import appEvents from '../../../core/app_events';
+import { DashboardModel } from '../dashboard/dashboard_model';
+import appEvents from '../../core/app_events';
 
 interface Props {
   dashboard: DashboardModel;

+ 52 - 11
public/app/features/dashboard/dashgrid/DataPanel.tsx

@@ -1,5 +1,7 @@
 // Library
 import React, { Component } from 'react';
+import Tooltip from 'app/core/components/Tooltip/Tooltip';
+import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary';
 
 // Services
 import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource_srv';
@@ -10,6 +12,9 @@ import kbn from 'app/core/utils/kbn';
 // Types
 import { DataQueryOptions, DataQueryResponse } from 'app/types';
 import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui';
+import { Themes } from 'app/core/components/Tooltip/Popper';
+
+const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
 
 interface RenderProps {
   loading: LoadingState;
@@ -33,6 +38,7 @@ export interface Props {
 export interface State {
   isFirstLoad: boolean;
   loading: LoadingState;
+  errorMessage: string;
   response: DataQueryResponse;
 }
 
@@ -51,6 +57,7 @@ export class DataPanel extends Component<Props, State> {
 
     this.state = {
       loading: LoadingState.NotStarted,
+      errorMessage: '',
       response: {
         data: [],
       },
@@ -90,7 +97,7 @@ export class DataPanel extends Component<Props, State> {
       return;
     }
 
-    this.setState({ loading: LoadingState.Loading });
+    this.setState({ loading: LoadingState.Loading, errorMessage: '' });
 
     try {
       const ds = await this.dataSourceSrv.get(datasource);
@@ -128,10 +135,20 @@ export class DataPanel extends Component<Props, State> {
       });
     } catch (err) {
       console.log('Loading error', err);
-      this.setState({ loading: LoadingState.Error, isFirstLoad: false });
+      this.onError('Request Error');
     }
   };
 
+  onError = (errorMessage: string) => {
+    if (this.state.loading !== LoadingState.Error || this.state.errorMessage !== errorMessage) {
+      this.setState({
+        loading: LoadingState.Error,
+        isFirstLoad: false,
+        errorMessage: errorMessage
+      });
+    }
+  }
+
   render() {
     const { queries } = this.props;
     const { response, loading, isFirstLoad } = this.state;
@@ -139,7 +156,7 @@ export class DataPanel extends Component<Props, State> {
     const timeSeries = response.data;
 
     if (isFirstLoad && loading === LoadingState.Loading) {
-      return this.renderLoadingSpinner();
+      return this.renderLoadingStates();
     }
 
     if (!queries.length) {
@@ -152,24 +169,48 @@ export class DataPanel extends Component<Props, State> {
 
     return (
       <>
-        {this.renderLoadingSpinner()}
-        {this.props.children({
-          timeSeries,
-          loading,
-        })}
+        {this.renderLoadingStates()}
+        <ErrorBoundary>
+          {({error, errorInfo}) => {
+            if (errorInfo) {
+              this.onError(error.message || DEFAULT_PLUGIN_ERROR);
+              return null;
+            }
+            return (
+              <>
+                {this.props.children({
+                  timeSeries,
+                  loading,
+                })}
+              </>
+            );
+          }}
+        </ErrorBoundary>
       </>
     );
   }
 
-  private renderLoadingSpinner(): JSX.Element {
-    const { loading } = this.state;
-
+  private renderLoadingStates(): JSX.Element {
+    const { loading, errorMessage } = this.state;
     if (loading === LoadingState.Loading) {
       return (
         <div className="panel-loading">
           <i className="fa fa-spinner fa-spin" />
         </div>
       );
+    } else if (loading === LoadingState.Error) {
+      return (
+        <Tooltip
+          content={errorMessage}
+          className="popper__manager--block"
+          refClassName={`panel-info-corner panel-info-corner--error`}
+          placement="bottom-start"
+          theme={Themes.Error}
+        >
+          <i className="fa" />
+          <span className="panel-info-corner-inner" />
+        </Tooltip>
+      );
     }
 
     return null;

+ 0 - 1
public/app/features/dashboard/dashgrid/PanelChrome.tsx

@@ -87,7 +87,6 @@ export class PanelChrome extends PureComponent<Props, State> {
     const { datasource, targets, transparent } = panel;
     const PanelComponent = plugin.exports.Panel;
     const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`;
-
     return (
       <AutoSizer>
         {({ width, height }) => {

+ 1 - 1
public/app/features/dashboard/dashgrid/PanelEditor.tsx

@@ -4,7 +4,7 @@ import classNames from 'classnames';
 import { QueriesTab } from './QueriesTab';
 import { VisualizationTab } from './VisualizationTab';
 import { GeneralTab } from './GeneralTab';
-import { AlertTab } from './AlertTab';
+import { AlertTab } from '../../alerting/AlertTab';
 
 import config from 'app/core/config';
 import { store } from 'app/store/store';

+ 6 - 2
public/app/features/dashboard/dashgrid/QueriesTab.tsx

@@ -50,17 +50,21 @@ export class QueriesTab extends PureComponent<Props, State> {
 
   constructor(props) {
     super(props);
-    const { panel } = props;
 
     this.state = {
-      currentDS: this.datasources.find(datasource => datasource.value === panel.datasource),
       isLoadingHelp: false,
+      currentDS: this.findCurrentDataSource(),
       helpContent: null,
       isPickerOpen: false,
       isAddingMixed: false,
     };
   }
 
+  findCurrentDataSource(): DataSourceSelectItem {
+    const { panel } = this.props;
+    return this.datasources.find(datasource => datasource.value === panel.datasource) || this.datasources[0];
+  }
+
   getAngularQueryComponentScope(): AngularQueryComponentScope {
     const { panel, dashboard } = this.props;
 

+ 0 - 4
public/app/plugins/panel/graph2/GraphPanel.tsx

@@ -10,10 +10,6 @@ import { Options } from './types';
 interface Props extends PanelProps<Options> {}
 
 export class GraphPanel extends PureComponent<Props> {
-  constructor(props) {
-    super(props);
-  }
-
   render() {
     const { timeSeries, timeRange, width, height } = this.props;
     const { showLines, showBars, showPoints } = this.props.options;

+ 5 - 2
public/sass/_variables.dark.scss

@@ -103,6 +103,7 @@ $panel-bg: #212124;
 $panel-border-color: $dark-1;
 $panel-border: solid 1px $panel-border-color;
 $panel-header-hover-bg: $dark-4;
+$panel-corner: $panel-bg;
 
 // page header
 $page-header-bg: linear-gradient(90deg, #292a2d, black);
@@ -302,12 +303,14 @@ $popover-error-bg: $btn-danger-bg;
 // Tooltips and popovers
 // -------------------------
 $tooltipColor: $popover-help-color;
-$tooltipBackground: $popover-help-bg;
 $tooltipArrowWidth: 5px;
-$tooltipArrowColor: $tooltipBackground;
 $tooltipLinkColor: $link-color;
 $graph-tooltip-bg: $dark-1;
 
+$tooltipBackground: $popover-help-bg;
+$tooltipArrowColor: $tooltipBackground;
+$tooltipBackgroundError: $brand-danger;
+
 // images
 $checkboxImageUrl: '../img/checkbox.png';
 

+ 5 - 2
public/sass/_variables.light.scss

@@ -102,6 +102,7 @@ $panel-bg: $white;
 $panel-border-color: $gray-5;
 $panel-border: solid 1px $panel-border-color;
 $panel-header-hover-bg: $gray-6;
+$panel-corner: $gray-4;
 
 // Page header
 $page-header-bg: linear-gradient(90deg, $white, $gray-7);
@@ -307,12 +308,14 @@ $popover-error-bg: $btn-danger-bg;
 // Tooltips and popovers
 // -------------------------
 $tooltipColor: $popover-help-color;
-$tooltipBackground: $popover-help-bg;
 $tooltipArrowWidth: 5px;
-$tooltipArrowColor: $tooltipBackground;
 $tooltipLinkColor: lighten($popover-help-color, 5%);
 $graph-tooltip-bg: $gray-5;
 
+$tooltipBackground: $popover-help-bg;
+$tooltipArrowColor: $tooltipBackground; // Used by Angular tooltip
+$tooltipBackgroundError: $brand-danger;
+
 // images
 $checkboxImageUrl: '../img/checkbox_white.png';
 

+ 3 - 3
public/sass/pages/_dashboard.scss

@@ -214,7 +214,7 @@ div.flot-text {
 
   &--info {
     display: block;
-    @include panel-corner-color(lighten($panel-bg, 4%));
+    @include panel-corner-color(lighten($panel-corner, 4%));
     .fa:before {
       content: '\f129';
     }
@@ -222,7 +222,7 @@ div.flot-text {
 
   &--links {
     display: block;
-    @include panel-corner-color(lighten($panel-bg, 4%));
+    @include panel-corner-color(lighten($panel-corner, 4%));
     .fa {
       left: 4px;
     }
@@ -233,7 +233,7 @@ div.flot-text {
 
   &--error {
     display: block;
-    color: $text-color;
+    color: $white;
     @include panel-corner-color($popover-error-bg);
     .fa:before {
       content: '\f12a';