diff --git a/.circleci/config.yml b/.circleci/config.yml index dba6c5f8bd0..236d5aec398 100644 --- a/.circleci/config.yml +++ b/.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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 867253ccd18..671740f7225 100644 --- a/CHANGELOG.md +++ b/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) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 28b975e31e8..d26af5277a1 100644 --- a/docs/sources/installation/debian.md +++ b/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 diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index 559bb0a8ef4..156f703e164 100644 --- a/docs/sources/installation/rpm.md +++ b/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 diff --git a/package.json b/package.json index eefe2cbbe53..c8d891b91bc 100644 --- a/package.json +++ b/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", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 2fb210e3b46..6548f75e91f 100644 --- a/packages/grafana-ui/package.json +++ b/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" } } diff --git a/public/app/core/components/CustomScrollbar/CustomScrollbar.test.tsx b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.test.tsx similarity index 100% rename from public/app/core/components/CustomScrollbar/CustomScrollbar.test.tsx rename to packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.test.tsx diff --git a/public/app/core/components/CustomScrollbar/CustomScrollbar.tsx b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx similarity index 96% rename from public/app/core/components/CustomScrollbar/CustomScrollbar.tsx rename to packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx index 977892c637d..cf1657e1c83 100644 --- a/public/app/core/components/CustomScrollbar/CustomScrollbar.tsx +++ b/packages/grafana-ui/src/components/CustomScrollbar/CustomScrollbar.tsx @@ -12,7 +12,7 @@ interface Props { /** * Wraps component into component from `react-custom-scrollbars` */ -class CustomScrollbar extends PureComponent { +export class CustomScrollbar extends PureComponent { static defaultProps: Partial = { customClassName: 'custom-scrollbars', autoHide: true, diff --git a/packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss b/packages/grafana-ui/src/components/CustomScrollbar/_CustomScrollbar.scss new file mode 100644 index 00000000000..c0a8077fb63 --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/public/app/core/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap b/packages/grafana-ui/src/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap similarity index 100% rename from public/app/core/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap rename to packages/grafana-ui/src/components/CustomScrollbar/__snapshots__/CustomScrollbar.test.tsx.snap diff --git a/public/app/core/components/Portal/Portal.tsx b/packages/grafana-ui/src/components/Portal/Portal.tsx similarity index 87% rename from public/app/core/components/Portal/Portal.tsx rename to packages/grafana-ui/src/components/Portal/Portal.tsx index 5ea5fb94ebf..bc6fa37f2d0 100644 --- a/public/app/core/components/Portal/Portal.tsx +++ b/packages/grafana-ui/src/components/Portal/Portal.tsx @@ -6,11 +6,11 @@ interface Props { root?: HTMLElement; } -export default class BodyPortal extends PureComponent { +export class Portal extends PureComponent { node: HTMLElement = document.createElement('div'); portalRoot: HTMLElement; - constructor(props) { + constructor(props: Props) { super(props); const { className, root = document.body } = this.props; diff --git a/public/app/core/components/Tooltip/Popper.tsx b/packages/grafana-ui/src/components/Tooltip/Popper.tsx similarity index 62% rename from public/app/core/components/Tooltip/Popper.tsx rename to packages/grafana-ui/src/components/Tooltip/Popper.tsx index 36cf0fe837e..f6f9fa6f73a 100644 --- a/public/app/core/components/Tooltip/Popper.tsx +++ b/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 { 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 { 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 ( - - {({ ref }) => ( -
- {children} -
- )} -
{transitionState => ( - + {({ ref, style, placement, arrowProps }) => { return (
{ data-placement={placement} className="popper" > -
+
{renderContent(content)}
diff --git a/packages/grafana-ui/src/components/Tooltip/PopperController.tsx b/packages/grafana-ui/src/components/Tooltip/PopperController.tsx new file mode 100644 index 00000000000..5f4010ac58a --- /dev/null +++ b/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 { + 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 ; + } + 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; diff --git a/public/app/core/components/Tooltip/Tooltip.test.tsx b/packages/grafana-ui/src/components/Tooltip/Tooltip.test.tsx similarity index 56% rename from public/app/core/components/Tooltip/Tooltip.test.tsx rename to packages/grafana-ui/src/components/Tooltip/Tooltip.test.tsx index d2c96bb23d2..95d01c7f2fe 100644 --- a/public/app/core/components/Tooltip/Tooltip.test.tsx +++ b/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( - - Link with tooltip + + + Link with tooltip + ) .toJSON(); diff --git a/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx b/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx new file mode 100644 index 00000000000..9cffb151d83 --- /dev/null +++ b/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(); + + return ( + + {(showPopper, hidePopper, popperProps) => { + return ( + <> + {tooltipTriggerRef.current && ( + + )} + {React.cloneElement(children, { + ref: tooltipTriggerRef, + onMouseEnter: showPopper, + onMouseLeave: hidePopper, + })} + + ); + }} + + ); +}; diff --git a/public/sass/components/_popper.scss b/packages/grafana-ui/src/components/Tooltip/_Tooltip.scss similarity index 92% rename from public/sass/components/_popper.scss rename to packages/grafana-ui/src/components/Tooltip/_Tooltip.scss index d869d52b92f..afa629d4043 100644 --- a/public/sass/components/_popper.scss +++ b/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; diff --git a/packages/grafana-ui/src/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap b/packages/grafana-ui/src/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap new file mode 100644 index 00000000000..761221906d4 --- /dev/null +++ b/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`] = ` + + Link with tooltip + +`; diff --git a/packages/grafana-ui/src/components/index.scss b/packages/grafana-ui/src/components/index.scss index d52508c946c..e1d1474bb16 100644 --- a/packages/grafana-ui/src/components/index.scss +++ b/packages/grafana-ui/src/components/index.scss @@ -1 +1,3 @@ +@import 'CustomScrollbar/CustomScrollbar'; @import 'DeleteButton/DeleteButton'; +@import 'Tooltip/Tooltip'; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index b57b9bcfdb7..abb1cf1b34c 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/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'; diff --git a/packages/grafana-ui/src/visualizations/Graph/Graph.tsx b/packages/grafana-ui/src/visualizations/Graph/Graph.tsx index 51afb33802d..ad038cebcda 100644 --- a/packages/grafana-ui/src/visualizations/Graph/Graph.tsx +++ b/packages/grafana-ui/src/visualizations/Graph/Graph.tsx @@ -98,6 +98,7 @@ export class Graph extends PureComponent { $.plot(this.element, timeSeries, flotOptions); } catch (err) { console.log('Graph rendering error', err, flotOptions, timeSeries); + throw new Error('Error rendering panel'); } } diff --git a/pkg/tsdb/cloudwatch/credentials.go b/pkg/tsdb/cloudwatch/credentials.go index 165f8fdbe97..fb92c827f7c 100644 --- a/pkg/tsdb/cloudwatch/credentials.go +++ b/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"), diff --git a/public/app/core/components/ErrorBoundary/ErrorBoundary.tsx b/public/app/core/components/ErrorBoundary/ErrorBoundary.tsx new file mode 100644 index 00000000000..188750b0fef --- /dev/null +++ b/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 { + 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; diff --git a/public/app/core/components/Label/Label.tsx b/public/app/core/components/Label/Label.tsx index 362c3c577f7..5d60efa056a 100644 --- a/public/app/core/components/Label/Label.tsx +++ b/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.children} {props.tooltip && ( - - + +
+ +
)}
diff --git a/public/app/core/components/Select/Select.tsx b/public/app/core/components/Select/Select.tsx index 893eb1a6655..f66e07c9ed6 100644 --- a/public/app/core/components/Select/Select.tsx +++ b/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; diff --git a/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx b/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx index 2524a265054..86e15923bda 100644 --- a/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx +++ b/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; diff --git a/public/app/core/components/Tooltip/Popover.test.tsx b/public/app/core/components/Tooltip/Popover.test.tsx deleted file mode 100644 index eea416a9262..00000000000 --- a/public/app/core/components/Tooltip/Popover.test.tsx +++ /dev/null @@ -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( - - - - ) - .toJSON(); - expect(tree).toMatchSnapshot(); - }); -}); diff --git a/public/app/core/components/Tooltip/Popover.tsx b/public/app/core/components/Tooltip/Popover.tsx deleted file mode 100644 index 62397243c1c..00000000000 --- a/public/app/core/components/Tooltip/Popover.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React, { PureComponent } from 'react'; -import Popper from './Popper'; -import withPopper, { UsingPopperProps } from './withPopper'; - -class Popover extends PureComponent { - render() { - const { children, hidePopper, showPopper, className, ...restProps } = this.props; - - const togglePopper = restProps.show ? hidePopper : showPopper; - - return ( -
- {children} -
- ); - } -} - -export default withPopper(Popover); diff --git a/public/app/core/components/Tooltip/Tooltip.tsx b/public/app/core/components/Tooltip/Tooltip.tsx deleted file mode 100644 index 795da94a03c..00000000000 --- a/public/app/core/components/Tooltip/Tooltip.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { PureComponent } from 'react'; -import Popper from './Popper'; -import withPopper, { UsingPopperProps } from './withPopper'; - -class Tooltip extends PureComponent { - render() { - const { children, hidePopper, showPopper, className, ...restProps } = this.props; - - return ( -
- {children} -
- ); - } -} - -export default withPopper(Tooltip); diff --git a/public/app/core/components/Tooltip/__snapshots__/Popover.test.tsx.snap b/public/app/core/components/Tooltip/__snapshots__/Popover.test.tsx.snap deleted file mode 100644 index b36a4fe9af9..00000000000 --- a/public/app/core/components/Tooltip/__snapshots__/Popover.test.tsx.snap +++ /dev/null @@ -1,16 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Popover renders correctly 1`] = ` -
-
- -
-
-`; diff --git a/public/app/core/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap b/public/app/core/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap deleted file mode 100644 index c7d680049f4..00000000000 --- a/public/app/core/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Tooltip renders correctly 1`] = ` - -`; diff --git a/public/app/core/components/Tooltip/withPopper.tsx b/public/app/core/components/Tooltip/withPopper.tsx deleted file mode 100644 index 4ba05937531..00000000000 --- a/public/app/core/components/Tooltip/withPopper.tsx +++ /dev/null @@ -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 { - 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 ; - } - return content; - } - - render() { - const { show, placement } = this.state; - const className = this.props.className || ''; - - return ( - - ); - } - }; -} diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/alerting/AlertTab.tsx similarity index 94% rename from public/app/features/dashboard/dashgrid/AlertTab.tsx rename to public/app/features/alerting/AlertTab.tsx index 20f7e90633e..a5afbc198fc 100644 --- a/public/app/features/dashboard/dashgrid/AlertTab.tsx +++ b/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; diff --git a/public/app/features/dashboard/dashgrid/StateHistory.tsx b/public/app/features/alerting/StateHistory.tsx similarity index 95% rename from public/app/features/dashboard/dashgrid/StateHistory.tsx rename to public/app/features/alerting/StateHistory.tsx index 99229b41848..eb5541f6094 100644 --- a/public/app/features/dashboard/dashgrid/StateHistory.tsx +++ b/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; diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 30a939b50aa..d71a274ab10 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/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 { this.state = { loading: LoadingState.NotStarted, + errorMessage: '', response: { data: [], }, @@ -90,7 +98,7 @@ export class DataPanel extends Component { 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 { }); } 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 { 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 { return ( <> - {this.renderLoadingSpinner()} - {this.props.children({ - timeSeries, - loading, - })} + {this.renderLoadingStates()} + + {({ error, errorInfo }) => { + if (errorInfo) { + this.onError(error.message || DEFAULT_PLUGIN_ERROR); + return null; + } + return ( + <> + {this.props.children({ + timeSeries, + loading, + })} + + ); + }} + ); } - private renderLoadingSpinner(): JSX.Element { - const { loading } = this.state; - + private renderLoadingStates(): JSX.Element { + const { loading, errorMessage } = this.state; if (loading === LoadingState.Loading) { return (
); + } else if (loading === LoadingState.Error) { + return ( + +
+ + +
+
+ ); } return null; diff --git a/public/app/features/dashboard/dashgrid/DataSourceOption.tsx b/public/app/features/dashboard/dashgrid/DataSourceOption.tsx index 0adfc4abe16..9a3ce527510 100644 --- a/public/app/features/dashboard/dashgrid/DataSourceOption.tsx +++ b/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; diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index b7da81a23f8..b159cb30a4b 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/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'; diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 94719dfe6e0..84e11511453 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -87,7 +87,6 @@ export class PanelChrome extends PureComponent { const { datasource, targets, transparent } = panel; const PanelComponent = plugin.exports.Panel; const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`; - return ( {({ width, height }) => { diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index fbc683c2eb3..123204aa239 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/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 (
onClick(tab)}> - + diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index 331e469a60d..6b6f81fc579 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/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 { {infoMode === InfoModes.Info || infoMode === InfoModes.Links ? ( - - +
+ + +
) : null} diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 741e2cd9ac1..77ab64b1dba 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -50,17 +50,21 @@ export class QueriesTab extends PureComponent { 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; diff --git a/public/app/features/dashboard/permissions/DashboardPermissions.tsx b/public/app/features/dashboard/permissions/DashboardPermissions.tsx index c07bef42930..96d0e23adcd 100644 --- a/public/app/features/dashboard/permissions/DashboardPermissions.tsx +++ b/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 {

Permissions

- - + +
+ +
diff --git a/public/app/features/folders/FolderPermissions.tsx b/public/app/features/folders/FolderPermissions.tsx index 176e270038b..62a479b9124 100644 --- a/public/app/features/folders/FolderPermissions.tsx +++ b/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 {

Folder Permissions

- - + +
+ +