Parcourir la source

Merge remote-tracking branch 'origin/master' into reactify-stackdriver

# Conflicts:
#	yarn.lock
Erik Sundell il y a 7 ans
Parent
commit
92121cacd1
64 fichiers modifiés avec 670 ajouts et 346 suppressions
  1. 24 4
      .circleci/config.yml
  2. 1 0
      CHANGELOG.md
  3. 6 0
      docs/sources/installation/debian.md
  4. 14 0
      docs/sources/installation/rpm.md
  5. 1 3
      package.json
  6. 8 2
      packages/grafana-ui/package.json
  7. 0 0
      packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.test.tsx
  8. 1 1
      packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx
  9. 40 0
      packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss
  10. 0 0
      packages/grafana-ui/src/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap
  11. 2 2
      packages/grafana-ui/src/components/Portal/Portal.tsx
  12. 20 16
      packages/grafana-ui/src/components/Tooltip/Popper.tsx
  13. 99 0
      packages/grafana-ui/src/components/Tooltip/PopperController.tsx
  14. 5 3
      packages/grafana-ui/src/components/Tooltip/Tooltip.test.tsx
  15. 32 0
      packages/grafana-ui/src/components/Tooltip/Tooltip.tsx
  16. 17 9
      packages/grafana-ui/src/components/Tooltip/_Tooltip.scss
  17. 12 0
      packages/grafana-ui/src/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap
  18. 2 0
      packages/grafana-ui/src/components/index.scss
  19. 3 0
      packages/grafana-ui/src/components/index.ts
  20. 1 0
      packages/grafana-ui/src/visualizations/Graph/Graph.tsx
  21. 1 2
      pkg/tsdb/cloudwatch/credentials.go
  22. 44 0
      public/app/core/components/ErrorBoundary/ErrorBoundary.tsx
  23. 5 3
      public/app/core/components/Label/Label.tsx
  24. 1 1
      public/app/core/components/Select/Select.tsx
  25. 1 1
      public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx
  26. 0 16
      public/app/core/components/Tooltip/Popover.test.tsx
  27. 0 19
      public/app/core/components/Tooltip/Popover.tsx
  28. 0 17
      public/app/core/components/Tooltip/Tooltip.tsx
  29. 0 16
      public/app/core/components/Tooltip/__snapshots__/Popover.test.tsx.snap
  30. 0 19
      public/app/core/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap
  31. 0 88
      public/app/core/components/Tooltip/withPopper.tsx
  32. 3 3
      public/app/features/alerting/AlertTab.tsx
  33. 3 3
      public/app/features/alerting/StateHistory.tsx
  34. 49 11
      public/app/features/dashboard/dashgrid/DataPanel.tsx
  35. 1 1
      public/app/features/dashboard/dashgrid/DataSourceOption.tsx
  36. 1 1
      public/app/features/dashboard/dashgrid/EditorTabBody.tsx
  37. 0 1
      public/app/features/dashboard/dashgrid/PanelChrome.tsx
  38. 3 3
      public/app/features/dashboard/dashgrid/PanelEditor.tsx
  39. 8 6
      public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx
  40. 6 2
      public/app/features/dashboard/dashgrid/QueriesTab.tsx
  41. 5 3
      public/app/features/dashboard/permissions/DashboardPermissions.tsx
  42. 1 1
      public/app/features/explore/Explore.tsx
  43. 5 3
      public/app/features/folders/FolderPermissions.tsx
  44. 5 3
      public/app/features/teams/TeamGroupSync.tsx
  45. 1 0
      public/app/features/teams/TeamSettings.tsx
  46. 18 12
      public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap
  47. 5 5
      public/app/plugins/datasource/stackdriver/constants.ts
  48. 1 1
      public/app/plugins/panel/graph/Legend/Legend.tsx
  49. 0 4
      public/app/plugins/panel/graph2/GraphPanel.tsx
  50. 0 1
      public/sass/_grafana.scss
  51. 5 2
      public/sass/_variables.dark.scss
  52. 6 4
      public/sass/_variables.light.scss
  53. 0 44
      public/sass/components/_scrollbar.scss
  54. 3 3
      public/sass/pages/_dashboard.scss
  55. 1 1
      public/sass/pages/_explore.scss
  56. 23 3
      scripts/build/ci-deploy/Dockerfile
  57. 1 1
      scripts/build/ci-deploy/build-deploy.sh
  58. 7 0
      scripts/build/load-signing-key.sh
  59. 27 0
      scripts/build/update_repo/aptly.conf
  60. 7 0
      scripts/build/update_repo/sign-rpm-repo.sh
  61. 7 0
      scripts/build/update_repo/unlock-gpg-key.sh
  62. 58 0
      scripts/build/update_repo/update-deb.sh
  63. 59 0
      scripts/build/update_repo/update-rpm.sh
  64. 11 2
      yarn.lock

+ 24 - 4
.circleci/config.yml

@@ -323,7 +323,7 @@ jobs:
 
   deploy-enterprise-master:
     docker:
-      - image: grafana/grafana-ci-deploy:1.0.0
+      - image: grafana/grafana-ci-deploy:1.1.0
     steps:
       - attach_workspace:
           at: .
@@ -346,7 +346,7 @@ jobs:
 
   deploy-enterprise-release:
     docker:
-    - image: grafana/grafana-ci-deploy:1.0.0
+    - image: grafana/grafana-ci-deploy:1.1.0
     steps:
       - attach_workspace:
          at: .
@@ -365,10 +365,20 @@ jobs:
       - run:
           name: Deploy to Grafana.com
           command: './scripts/build/publish.sh --enterprise'
+      - run:
+          name: Load GPG private key
+          comand: './scripts/build/load-signing-key.sh'
+      - run:
+          name: Update Debian repository
+          command: './scripts/build/update_repo/update-deb.sh "enterprise" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
+      - run:
+          name: Update RPM repository
+          command: './scripts/build/update_repo/update-rpm.sh "enterprise" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
+
 
   deploy-master:
     docker:
-      - image: grafana/grafana-ci-deploy:1.0.0
+      - image: grafana/grafana-ci-deploy:1.1.0
     steps:
       - attach_workspace:
           at: .
@@ -398,8 +408,9 @@ jobs:
 
   deploy-release:
     docker:
-      - image: grafana/grafana-ci-deploy:1.0.0
+      - image: grafana/grafana-ci-deploy:1.1.0
     steps:
+      - checkout
       - attach_workspace:
           at: .
       - run:
@@ -417,6 +428,15 @@ jobs:
       - run:
           name: Deploy to Grafana.com
           command: './scripts/build/publish.sh'
+      - run:
+          name: Load GPG private key
+          comand: './scripts/build/load-signing-key.sh'
+      - run:
+          name: Update Debian repository
+          command: './scripts/build/update_repo/update-deb.sh "oss" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
+      - run:
+          name: Update RPM repository
+          command: './scripts/build/update_repo/update-rpm.sh "oss" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG"'
 
 workflows:
   version: 2

+ 1 - 0
CHANGELOG.md

@@ -17,6 +17,7 @@
 * **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard)
 * **OAuth**: Support OAuth providers that are not RFC6749 compliant [#14562](https://github.com/grafana/grafana/issues/14562), thx [@tdabasinskas](https://github.com/tdabasinskas)
 * **Units**: Add blood glucose level units mg/dL and mmol/L [#14519](https://github.com/grafana/grafana/issues/14519), thx [@kjedamzik](https://github.com/kjedamzik)
+* **Stackdriver**: Aggregating series returns more than one series [#14581](https://github.com/grafana/grafana/issues/14581) and [#13914](https://github.com/grafana/grafana/issues/13914), thx [@kinok](https://github.com/kinok)
 
 ### Bug fixes
 * **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486)

+ 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

+ 1 - 3
package.json

@@ -24,7 +24,6 @@
     "@types/jquery": "^1.10.35",
     "@types/node": "^8.0.31",
     "@types/react": "^16.7.6",
-    "@types/react-custom-scrollbars": "^4.0.5",
     "@types/react-dom": "^16.0.9",
     "@types/react-select": "^2.0.4",
     "angular-mocks": "1.6.6",
@@ -72,8 +71,8 @@
     "ng-annotate-loader": "^0.6.1",
     "ng-annotate-webpack-plugin": "^0.3.0",
     "ngtemplate-loader": "^2.0.1",
-    "npm": "^5.4.2",
     "node-sass": "^4.11.0",
+    "npm": "^5.4.2",
     "optimize-css-assets-webpack-plugin": "^4.0.2",
     "phantomjs-prebuilt": "^2.1.15",
     "postcss-browser-reporter": "^0.5.0",
@@ -167,7 +166,6 @@
     "prop-types": "^15.6.2",
     "rc-cascader": "^0.14.0",
     "react": "^16.6.3",
-    "react-custom-scrollbars": "^4.2.1",
     "react-dom": "^16.6.3",
     "react-grid-layout": "0.16.6",
     "react-highlight-words": "0.11.0",

+ 8 - 2
packages/grafana-ui/package.json

@@ -11,11 +11,14 @@
   "license": "ISC",
   "dependencies": {
     "@torkelo/react-select": "2.1.1",
+    "@types/react-test-renderer": "^16.0.3",
+    "@types/react-transition-group": "^2.0.15",
     "classnames": "^2.2.5",
     "jquery": "^3.2.1",
     "lodash": "^4.17.10",
     "moment": "^2.22.2",
     "react": "^16.6.3",
+    "react-custom-scrollbars": "^4.2.1",
     "react-dom": "^16.6.3",
     "react-highlight-words": "0.11.0",
     "react-popper": "^1.3.0",
@@ -23,11 +26,14 @@
     "react-virtualized": "^9.21.0"
   },
   "devDependencies": {
+    "@types/classnames": "^2.2.6",
     "@types/jest": "^23.3.2",
+    "@types/jquery": "^1.10.35",
     "@types/lodash": "^4.14.119",
     "@types/react": "^16.7.6",
-    "@types/classnames": "^2.2.6",
-    "@types/jquery": "^1.10.35",
+    "@types/react-custom-scrollbars": "^4.0.5",
+    "@types/react-test-renderer": "^16.0.3",
+    "react-test-renderer": "^16.7.0",
     "typescript": "^3.2.2"
   }
 }

+ 0 - 0
public/app/core/components/CustomScrollbar/CustomScrollbar.test.tsx → packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.test.tsx


+ 1 - 1
public/app/core/components/CustomScrollbar/CustomScrollbar.tsx → packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx

@@ -12,7 +12,7 @@ interface Props {
 /**
  * Wraps component into <Scrollbars> component from `react-custom-scrollbars`
  */
-class CustomScrollbar extends PureComponent<Props> {
+export class CustomScrollbar extends PureComponent<Props> {
   static defaultProps: Partial<Props> = {
     customClassName: 'custom-scrollbars',
     autoHide: true,

+ 40 - 0
packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss

@@ -0,0 +1,40 @@
+.custom-scrollbars {
+  // Fix for Firefox. For some reason sometimes .view container gets a height of its content, but in order to
+  // make scroll working it should fit outer container size (scroll appears only when inner container size is
+  // greater than outer one).
+  display: flex;
+  flex-grow: 1;
+
+  .view {
+    display: flex;
+    flex-grow: 1;
+    flex-direction: column;
+  }
+
+  .track-vertical {
+    border-radius: 3px;
+    width: 6px !important;
+    right: 2px;
+    bottom: 2px;
+    top: 2px;
+  }
+
+  .track-horizontal {
+    border-radius: 3px;
+    height: 6px !important;
+
+    right: 2px;
+    bottom: 2px;
+    left: 2px;
+  }
+
+  .thumb-vertical {
+    @include gradient-vertical($scrollbarBackground, $scrollbarBackground2);
+    border-radius: 6px;
+  }
+
+  .thumb-horizontal {
+    @include gradient-horizontal($scrollbarBackground, $scrollbarBackground2);
+    border-radius: 6px;
+  }
+}

+ 0 - 0
public/app/core/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap → packages/grafana-ui/src/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap


+ 2 - 2
public/app/core/components/Portal/Portal.tsx → packages/grafana-ui/src/components/Portal/Portal.tsx

@@ -6,11 +6,11 @@ interface Props {
   root?: HTMLElement;
 }
 
-export default class BodyPortal extends PureComponent<Props> {
+export class Portal extends PureComponent<Props> {
   node: HTMLElement = document.createElement('div');
   portalRoot: HTMLElement;
 
-  constructor(props) {
+  constructor(props: Props) {
     super(props);
     const { className, root = document.body } = this.props;
 

+ 20 - 16
public/app/core/components/Tooltip/Popper.tsx → packages/grafana-ui/src/components/Tooltip/Popper.tsx

@@ -1,49 +1,53 @@
 import React, { PureComponent } from 'react';
-import Portal from 'app/core/components/Portal/Portal';
-import { Manager, Popper as ReactPopper, Reference } from 'react-popper';
+import * as PopperJS from 'popper.js';
+import { Manager, Popper as ReactPopper } from 'react-popper';
+import { Portal } from '@grafana/ui';
 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,
 };
 
-const transitionStyles = {
+const transitionStyles: {[key: string]: object} = {
   exited: { opacity: 0 },
   entering: { opacity: 0 },
   entered: { opacity: 1 },
   exiting: { opacity: 0 },
 };
 
-interface Props {
+interface Props extends React.DOMAttributes<HTMLDivElement> {
   renderContent: (content: any) => any;
   show: boolean;
-  placement?: any;
+  placement?: PopperJS.Placement;
   content: string | ((props: any) => JSX.Element);
-  refClassName?: string;
+  referenceElement: PopperJS.ReferenceObject;
+  theme?: Themes;
 }
 
 class Popper extends PureComponent<Props> {
   render() {
-    const { children, renderContent, show, placement, refClassName } = this.props;
+    const { renderContent, show, placement, onMouseEnter, onMouseLeave, theme } = this.props;
     const { content } = this.props;
 
+    const popperBackgroundClassName = 'popper__background' + (theme ? ' ' + theme : '');
+
     return (
       <Manager>
-        <Reference>
-          {({ ref }) => (
-            <div className={`popper_ref ${refClassName || ''}`} ref={ref}>
-              {children}
-            </div>
-          )}
-        </Reference>
         <Transition in={show} timeout={100} mountOnEnter={true} unmountOnExit={true}>
           {transitionState => (
             <Portal>
-              <ReactPopper placement={placement}>
+              <ReactPopper placement={placement} referenceElement={this.props.referenceElement}>
                 {({ ref, style, placement, arrowProps }) => {
                   return (
                     <div
+                      onMouseEnter={onMouseEnter}
+                      onMouseLeave={onMouseLeave}
                       ref={ref}
                       style={{
                         ...style,
@@ -53,7 +57,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>

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

@@ -0,0 +1,99 @@
+import React from 'react';
+import * as PopperJS from 'popper.js';
+import { Themes } from './Popper';
+
+type PopperContent = string | (() => JSX.Element);
+
+export interface UsingPopperProps {
+  show?: boolean;
+  placement?: PopperJS.Placement;
+  content: PopperContent;
+  children: JSX.Element;
+  renderContent?: (content: PopperContent) => JSX.Element;
+  theme?: Themes;
+}
+
+type PopperControllerRenderProp = (
+  showPopper: () => void,
+  hidePopper: () => void,
+  popperProps: {
+    show: boolean;
+    placement: PopperJS.Placement;
+    content: string | ((props: any) => JSX.Element);
+    renderContent: (content: any) => any;
+    theme?: Themes;
+  }
+) => JSX.Element;
+
+interface Props {
+  placement?: PopperJS.Placement;
+  content: PopperContent;
+  className?: string;
+  children: PopperControllerRenderProp;
+  theme?: Themes;
+}
+
+interface State {
+  placement: PopperJS.Placement;
+  show: boolean;
+}
+
+class PopperController extends React.Component<Props, State> {
+  constructor(props: Props) {
+    super(props);
+
+    this.state = {
+      placement: this.props.placement || 'auto',
+      show: false,
+    };
+  }
+
+  componentWillReceiveProps(nextProps: Props) {
+    if (nextProps.placement && nextProps.placement !== this.state.placement) {
+      this.setState((prevState: State) => {
+        return {
+          ...prevState,
+          placement: nextProps.placement || 'auto',
+        };
+      });
+    }
+  }
+
+  showPopper = () => {
+    this.setState(prevState => ({
+      ...prevState,
+      show: true,
+    }));
+  };
+
+  hidePopper = () => {
+    this.setState(prevState => ({
+      ...prevState,
+      show: false,
+    }));
+  };
+
+  renderContent(content: PopperContent) {
+    if (typeof content === 'function') {
+      // If it's a function we assume it's a React component
+      const ReactComponent = content;
+      return <ReactComponent />;
+    }
+    return content;
+  }
+
+  render() {
+    const { children, content, theme } = this.props;
+    const { show, placement } = this.state;
+
+    return children(this.showPopper, this.hidePopper, {
+      show,
+      placement,
+      content,
+      renderContent: this.renderContent,
+      theme,
+    });
+  }
+}
+
+export default PopperController;

+ 5 - 3
public/app/core/components/Tooltip/Tooltip.test.tsx → packages/grafana-ui/src/components/Tooltip/Tooltip.test.tsx

@@ -1,13 +1,15 @@
 import React from 'react';
 import renderer from 'react-test-renderer';
-import Tooltip from './Tooltip';
+import { Tooltip } from './Tooltip';
 
 describe('Tooltip', () => {
   it('renders correctly', () => {
     const tree = renderer
       .create(
-        <Tooltip className="test-class" placement="auto" content="Tooltip text">
-          <a href="http://www.grafana.com">Link with tooltip</a>
+        <Tooltip placement="auto" content="Tooltip text">
+          <a className="test-class" href="http://www.grafana.com">
+            Link with tooltip
+          </a>
         </Tooltip>
       )
       .toJSON();

+ 32 - 0
packages/grafana-ui/src/components/Tooltip/Tooltip.tsx

@@ -0,0 +1,32 @@
+import React, { createRef } from 'react';
+import * as PopperJS from 'popper.js';
+import Popper from './Popper';
+import PopperController, { UsingPopperProps } from './PopperController';
+
+export const Tooltip = ({ children, renderContent, ...controllerProps }: UsingPopperProps) => {
+  const tooltipTriggerRef = createRef<PopperJS.ReferenceObject>();
+
+  return (
+    <PopperController {...controllerProps}>
+      {(showPopper, hidePopper, popperProps) => {
+        return (
+          <>
+            {tooltipTriggerRef.current && (
+              <Popper
+                {...popperProps}
+                onMouseEnter={showPopper}
+                onMouseLeave={hidePopper}
+                referenceElement={tooltipTriggerRef.current}
+              />
+            )}
+            {React.cloneElement(children, {
+              ref: tooltipTriggerRef,
+              onMouseEnter: showPopper,
+              onMouseLeave: hidePopper,
+            })}
+          </>
+        );
+      }}
+    </PopperController>
+  );
+};

+ 17 - 9
public/sass/components/_popper.scss → 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;

+ 12 - 0
packages/grafana-ui/src/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap

@@ -0,0 +1,12 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Tooltip renders correctly 1`] = `
+<a
+  className="test-class"
+  href="http://www.grafana.com"
+  onMouseEnter={[Function]}
+  onMouseLeave={[Function]}
+>
+  Link with tooltip
+</a>
+`;

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

@@ -1 +1,3 @@
+@import 'CustomScrollbar/CustomScrollbar';
 @import 'DeleteButton/DeleteButton';
+@import 'Tooltip/Tooltip';

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

@@ -1 +1,4 @@
 export { DeleteButton } from './DeleteButton/DeleteButton';
+export { Tooltip } from './Tooltip/Tooltip';
+export { Portal } from './Portal/Portal';
+export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';

+ 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');
     }
   }
 

+ 1 - 2
pkg/tsdb/cloudwatch/credentials.go

@@ -3,7 +3,6 @@ package cloudwatch
 import (
 	"fmt"
 	"os"
-	"strings"
 	"sync"
 	"time"
 
@@ -43,7 +42,7 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) {
 	secretAccessKey := ""
 	sessionToken := ""
 	var expiration *time.Time = nil
-	if dsInfo.AuthType == "arn" && strings.Index(dsInfo.AssumeRoleArn, "arn:aws:iam:") == 0 {
+	if dsInfo.AuthType == "arn" {
 		params := &sts.AssumeRoleInput{
 			RoleArn:         aws.String(dsInfo.AssumeRoleArn),
 			RoleSessionName: aws.String("GrafanaSession"),

+ 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;

+ 5 - 3
public/app/core/components/Label/Label.tsx

@@ -1,5 +1,5 @@
 import React, { SFC, ReactNode } from 'react';
-import Tooltip from '../Tooltip/Tooltip';
+import { Tooltip } from '@grafana/ui';
 
 interface Props {
   tooltip?: string;
@@ -14,8 +14,10 @@ export const Label: SFC<Props> = props => {
     <span className={`gf-form-label width-${props.width ? props.width : '10'}`}>
       <span>{props.children}</span>
       {props.tooltip && (
-        <Tooltip className="gf-form-help-icon--right-normal" placement="auto" content={props.tooltip}>
-          <i className="gicon gicon-question gicon--has-hover" />
+        <Tooltip placement="auto" content={props.tooltip}>
+          <div className="gf-form-help-icon--right-normal">
+            <i className="gicon gicon-question gicon--has-hover" />
+          </div>
         </Tooltip>
       )}
     </span>

+ 1 - 1
public/app/core/components/Select/Select.tsx

@@ -11,7 +11,7 @@ import OptionGroup from './OptionGroup';
 import IndicatorsContainer from './IndicatorsContainer';
 import NoOptionsMessage from './NoOptionsMessage';
 import ResetStyles from './ResetStyles';
-import CustomScrollbar from '../CustomScrollbar/CustomScrollbar';
+import { CustomScrollbar } from '@grafana/ui';
 
 export interface SelectOptionItem {
   label?: string;

+ 1 - 1
public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx

@@ -1,5 +1,5 @@
 import React, { SFC, ReactNode, PureComponent } from 'react';
-import Tooltip from 'app/core/components/Tooltip/Tooltip';
+import { Tooltip } from '@grafana/ui';
 
 interface ToggleButtonGroupProps {
   label?: string;

+ 0 - 16
public/app/core/components/Tooltip/Popover.test.tsx

@@ -1,16 +0,0 @@
-import React from 'react';
-import renderer from 'react-test-renderer';
-import Popover from './Popover';
-
-describe('Popover', () => {
-  it('renders correctly', () => {
-    const tree = renderer
-      .create(
-        <Popover className="test-class" placement="auto" content="Popover text">
-          <button>Button with Popover</button>
-        </Popover>
-      )
-      .toJSON();
-    expect(tree).toMatchSnapshot();
-  });
-});

+ 0 - 19
public/app/core/components/Tooltip/Popover.tsx

@@ -1,19 +0,0 @@
-import React, { PureComponent } from 'react';
-import Popper from './Popper';
-import withPopper, { UsingPopperProps } from './withPopper';
-
-class Popover extends PureComponent<UsingPopperProps> {
-  render() {
-    const { children, hidePopper, showPopper, className, ...restProps } = this.props;
-
-    const togglePopper = restProps.show ? hidePopper : showPopper;
-
-    return (
-      <div className={`popper__manager ${className}`} onClick={togglePopper}>
-        <Popper {...restProps}>{children}</Popper>
-      </div>
-    );
-  }
-}
-
-export default withPopper(Popover);

+ 0 - 17
public/app/core/components/Tooltip/Tooltip.tsx

@@ -1,17 +0,0 @@
-import React, { PureComponent } from 'react';
-import Popper from './Popper';
-import withPopper, { UsingPopperProps } from './withPopper';
-
-class Tooltip extends PureComponent<UsingPopperProps> {
-  render() {
-    const { children, hidePopper, showPopper, className, ...restProps } = this.props;
-
-    return (
-      <div className={`popper__manager ${className}`} onMouseEnter={showPopper} onMouseLeave={hidePopper}>
-        <Popper {...restProps}>{children}</Popper>
-      </div>
-    );
-  }
-}
-
-export default withPopper(Tooltip);

+ 0 - 16
public/app/core/components/Tooltip/__snapshots__/Popover.test.tsx.snap

@@ -1,16 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Popover renders correctly 1`] = `
-<div
-  className="popper__manager test-class"
-  onClick={[Function]}
->
-  <div
-    className="popper_ref "
-  >
-    <button>
-      Button with Popover
-    </button>
-  </div>
-</div>
-`;

+ 0 - 19
public/app/core/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap

@@ -1,19 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Tooltip renders correctly 1`] = `
-<div
-  className="popper__manager test-class"
-  onMouseEnter={[Function]}
-  onMouseLeave={[Function]}
->
-  <div
-    className="popper_ref "
-  >
-    <a
-      href="http://www.grafana.com"
-    >
-      Link with tooltip
-    </a>
-  </div>
-</div>
-`;

+ 0 - 88
public/app/core/components/Tooltip/withPopper.tsx

@@ -1,88 +0,0 @@
-import React from 'react';
-
-export interface UsingPopperProps {
-  showPopper: (prevState: object) => void;
-  hidePopper: (prevState: object) => void;
-  renderContent: (content: any) => any;
-  show: boolean;
-  placement?: string;
-  content: string | ((props: any) => JSX.Element);
-  className?: string;
-  refClassName?: string;
-}
-
-interface Props {
-  placement?: string;
-  className?: string;
-  refClassName?: string;
-  content: string | ((props: any) => JSX.Element);
-}
-
-interface State {
-  placement: string;
-  show: boolean;
-}
-
-export default function withPopper(WrappedComponent) {
-  return class extends React.Component<Props, State> {
-    constructor(props) {
-      super(props);
-      this.setState = this.setState.bind(this);
-      this.state = {
-        placement: this.props.placement || 'auto',
-        show: false,
-      };
-    }
-
-    componentWillReceiveProps(nextProps) {
-      if (nextProps.placement && nextProps.placement !== this.state.placement) {
-        this.setState(prevState => {
-          return {
-            ...prevState,
-            placement: nextProps.placement,
-          };
-        });
-      }
-    }
-
-    showPopper = () => {
-      this.setState(prevState => ({
-        ...prevState,
-        show: true,
-      }));
-    };
-
-    hidePopper = () => {
-      this.setState(prevState => ({
-        ...prevState,
-        show: false,
-      }));
-    };
-
-    renderContent(content) {
-      if (typeof content === 'function') {
-        // If it's a function we assume it's a React component
-        const ReactComponent = content;
-        return <ReactComponent />;
-      }
-      return content;
-    }
-
-    render() {
-      const { show, placement } = this.state;
-      const className = this.props.className || '';
-
-      return (
-        <WrappedComponent
-          {...this.props}
-          showPopper={this.showPopper}
-          hidePopper={this.hidePopper}
-          renderContent={this.renderContent}
-          show={show}
-          placement={placement}
-          className={className}
-        />
-      );
-    }
-  };
-}

+ 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;

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

@@ -1,5 +1,9 @@
 // Library
 import React, { Component } from 'react';
+import { Tooltip } from '@grafana/ui';
+import { Themes } from '@grafana/ui/src/components/Tooltip/Popper';
+
+import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary';
 
 // Services
 import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource_srv';
@@ -11,6 +15,8 @@ import kbn from 'app/core/utils/kbn';
 import { DataQueryOptions, DataQueryResponse } from 'app/types';
 import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui';
 
+const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
+
 interface RenderProps {
   loading: LoadingState;
   timeSeries: TimeSeries[];
@@ -33,6 +39,7 @@ export interface Props {
 export interface State {
   isFirstLoad: boolean;
   loading: LoadingState;
+  errorMessage: string;
   response: DataQueryResponse;
 }
 
@@ -51,6 +58,7 @@ export class DataPanel extends Component<Props, State> {
 
     this.state = {
       loading: LoadingState.NotStarted,
+      errorMessage: '',
       response: {
         data: [],
       },
@@ -90,7 +98,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,7 +136,17 @@ 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,
+      });
     }
   };
 
@@ -139,7 +157,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 +170,44 @@ 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} placement="bottom-start" theme={Themes.Error}>
+          <div className="panel-info-corner panel-info-corner--error">
+            <i className="fa" />
+            <span className="panel-info-corner-inner" />
+          </div>
+        </Tooltip>
+      );
     }
 
     return null;

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

@@ -1,5 +1,5 @@
 import React, { SFC } from 'react';
-import Tooltip from 'app/core/components/Tooltip/Tooltip';
+import { Tooltip } from '@grafana/ui';
 
 interface Props {
   label: string;

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

@@ -2,7 +2,7 @@
 import React, { PureComponent } from 'react';
 
 // Components
-import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
+import { CustomScrollbar } from '@grafana/ui';
 import { FadeIn } from 'app/core/components/Animations/FadeIn';
 import { PanelOptionSection } from './PanelOptionSection';
 

+ 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 }) => {

+ 3 - 3
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';
@@ -15,7 +15,7 @@ import { PanelModel } from '../panel_model';
 import { DashboardModel } from '../dashboard_model';
 import { PanelPlugin } from 'app/types/plugins';
 
-import Tooltip from 'app/core/components/Tooltip/Tooltip';
+import { Tooltip } from '@grafana/ui';
 
 interface PanelEditorProps {
   panel: PanelModel;
@@ -138,7 +138,7 @@ function TabItem({ tab, activeTab, onClick }: TabItemParams) {
   return (
     <div className="panel-editor-tabs__item" onClick={() => onClick(tab)}>
       <a className={tabClasses}>
-        <Tooltip content={`${tab.text}`} className="popper__manager--block" placement="auto">
+        <Tooltip content={`${tab.text}`} placement="auto">
           <i className={`gicon gicon-${tab.id}${activeTab === tab.id ? '-active' : ''}`} />
         </Tooltip>
       </a>

+ 8 - 6
public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx

@@ -1,10 +1,10 @@
 import React, { Component } from 'react';
+import Remarkable from 'remarkable';
+import { Tooltip } from '@grafana/ui';
 import { PanelModel } from 'app/features/dashboard/panel_model';
-import Tooltip from 'app/core/components/Tooltip/Tooltip';
 import templateSrv from 'app/features/templating/template_srv';
 import { LinkSrv } from 'app/features/dashboard/panellinks/link_srv';
 import { getTimeSrv, TimeSrv } from 'app/features/dashboard/time_srv';
-import Remarkable from 'remarkable';
 
 enum InfoModes {
   Error = 'Error',
@@ -78,12 +78,14 @@ export class PanelHeaderCorner extends Component<Props> {
         {infoMode === InfoModes.Info || infoMode === InfoModes.Links ? (
           <Tooltip
             content={this.getInfoContent}
-            className="popper__manager--block"
-            refClassName={`panel-info-corner panel-info-corner--${infoMode.toLowerCase()}`}
             placement="bottom-start"
           >
-            <i className="fa" />
-            <span className="panel-info-corner-inner" />
+            <div
+              className={`panel-info-corner panel-info-corner--${infoMode.toLowerCase()}`}
+            >
+              <i className="fa" />
+              <span className="panel-info-corner-inner" />
+            </div>
           </Tooltip>
         ) : null}
       </>

+ 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;
 

+ 5 - 3
public/app/features/dashboard/permissions/DashboardPermissions.tsx

@@ -1,5 +1,5 @@
 import React, { PureComponent } from 'react';
-import Tooltip from 'app/core/components/Tooltip/Tooltip';
+import { Tooltip } from '@grafana/ui';
 import SlideDown from 'app/core/components/Animations/SlideDown';
 import { StoreState, FolderInfo } from 'app/types';
 import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl';
@@ -70,8 +70,10 @@ export class DashboardPermissions extends PureComponent<Props, State> {
         <div className="dashboard-settings__header">
           <div className="page-action-bar">
             <h3 className="d-inline-block">Permissions</h3>
-            <Tooltip className="page-sub-heading-icon" placement="auto" content={PermissionsInfo}>
-              <i className="gicon gicon-question gicon--has-hover" />
+            <Tooltip placement="auto" content={PermissionsInfo}>
+              <div className="page-sub-heading-icon">
+                <i className="gicon gicon-question gicon--has-hover" />
+              </div>
             </Tooltip>
             <div className="page-action-bar__spacer" />
             <button className="btn btn-success pull-right" onClick={this.onOpenAddPermissions} disabled={isAdding}>

+ 1 - 1
public/app/features/explore/Explore.tsx

@@ -944,7 +944,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
           <div className="navbar-buttons relative">
             <button className="btn navbar-button navbar-button--primary" onClick={this.onSubmit}>
               Run Query{' '}
-              {loading ? <i className="fa fa-spinner fa-spin run-icon" /> : <i className="fa fa-level-down run-icon" />}
+              {loading ? <i className="fa fa-spinner fa-fw fa-spin run-icon" /> : <i className="fa fa-level-down fa-fw run-icon" />}
             </button>
           </div>
         </div>

+ 5 - 3
public/app/features/folders/FolderPermissions.tsx

@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
 import { hot } from 'react-hot-loader';
 import { connect } from 'react-redux';
 import PageHeader from 'app/core/components/PageHeader/PageHeader';
-import Tooltip from 'app/core/components/Tooltip/Tooltip';
+import { Tooltip } from '@grafana/ui';
 import SlideDown from 'app/core/components/Animations/SlideDown';
 import { getNavModel } from 'app/core/selectors/navModel';
 import { NavModel, StoreState, FolderState } from 'app/types';
@@ -84,8 +84,10 @@ export class FolderPermissions extends PureComponent<Props, State> {
         <div className="page-container page-body">
           <div className="page-action-bar">
             <h3 className="page-sub-heading">Folder Permissions</h3>
-            <Tooltip className="page-sub-heading-icon" placement="auto" content={PermissionsInfo}>
-              <i className="gicon gicon-question gicon--has-hover" />
+            <Tooltip placement="auto" content={PermissionsInfo}>
+              <div className="page-sub-heading-icon">
+                <i className="gicon gicon-question gicon--has-hover" />
+              </div>
             </Tooltip>
             <div className="page-action-bar__spacer" />
             <button className="btn btn-success pull-right" onClick={this.onOpenAddPermissions} disabled={isAdding}>

+ 5 - 3
public/app/features/teams/TeamGroupSync.tsx

@@ -1,7 +1,7 @@
 import React, { PureComponent } from 'react';
 import { connect } from 'react-redux';
 import SlideDown from 'app/core/components/Animations/SlideDown';
-import Tooltip from 'app/core/components/Tooltip/Tooltip';
+import { Tooltip } from '@grafana/ui';
 import { TeamGroup } from '../../types';
 import { addTeamGroup, loadTeamGroups, removeTeamGroup } from './state/actions';
 import { getTeamGroups } from './state/selectors';
@@ -77,8 +77,10 @@ export class TeamGroupSync extends PureComponent<Props, State> {
       <div>
         <div className="page-action-bar">
           <h3 className="page-sub-heading">External group sync</h3>
-          <Tooltip className="page-sub-heading-icon" placement="auto" content={headerTooltip}>
-            <i className="gicon gicon-question gicon--has-hover" />
+          <Tooltip placement="auto" content={headerTooltip}>
+            <div className="page-sub-heading-icon">
+              <i className="gicon gicon-question gicon--has-hover" />
+            </div>
           </Tooltip>
           <div className="page-action-bar__spacer" />
           {groups.length > 0 && (

+ 1 - 0
public/app/features/teams/TeamSettings.tsx

@@ -60,6 +60,7 @@ export class TeamSettings extends React.Component<Props, State> {
               onChange={this.onChangeName}
             />
           </div>
+
           <div className="gf-form max-width-30">
             <Label tooltip="This is optional and is primarily used to set the team profile avatar (via gravatar service)">
               Email

+ 18 - 12
public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap

@@ -10,15 +10,18 @@ exports[`Render should render component 1`] = `
     >
       External group sync
     </h3>
-    <class_1
-      className="page-sub-heading-icon"
+    <Component
       content="Sync LDAP or OAuth groups with your Grafana teams."
       placement="auto"
     >
-      <i
-        className="gicon gicon-question gicon--has-hover"
-      />
-    </class_1>
+      <div
+        className="page-sub-heading-icon"
+      >
+        <i
+          className="gicon gicon-question gicon--has-hover"
+        />
+      </div>
+    </Component>
     <div
       className="page-action-bar__spacer"
     />
@@ -116,15 +119,18 @@ exports[`Render should render groups table 1`] = `
     >
       External group sync
     </h3>
-    <class_1
-      className="page-sub-heading-icon"
+    <Component
       content="Sync LDAP or OAuth groups with your Grafana teams."
       placement="auto"
     >
-      <i
-        className="gicon gicon-question gicon--has-hover"
-      />
-    </class_1>
+      <div
+        className="page-sub-heading-icon"
+      >
+        <i
+          className="gicon gicon-question gicon--has-hover"
+        />
+      </div>
+    </Component>
     <div
       className="page-action-bar__spacer"
     />

+ 5 - 5
public/app/plugins/datasource/stackdriver/constants.ts

@@ -151,32 +151,32 @@ export const aggOptions = [
   {
     text: 'mean',
     value: 'REDUCE_MEAN',
-    valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY],
+    valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION],
     metricKinds: [MetricKind.GAUGE, MetricKind.DELTA],
   },
   {
     text: 'min',
     value: 'REDUCE_MIN',
     valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY],
-    metricKinds: [MetricKind.GAUGE, MetricKind.DELTA],
+    metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE, MetricKind.METRIC_KIND_UNSPECIFIED],
   },
   {
     text: 'max',
     value: 'REDUCE_MAX',
     valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY],
-    metricKinds: [MetricKind.GAUGE, MetricKind.DELTA],
+    metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE, MetricKind.METRIC_KIND_UNSPECIFIED],
   },
   {
     text: 'sum',
     value: 'REDUCE_SUM',
     valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION],
-    metricKinds: [MetricKind.GAUGE, MetricKind.DELTA],
+    metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE, MetricKind.METRIC_KIND_UNSPECIFIED],
   },
   {
     text: 'std. dev.',
     value: 'REDUCE_STDDEV',
     valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION],
-    metricKinds: [MetricKind.GAUGE, MetricKind.DELTA],
+    metricKinds: [MetricKind.GAUGE, MetricKind.DELTA, MetricKind.CUMULATIVE, MetricKind.METRIC_KIND_UNSPECIFIED],
   },
   {
     text: 'count',

+ 1 - 1
public/app/plugins/panel/graph/Legend/Legend.tsx

@@ -1,7 +1,7 @@
 import _ from 'lodash';
 import React, { PureComponent } from 'react';
 import { TimeSeries } from 'app/core/core';
-import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
+import { CustomScrollbar } from '@grafana/ui';
 import { LegendItem, LEGEND_STATS } from './LegendSeriesItem';
 
 interface LegendProps {

+ 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;

+ 0 - 1
public/sass/_grafana.scss

@@ -97,7 +97,6 @@
 @import 'components/page_header';
 @import 'components/dashboard_settings';
 @import 'components/empty_list_cta';
-@import 'components/popper';
 @import 'components/form_select_box';
 @import 'components/panel_editor';
 @import 'components/toolbar';

+ 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';
 

+ 6 - 4
public/sass/_variables.light.scss

@@ -76,8 +76,7 @@ $textShadow: none;
 
 // gradients
 $brand-gradient: linear-gradient(to right, rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%);
-$page-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
-//$page-gradient: linear-gradient(180deg, $white 10px, $gray-7 100px);
+$page-gradient: linear-gradient(180deg, $white 10px, $gray-7 100px);
 $edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
 
 // Links
@@ -102,6 +101,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 +307,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';
 

+ 0 - 44
public/sass/components/_scrollbar.scss

@@ -295,50 +295,6 @@
   }
 }
 
-// Custom styles for 'react-custom-scrollbars'
-
-.custom-scrollbars {
-  // Fix for Firefox. For some reason sometimes .view container gets a height of its content, but in order to
-  // make scroll working it should fit outer container size (scroll appears only when inner container size is
-  // greater than outer one).
-  display: flex;
-  flex-grow: 1;
-
-  .view {
-    display: flex;
-    flex-grow: 1;
-    flex-direction: column;
-  }
-
-  .track-vertical {
-    border-radius: 3px;
-    width: 6px !important;
-
-    right: 2px;
-    bottom: 2px;
-    top: 2px;
-  }
-
-  .track-horizontal {
-    border-radius: 3px;
-    height: 6px !important;
-
-    right: 2px;
-    bottom: 2px;
-    left: 2px;
-  }
-
-  .thumb-vertical {
-    @include gradient-vertical($scrollbarBackground, $scrollbarBackground2);
-    border-radius: 6px;
-  }
-
-  .thumb-horizontal {
-    @include gradient-horizontal($scrollbarBackground, $scrollbarBackground2);
-    border-radius: 6px;
-  }
-}
-
 .scroll-margin-helper {
   margin-right: 12px;
 }

+ 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';

+ 1 - 1
public/sass/pages/_explore.scss

@@ -160,7 +160,7 @@
   }
 
   .run-icon {
-    margin-left: 0.5em;
+    margin-left: 0.25em;
     transform: rotate(90deg);
   }
 

+ 23 - 3
scripts/build/ci-deploy/Dockerfile

@@ -1,5 +1,25 @@
+FROM circleci/golang:1.11
+
+RUN git clone https://github.com/aptly-dev/aptly $GOPATH/src/github.com/aptly-dev/aptly && \
+    cd $GOPATH/src/github.com/aptly-dev/aptly && \
+    # pin aptly to a specific commit after 1.3.0 that contains gpg2 support
+    git reset --hard a64807efdaf5e380bfa878c71bc88eae10d62be1 && \
+    make install
+
 FROM circleci/python:2.7-stretch
 
-RUN sudo pip install awscli && \
-  curl https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-222.0.0-linux-x86_64.tar.gz | \
-  sudo tar xvzf - -C /opt
+ENV PATH=$PATH:/opt/google-cloud-sdk/bin
+
+USER root
+
+RUN pip install awscli && \
+    curl https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-222.0.0-linux-x86_64.tar.gz | \
+      tar xvzf - -C /opt && \
+    apt update && \
+    apt install -y createrepo expect && \
+    apt-get autoremove -y && \
+    rm -rf /var/lib/apt/lists/*
+
+COPY --from=0 /go/bin/aptly /usr/local/bin/aptly
+
+USER circleci

+ 1 - 1
scripts/build/ci-deploy/build-deploy.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-_version="1.0.0"
+_version="1.1.0"
 _tag="grafana/grafana-ci-deploy:${_version}"
 
 docker build -t $_tag .

+ 7 - 0
scripts/build/load-signing-key.sh

@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+set -e
+
+git clone git@github.com:torkelo/private.git ~/private-repo
+gpg --batch --allow-secret-key-import --import ~/private-repo/signing/private.key
+pkill gpg-agent

+ 27 - 0
scripts/build/update_repo/aptly.conf

@@ -0,0 +1,27 @@
+{
+  "rootDir": "/deb-repo/db",
+  "downloadConcurrency": 4,
+  "downloadSpeedLimit": 0,
+  "architectures": [],
+  "dependencyFollowSuggests": false,
+  "dependencyFollowRecommends": false,
+  "dependencyFollowAllVariants": false,
+  "dependencyFollowSource": false,
+  "dependencyVerboseResolve": false,
+  "gpgDisableSign": false,
+  "gpgDisableVerify": false,
+  "gpgProvider": "gpg2",
+  "downloadSourcePackages": false,
+  "skipLegacyPool": true,
+  "ppaDistributorID": "ubuntu",
+  "ppaCodename": "",
+  "skipContentsPublishing": false,
+  "FileSystemPublishEndpoints": {
+    "repo": {
+      "rootDir": "/deb-repo/repo",
+      "linkMethod": "copy"
+    }
+  },
+  "S3PublishEndpoints": {},
+  "SwiftPublishEndpoints": {}
+}

+ 7 - 0
scripts/build/update_repo/sign-rpm-repo.sh

@@ -0,0 +1,7 @@
+#!/usr/bin/env expect
+
+set password [lindex $argv 0]
+spawn gpg --detach-sign --armor /rpm-repo/repodata/repomd.xml
+expect "Enter passphrase: "
+send -- "$password\r"
+expect eof

+ 7 - 0
scripts/build/update_repo/unlock-gpg-key.sh

@@ -0,0 +1,7 @@
+#!/usr/bin/env expect
+
+set password [lindex $argv 0]
+spawn gpg --detach-sign --armor /tmp/sign-this
+expect "Enter passphrase: "
+send -- "$password\r"
+expect eof

+ 58 - 0
scripts/build/update_repo/update-deb.sh

@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+
+RELEASE_TYPE="${1:-}"
+GPG_PASS="${2:-}"
+RELEASE_TAG="${3:-}"
+REPO="grafana"
+
+if [ -z "$RELEASE_TYPE" -o -z "$GPG_PASS" ]; then
+    echo "Both RELEASE_TYPE (arg 1) and GPG_PASS (arg 2) has to be set"
+    exit 1
+fi
+
+if [[ "$RELEASE_TYPE" != "oss" && "$RELEASE_TYPE" != "enterprise" ]]; then
+    echo "RELEASE_TYPE (arg 1) must be either oss or enterprise."
+    exit 1
+fi
+
+if echo "$RELEASE_TAG" | grep -q "beta"; then
+    REPO="beta"
+fi
+
+set -e
+
+# Setup environment
+cp scripts/build/update_repo/aptly.conf /etc/aptly.conf
+mkdir -p /deb-repo/db   \
+         /deb-repo/repo \
+         /deb-repo/tmp
+
+# Download the database
+gsutil -m rsync -r "gs://grafana-aptly-db/$RELEASE_TYPE" /deb-repo/db
+
+# Add the new release to the repo
+aptly publish drop grafana filesystem:repo:grafana || true
+aptly publish drop beta filesystem:repo:grafana || true
+cp ./dist/*.deb /deb-repo/tmp
+rm /deb-repo/tmp/grafana_latest*.deb || true
+aptly repo add "$REPO" ./dist
+
+# Setup signing and sign the repo
+
+echo "allow-loopback-pinentry" > ~/.gnupg/gpg-agent.conf
+echo "pinentry-mode loopback" > ~/.gnupg/gpg.conf
+
+touch /tmp/sign-this
+./scripts/build/update_repo/unlock-gpg-key.sh "$GPG_PASS"
+rm /tmp/sign-this /tmp/sign-this.asc
+
+aptly publish repo grafana filesystem:repo:grafana
+aptly publish repo beta filesystem:repo:grafana
+
+# Update the repo and db on gcp
+gsutil -m rsync -r -d /deb-repo/db "gs://grafana-aptly-db/$RELEASE_TYPE"
+gsutil -m rsync -r -d /deb-repo/repo/grafana "gs://grafana-repo/$RELEASE_TYPE/deb"
+
+# usage:
+# 
+# deb https://packages.grafana.com/oss/deb stable main

+ 59 - 0
scripts/build/update_repo/update-rpm.sh

@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+
+RELEASE_TYPE="${1:-}"
+GPG_PASS="${2:-}"
+
+RELEASE_TAG="${3:-}"
+REPO="rpm"
+
+if [ -z "$RELEASE_TYPE" -o -z "$GPG_PASS" ]; then
+    echo "Both RELEASE_TYPE (arg 1) and GPG_PASS (arg 2) has to be set"
+    exit 1
+fi
+
+if [[ "$RELEASE_TYPE" != "oss" && "$RELEASE_TYPE" != "enterprise" ]]; then
+    echo "RELEASE_TYPE (arg 1) must be either oss or enterprise."
+    exit 1
+fi
+
+if echo "$RELEASE_TAG" | grep -q "beta"; then
+    REPO="rpm-beta"
+fi
+
+set -e
+
+# Setup environment
+BUCKET="gs://grafana-repo/$RELEASE_TYPE/$REPO"
+mkdir -p /rpm-repo
+
+# Download the database
+gsutil -m rsync -r "$BUCKET" /rpm-repo
+
+# Add the new release to the repo
+cp ./dist/*.rpm /rpm-repo
+rm /rpm-repo/grafana-latest-1*.rpm || true
+cd /rpm-repo
+createrepo .
+
+# Setup signing and sign the repo
+
+echo "allow-loopback-pinentry" > ~/.gnupg/gpg-agent.conf
+echo "pinentry-mode loopback" > ~/.gnupg/gpg.conf
+
+rm /rpm-repo/repodata/repomd.xml.asc || true
+pkill gpg-agent || true
+./scripts/build/update_repo/sign-rpm-repo.sh "$GPG_PASS"
+
+# Update the repo and db on gcp
+gsutil -m rsync -r -d /rpm-repo "$BUCKET"
+
+# usage:
+# [grafana]
+# name=grafana
+# baseurl=https://packages.grafana.com/oss/rpm
+# repo_gpgcheck=1
+# enabled=1
+# gpgcheck=1
+# gpgkey=https://packages.grafana.com/gpg.key
+# sslverify=1
+# sslcacert=/etc/pki/tls/certs/ca-bundle.crt

+ 11 - 2
yarn.lock

@@ -937,7 +937,14 @@
     "@types/react-dom" "*"
     "@types/react-transition-group" "*"
 
-"@types/react-transition-group@*":
+"@types/react-test-renderer@^16.0.3":
+  version "16.0.3"
+  resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.0.3.tgz#cce5c983d66cc5c3582e7c2f44b274ab635a8acc"
+  integrity sha512-NWOAxVQeJxpXuNKgw83Hah0nquiw1nUexM9qY/Hk3a+XhZwgMtaa6GLA9E1TKMT75Odb3/KE/jiBO4enTuEJjQ==
+  dependencies:
+    "@types/react" "*"
+
+"@types/react-transition-group@*", "@types/react-transition-group@^2.0.15":
   version "2.0.15"
   resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-2.0.15.tgz#e5ee3fe558832e141cc6041bdd54caea7b787af8"
   dependencies:
@@ -2743,6 +2750,7 @@ caniuse-api@^1.5.2:
 caniuse-db@1.0.30000772, caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
   version "1.0.30000772"
   resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b"
+  integrity sha1-UarokXaChureSj2DGep21qAbUSs=
 
 caniuse-lite@^1.0.30000925:
   version "1.0.30000926"
@@ -10561,7 +10569,7 @@ react-table@^6.8.6:
   dependencies:
     classnames "^2.2.5"
 
-react-test-renderer@^16.0.0-0, react-test-renderer@^16.5.0:
+react-test-renderer@^16.0.0-0, react-test-renderer@^16.5.0, react-test-renderer@^16.7.0:
   version "16.7.0"
   resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.7.0.tgz#1ca96c2b450ab47c36ba92cd8c03fcefc52ea01c"
   dependencies:
@@ -11298,6 +11306,7 @@ sax@^1.2.4, sax@~1.2.1:
 scheduler@^0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.12.0.tgz#8ab17699939c0aedc5a196a657743c496538647b"
+  integrity sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==
   dependencies:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"