Browse Source

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

# Conflicts:
#	yarn.lock
Erik Sundell 7 years ago
parent
commit
92121cacd1
64 changed files with 670 additions and 346 deletions
  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:
   deploy-enterprise-master:
     docker:
     docker:
-      - image: grafana/grafana-ci-deploy:1.0.0
+      - image: grafana/grafana-ci-deploy:1.1.0
     steps:
     steps:
       - attach_workspace:
       - attach_workspace:
           at: .
           at: .
@@ -346,7 +346,7 @@ jobs:
 
 
   deploy-enterprise-release:
   deploy-enterprise-release:
     docker:
     docker:
-    - image: grafana/grafana-ci-deploy:1.0.0
+    - image: grafana/grafana-ci-deploy:1.1.0
     steps:
     steps:
       - attach_workspace:
       - attach_workspace:
          at: .
          at: .
@@ -365,10 +365,20 @@ jobs:
       - run:
       - run:
           name: Deploy to Grafana.com
           name: Deploy to Grafana.com
           command: './scripts/build/publish.sh --enterprise'
           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:
   deploy-master:
     docker:
     docker:
-      - image: grafana/grafana-ci-deploy:1.0.0
+      - image: grafana/grafana-ci-deploy:1.1.0
     steps:
     steps:
       - attach_workspace:
       - attach_workspace:
           at: .
           at: .
@@ -398,8 +408,9 @@ jobs:
 
 
   deploy-release:
   deploy-release:
     docker:
     docker:
-      - image: grafana/grafana-ci-deploy:1.0.0
+      - image: grafana/grafana-ci-deploy:1.1.0
     steps:
     steps:
+      - checkout
       - attach_workspace:
       - attach_workspace:
           at: .
           at: .
       - run:
       - run:
@@ -417,6 +428,15 @@ jobs:
       - run:
       - run:
           name: Deploy to Grafana.com
           name: Deploy to Grafana.com
           command: './scripts/build/publish.sh'
           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:
 workflows:
   version: 2
   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)
 * **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)
 * **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)
 * **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
 ### Bug fixes
 * **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486)
 * **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
 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.
 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
 ```bash

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

@@ -76,6 +76,20 @@ sslverify=1
 sslcacert=/etc/pki/tls/certs/ca-bundle.crt
 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.
 Then install Grafana via the `yum` command.
 
 
 ```bash
 ```bash

+ 1 - 3
package.json

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

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

@@ -11,11 +11,14 @@
   "license": "ISC",
   "license": "ISC",
   "dependencies": {
   "dependencies": {
     "@torkelo/react-select": "2.1.1",
     "@torkelo/react-select": "2.1.1",
+    "@types/react-test-renderer": "^16.0.3",
+    "@types/react-transition-group": "^2.0.15",
     "classnames": "^2.2.5",
     "classnames": "^2.2.5",
     "jquery": "^3.2.1",
     "jquery": "^3.2.1",
     "lodash": "^4.17.10",
     "lodash": "^4.17.10",
     "moment": "^2.22.2",
     "moment": "^2.22.2",
     "react": "^16.6.3",
     "react": "^16.6.3",
+    "react-custom-scrollbars": "^4.2.1",
     "react-dom": "^16.6.3",
     "react-dom": "^16.6.3",
     "react-highlight-words": "0.11.0",
     "react-highlight-words": "0.11.0",
     "react-popper": "^1.3.0",
     "react-popper": "^1.3.0",
@@ -23,11 +26,14 @@
     "react-virtualized": "^9.21.0"
     "react-virtualized": "^9.21.0"
   },
   },
   "devDependencies": {
   "devDependencies": {
+    "@types/classnames": "^2.2.6",
     "@types/jest": "^23.3.2",
     "@types/jest": "^23.3.2",
+    "@types/jquery": "^1.10.35",
     "@types/lodash": "^4.14.119",
     "@types/lodash": "^4.14.119",
     "@types/react": "^16.7.6",
     "@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"
     "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`
  * Wraps component into <Scrollbars> component from `react-custom-scrollbars`
  */
  */
-class CustomScrollbar extends PureComponent<Props> {
+export class CustomScrollbar extends PureComponent<Props> {
   static defaultProps: Partial<Props> = {
   static defaultProps: Partial<Props> = {
     customClassName: 'custom-scrollbars',
     customClassName: 'custom-scrollbars',
     autoHide: true,
     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;
   root?: HTMLElement;
 }
 }
 
 
-export default class BodyPortal extends PureComponent<Props> {
+export class Portal extends PureComponent<Props> {
   node: HTMLElement = document.createElement('div');
   node: HTMLElement = document.createElement('div');
   portalRoot: HTMLElement;
   portalRoot: HTMLElement;
 
 
-  constructor(props) {
+  constructor(props: Props) {
     super(props);
     super(props);
     const { className, root = document.body } = this.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 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';
 import Transition from 'react-transition-group/Transition';
 
 
+export enum Themes {
+  Default = 'popper__background--default',
+  Error = 'popper__background--error',
+}
+
 const defaultTransitionStyles = {
 const defaultTransitionStyles = {
   transition: 'opacity 200ms linear',
   transition: 'opacity 200ms linear',
   opacity: 0,
   opacity: 0,
 };
 };
 
 
-const transitionStyles = {
+const transitionStyles: {[key: string]: object} = {
   exited: { opacity: 0 },
   exited: { opacity: 0 },
   entering: { opacity: 0 },
   entering: { opacity: 0 },
   entered: { opacity: 1 },
   entered: { opacity: 1 },
   exiting: { opacity: 0 },
   exiting: { opacity: 0 },
 };
 };
 
 
-interface Props {
+interface Props extends React.DOMAttributes<HTMLDivElement> {
   renderContent: (content: any) => any;
   renderContent: (content: any) => any;
   show: boolean;
   show: boolean;
-  placement?: any;
+  placement?: PopperJS.Placement;
   content: string | ((props: any) => JSX.Element);
   content: string | ((props: any) => JSX.Element);
-  refClassName?: string;
+  referenceElement: PopperJS.ReferenceObject;
+  theme?: Themes;
 }
 }
 
 
 class Popper extends PureComponent<Props> {
 class Popper extends PureComponent<Props> {
   render() {
   render() {
-    const { children, renderContent, show, placement, refClassName } = this.props;
+    const { renderContent, show, placement, onMouseEnter, onMouseLeave, theme } = this.props;
     const { content } = this.props;
     const { content } = this.props;
 
 
+    const popperBackgroundClassName = 'popper__background' + (theme ? ' ' + theme : '');
+
     return (
     return (
       <Manager>
       <Manager>
-        <Reference>
-          {({ ref }) => (
-            <div className={`popper_ref ${refClassName || ''}`} ref={ref}>
-              {children}
-            </div>
-          )}
-        </Reference>
         <Transition in={show} timeout={100} mountOnEnter={true} unmountOnExit={true}>
         <Transition in={show} timeout={100} mountOnEnter={true} unmountOnExit={true}>
           {transitionState => (
           {transitionState => (
             <Portal>
             <Portal>
-              <ReactPopper placement={placement}>
+              <ReactPopper placement={placement} referenceElement={this.props.referenceElement}>
                 {({ ref, style, placement, arrowProps }) => {
                 {({ ref, style, placement, arrowProps }) => {
                   return (
                   return (
                     <div
                     <div
+                      onMouseEnter={onMouseEnter}
+                      onMouseLeave={onMouseLeave}
                       ref={ref}
                       ref={ref}
                       style={{
                       style={{
                         ...style,
                         ...style,
@@ -53,7 +57,7 @@ class Popper extends PureComponent<Props> {
                       data-placement={placement}
                       data-placement={placement}
                       className="popper"
                       className="popper"
                     >
                     >
-                      <div className="popper__background">
+                      <div className={popperBackgroundClassName}>
                         {renderContent(content)}
                         {renderContent(content)}
                         <div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" />
                         <div ref={arrowProps.ref} data-placement={placement} className="popper__arrow" />
                       </div>
                       </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 React from 'react';
 import renderer from 'react-test-renderer';
 import renderer from 'react-test-renderer';
-import Tooltip from './Tooltip';
+import { Tooltip } from './Tooltip';
 
 
 describe('Tooltip', () => {
 describe('Tooltip', () => {
   it('renders correctly', () => {
   it('renders correctly', () => {
     const tree = renderer
     const tree = renderer
       .create(
       .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>
         </Tooltip>
       )
       )
       .toJSON();
       .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;
   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;
   width: 0;
   height: 0;
   height: 0;
   border-style: solid;
   border-style: solid;
@@ -16,17 +31,10 @@ $popper-margin-from-ref: 5px;
   margin: 0px;
   margin: 0px;
 }
 }
 
 
-.popper .popper__arrow {
+.popper__arrow {
   border-color: $tooltipBackground;
   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
 // Top
 .popper[data-placement^='top'] {
 .popper[data-placement^='top'] {
   padding-bottom: $popper-margin-from-ref;
   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 'DeleteButton/DeleteButton';
+@import 'Tooltip/Tooltip';

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

@@ -1 +1,4 @@
 export { DeleteButton } from './DeleteButton/DeleteButton';
 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);
       $.plot(this.element, timeSeries, flotOptions);
     } catch (err) {
     } catch (err) {
       console.log('Graph rendering error', err, flotOptions, timeSeries);
       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 (
 import (
 	"fmt"
 	"fmt"
 	"os"
 	"os"
-	"strings"
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
@@ -43,7 +42,7 @@ func GetCredentials(dsInfo *DatasourceInfo) (*credentials.Credentials, error) {
 	secretAccessKey := ""
 	secretAccessKey := ""
 	sessionToken := ""
 	sessionToken := ""
 	var expiration *time.Time = nil
 	var expiration *time.Time = nil
-	if dsInfo.AuthType == "arn" && strings.Index(dsInfo.AssumeRoleArn, "arn:aws:iam:") == 0 {
+	if dsInfo.AuthType == "arn" {
 		params := &sts.AssumeRoleInput{
 		params := &sts.AssumeRoleInput{
 			RoleArn:         aws.String(dsInfo.AssumeRoleArn),
 			RoleArn:         aws.String(dsInfo.AssumeRoleArn),
 			RoleSessionName: aws.String("GrafanaSession"),
 			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 React, { SFC, ReactNode } from 'react';
-import Tooltip from '../Tooltip/Tooltip';
+import { Tooltip } from '@grafana/ui';
 
 
 interface Props {
 interface Props {
   tooltip?: string;
   tooltip?: string;
@@ -14,8 +14,10 @@ export const Label: SFC<Props> = props => {
     <span className={`gf-form-label width-${props.width ? props.width : '10'}`}>
     <span className={`gf-form-label width-${props.width ? props.width : '10'}`}>
       <span>{props.children}</span>
       <span>{props.children}</span>
       {props.tooltip && (
       {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>
         </Tooltip>
       )}
       )}
     </span>
     </span>

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

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

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

@@ -1,5 +1,5 @@
 import React, { SFC, ReactNode, PureComponent } from 'react';
 import React, { SFC, ReactNode, PureComponent } from 'react';
-import Tooltip from 'app/core/components/Tooltip/Tooltip';
+import { Tooltip } from '@grafana/ui';
 
 
 interface ToggleButtonGroupProps {
 interface ToggleButtonGroupProps {
   label?: string;
   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';
 import appEvents from 'app/core/app_events';
 
 
 // Components
 // Components
-import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
+import { EditorTabBody, EditorToolbarView } from '../dashboard/dashgrid/EditorTabBody';
 import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
 import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
 import StateHistory from './StateHistory';
 import StateHistory from './StateHistory';
 import 'app/features/alerting/AlertTabCtrl';
 import 'app/features/alerting/AlertTabCtrl';
 
 
 // Types
 // 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 {
 interface Props {
   angularPanel?: AngularComponent;
   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 React, { PureComponent } from 'react';
-import alertDef from '../../alerting/state/alertDef';
+import alertDef from './state/alertDef';
 import { getBackendSrv } from 'app/core/services/backend_srv';
 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 {
 interface Props {
   dashboard: DashboardModel;
   dashboard: DashboardModel;

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

@@ -1,5 +1,9 @@
 // Library
 // Library
 import React, { Component } from 'react';
 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
 // Services
 import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource_srv';
 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 { DataQueryOptions, DataQueryResponse } from 'app/types';
 import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui';
 import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui';
 
 
+const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
+
 interface RenderProps {
 interface RenderProps {
   loading: LoadingState;
   loading: LoadingState;
   timeSeries: TimeSeries[];
   timeSeries: TimeSeries[];
@@ -33,6 +39,7 @@ export interface Props {
 export interface State {
 export interface State {
   isFirstLoad: boolean;
   isFirstLoad: boolean;
   loading: LoadingState;
   loading: LoadingState;
+  errorMessage: string;
   response: DataQueryResponse;
   response: DataQueryResponse;
 }
 }
 
 
@@ -51,6 +58,7 @@ export class DataPanel extends Component<Props, State> {
 
 
     this.state = {
     this.state = {
       loading: LoadingState.NotStarted,
       loading: LoadingState.NotStarted,
+      errorMessage: '',
       response: {
       response: {
         data: [],
         data: [],
       },
       },
@@ -90,7 +98,7 @@ export class DataPanel extends Component<Props, State> {
       return;
       return;
     }
     }
 
 
-    this.setState({ loading: LoadingState.Loading });
+    this.setState({ loading: LoadingState.Loading, errorMessage: '' });
 
 
     try {
     try {
       const ds = await this.dataSourceSrv.get(datasource);
       const ds = await this.dataSourceSrv.get(datasource);
@@ -128,7 +136,17 @@ export class DataPanel extends Component<Props, State> {
       });
       });
     } catch (err) {
     } catch (err) {
       console.log('Loading error', 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;
     const timeSeries = response.data;
 
 
     if (isFirstLoad && loading === LoadingState.Loading) {
     if (isFirstLoad && loading === LoadingState.Loading) {
-      return this.renderLoadingSpinner();
+      return this.renderLoadingStates();
     }
     }
 
 
     if (!queries.length) {
     if (!queries.length) {
@@ -152,24 +170,44 @@ export class DataPanel extends Component<Props, State> {
 
 
     return (
     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) {
     if (loading === LoadingState.Loading) {
       return (
       return (
         <div className="panel-loading">
         <div className="panel-loading">
           <i className="fa fa-spinner fa-spin" />
           <i className="fa fa-spinner fa-spin" />
         </div>
         </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;
     return null;

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

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

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

@@ -2,7 +2,7 @@
 import React, { PureComponent } from 'react';
 import React, { PureComponent } from 'react';
 
 
 // Components
 // Components
-import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
+import { CustomScrollbar } from '@grafana/ui';
 import { FadeIn } from 'app/core/components/Animations/FadeIn';
 import { FadeIn } from 'app/core/components/Animations/FadeIn';
 import { PanelOptionSection } from './PanelOptionSection';
 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 { datasource, targets, transparent } = panel;
     const PanelComponent = plugin.exports.Panel;
     const PanelComponent = plugin.exports.Panel;
     const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`;
     const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`;
-
     return (
     return (
       <AutoSizer>
       <AutoSizer>
         {({ width, height }) => {
         {({ width, height }) => {

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

@@ -4,7 +4,7 @@ import classNames from 'classnames';
 import { QueriesTab } from './QueriesTab';
 import { QueriesTab } from './QueriesTab';
 import { VisualizationTab } from './VisualizationTab';
 import { VisualizationTab } from './VisualizationTab';
 import { GeneralTab } from './GeneralTab';
 import { GeneralTab } from './GeneralTab';
-import { AlertTab } from './AlertTab';
+import { AlertTab } from '../../alerting/AlertTab';
 
 
 import config from 'app/core/config';
 import config from 'app/core/config';
 import { store } from 'app/store/store';
 import { store } from 'app/store/store';
@@ -15,7 +15,7 @@ import { PanelModel } from '../panel_model';
 import { DashboardModel } from '../dashboard_model';
 import { DashboardModel } from '../dashboard_model';
 import { PanelPlugin } from 'app/types/plugins';
 import { PanelPlugin } from 'app/types/plugins';
 
 
-import Tooltip from 'app/core/components/Tooltip/Tooltip';
+import { Tooltip } from '@grafana/ui';
 
 
 interface PanelEditorProps {
 interface PanelEditorProps {
   panel: PanelModel;
   panel: PanelModel;
@@ -138,7 +138,7 @@ function TabItem({ tab, activeTab, onClick }: TabItemParams) {
   return (
   return (
     <div className="panel-editor-tabs__item" onClick={() => onClick(tab)}>
     <div className="panel-editor-tabs__item" onClick={() => onClick(tab)}>
       <a className={tabClasses}>
       <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' : ''}`} />
           <i className={`gicon gicon-${tab.id}${activeTab === tab.id ? '-active' : ''}`} />
         </Tooltip>
         </Tooltip>
       </a>
       </a>

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

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

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

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

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

@@ -1,5 +1,5 @@
 import React, { PureComponent } from 'react';
 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 SlideDown from 'app/core/components/Animations/SlideDown';
 import { StoreState, FolderInfo } from 'app/types';
 import { StoreState, FolderInfo } from 'app/types';
 import { DashboardAcl, PermissionLevel, NewDashboardAclItem } from 'app/types/acl';
 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="dashboard-settings__header">
           <div className="page-action-bar">
           <div className="page-action-bar">
             <h3 className="d-inline-block">Permissions</h3>
             <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>
             </Tooltip>
             <div className="page-action-bar__spacer" />
             <div className="page-action-bar__spacer" />
             <button className="btn btn-success pull-right" onClick={this.onOpenAddPermissions} disabled={isAdding}>
             <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">
           <div className="navbar-buttons relative">
             <button className="btn navbar-button navbar-button--primary" onClick={this.onSubmit}>
             <button className="btn navbar-button navbar-button--primary" onClick={this.onSubmit}>
               Run Query{' '}
               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>
             </button>
           </div>
           </div>
         </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 { hot } from 'react-hot-loader';
 import { connect } from 'react-redux';
 import { connect } from 'react-redux';
 import PageHeader from 'app/core/components/PageHeader/PageHeader';
 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 SlideDown from 'app/core/components/Animations/SlideDown';
 import { getNavModel } from 'app/core/selectors/navModel';
 import { getNavModel } from 'app/core/selectors/navModel';
 import { NavModel, StoreState, FolderState } from 'app/types';
 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-container page-body">
           <div className="page-action-bar">
           <div className="page-action-bar">
             <h3 className="page-sub-heading">Folder Permissions</h3>
             <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>
             </Tooltip>
             <div className="page-action-bar__spacer" />
             <div className="page-action-bar__spacer" />
             <button className="btn btn-success pull-right" onClick={this.onOpenAddPermissions} disabled={isAdding}>
             <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 React, { PureComponent } from 'react';
 import { connect } from 'react-redux';
 import { connect } from 'react-redux';
 import SlideDown from 'app/core/components/Animations/SlideDown';
 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 { TeamGroup } from '../../types';
 import { addTeamGroup, loadTeamGroups, removeTeamGroup } from './state/actions';
 import { addTeamGroup, loadTeamGroups, removeTeamGroup } from './state/actions';
 import { getTeamGroups } from './state/selectors';
 import { getTeamGroups } from './state/selectors';
@@ -77,8 +77,10 @@ export class TeamGroupSync extends PureComponent<Props, State> {
       <div>
       <div>
         <div className="page-action-bar">
         <div className="page-action-bar">
           <h3 className="page-sub-heading">External group sync</h3>
           <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>
           </Tooltip>
           <div className="page-action-bar__spacer" />
           <div className="page-action-bar__spacer" />
           {groups.length > 0 && (
           {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}
               onChange={this.onChangeName}
             />
             />
           </div>
           </div>
+
           <div className="gf-form max-width-30">
           <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)">
             <Label tooltip="This is optional and is primarily used to set the team profile avatar (via gravatar service)">
               Email
               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
       External group sync
     </h3>
     </h3>
-    <class_1
-      className="page-sub-heading-icon"
+    <Component
       content="Sync LDAP or OAuth groups with your Grafana teams."
       content="Sync LDAP or OAuth groups with your Grafana teams."
       placement="auto"
       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
     <div
       className="page-action-bar__spacer"
       className="page-action-bar__spacer"
     />
     />
@@ -116,15 +119,18 @@ exports[`Render should render groups table 1`] = `
     >
     >
       External group sync
       External group sync
     </h3>
     </h3>
-    <class_1
-      className="page-sub-heading-icon"
+    <Component
       content="Sync LDAP or OAuth groups with your Grafana teams."
       content="Sync LDAP or OAuth groups with your Grafana teams."
       placement="auto"
       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
     <div
       className="page-action-bar__spacer"
       className="page-action-bar__spacer"
     />
     />

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

@@ -151,32 +151,32 @@ export const aggOptions = [
   {
   {
     text: 'mean',
     text: 'mean',
     value: 'REDUCE_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],
     metricKinds: [MetricKind.GAUGE, MetricKind.DELTA],
   },
   },
   {
   {
     text: 'min',
     text: 'min',
     value: 'REDUCE_MIN',
     value: 'REDUCE_MIN',
     valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY],
     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',
     text: 'max',
     value: 'REDUCE_MAX',
     value: 'REDUCE_MAX',
     valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY],
     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',
     text: 'sum',
     value: 'REDUCE_SUM',
     value: 'REDUCE_SUM',
     valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION],
     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.',
     text: 'std. dev.',
     value: 'REDUCE_STDDEV',
     value: 'REDUCE_STDDEV',
     valueTypes: [ValueTypes.INT64, ValueTypes.DOUBLE, ValueTypes.MONEY, ValueTypes.DISTRIBUTION],
     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',
     text: 'count',

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

@@ -1,7 +1,7 @@
 import _ from 'lodash';
 import _ from 'lodash';
 import React, { PureComponent } from 'react';
 import React, { PureComponent } from 'react';
 import { TimeSeries } from 'app/core/core';
 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';
 import { LegendItem, LEGEND_STATS } from './LegendSeriesItem';
 
 
 interface LegendProps {
 interface LegendProps {

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

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

+ 0 - 1
public/sass/_grafana.scss

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

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

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

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

@@ -76,8 +76,7 @@ $textShadow: none;
 
 
 // gradients
 // 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%);
 $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%);
 $edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
 
 
 // Links
 // Links
@@ -102,6 +101,7 @@ $panel-bg: $white;
 $panel-border-color: $gray-5;
 $panel-border-color: $gray-5;
 $panel-border: solid 1px $panel-border-color;
 $panel-border: solid 1px $panel-border-color;
 $panel-header-hover-bg: $gray-6;
 $panel-header-hover-bg: $gray-6;
+$panel-corner: $gray-4;
 
 
 // Page header
 // Page header
 $page-header-bg: linear-gradient(90deg, $white, $gray-7);
 $page-header-bg: linear-gradient(90deg, $white, $gray-7);
@@ -307,12 +307,14 @@ $popover-error-bg: $btn-danger-bg;
 // Tooltips and popovers
 // Tooltips and popovers
 // -------------------------
 // -------------------------
 $tooltipColor: $popover-help-color;
 $tooltipColor: $popover-help-color;
-$tooltipBackground: $popover-help-bg;
 $tooltipArrowWidth: 5px;
 $tooltipArrowWidth: 5px;
-$tooltipArrowColor: $tooltipBackground;
 $tooltipLinkColor: lighten($popover-help-color, 5%);
 $tooltipLinkColor: lighten($popover-help-color, 5%);
 $graph-tooltip-bg: $gray-5;
 $graph-tooltip-bg: $gray-5;
 
 
+$tooltipBackground: $popover-help-bg;
+$tooltipArrowColor: $tooltipBackground; // Used by Angular tooltip
+$tooltipBackgroundError: $brand-danger;
+
 // images
 // images
 $checkboxImageUrl: '../img/checkbox_white.png';
 $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 {
 .scroll-margin-helper {
   margin-right: 12px;
   margin-right: 12px;
 }
 }

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

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

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

@@ -160,7 +160,7 @@
   }
   }
 
 
   .run-icon {
   .run-icon {
-    margin-left: 0.5em;
+    margin-left: 0.25em;
     transform: rotate(90deg);
     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
 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
 #!/bin/bash
 
 
-_version="1.0.0"
+_version="1.1.0"
 _tag="grafana/grafana-ci-deploy:${_version}"
 _tag="grafana/grafana-ci-deploy:${_version}"
 
 
 docker build -t $_tag .
 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-dom" "*"
     "@types/react-transition-group" "*"
     "@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"
   version "2.0.15"
   resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-2.0.15.tgz#e5ee3fe558832e141cc6041bdd54caea7b787af8"
   resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-2.0.15.tgz#e5ee3fe558832e141cc6041bdd54caea7b787af8"
   dependencies:
   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:
 caniuse-db@1.0.30000772, caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
   version "1.0.30000772"
   version "1.0.30000772"
   resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b"
   resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b"
+  integrity sha1-UarokXaChureSj2DGep21qAbUSs=
 
 
 caniuse-lite@^1.0.30000925:
 caniuse-lite@^1.0.30000925:
   version "1.0.30000926"
   version "1.0.30000926"
@@ -10561,7 +10569,7 @@ react-table@^6.8.6:
   dependencies:
   dependencies:
     classnames "^2.2.5"
     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"
   version "16.7.0"
   resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.7.0.tgz#1ca96c2b450ab47c36ba92cd8c03fcefc52ea01c"
   resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.7.0.tgz#1ca96c2b450ab47c36ba92cd8c03fcefc52ea01c"
   dependencies:
   dependencies:
@@ -11298,6 +11306,7 @@ sax@^1.2.4, sax@~1.2.1:
 scheduler@^0.12.0:
 scheduler@^0.12.0:
   version "0.12.0"
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.12.0.tgz#8ab17699939c0aedc5a196a657743c496538647b"
   resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.12.0.tgz#8ab17699939c0aedc5a196a657743c496538647b"
+  integrity sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==
   dependencies:
   dependencies:
     loose-envify "^1.1.0"
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
     object-assign "^4.1.1"