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/packages/grafana-ui/src/components/Tooltip/Popper.tsx b/packages/grafana-ui/src/components/Tooltip/Popper.tsx index dc89b801e37..fd12e7db517 100644 --- a/packages/grafana-ui/src/components/Tooltip/Popper.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Popper.tsx @@ -4,6 +4,11 @@ import { Manager, Popper as ReactPopper } from 'react-popper'; import Portal from 'app/core/components/Portal/Portal'; import Transition from 'react-transition-group/Transition'; +export enum Themes { + Default = 'popper__background--default', + Error = 'popper__background--error', +} + const defaultTransitionStyles = { transition: 'opacity 200ms linear', opacity: 0, @@ -23,13 +28,16 @@ interface Props extends React.DOMAttributes { content: string | ((props: any) => JSX.Element); refClassName?: string; referenceElement: PopperJS.ReferenceObject; + theme?: Themes; } class Popper extends PureComponent { render() { - const { renderContent, show, placement, onMouseEnter, onMouseLeave } = this.props; + const { renderContent, show, placement, onMouseEnter, onMouseLeave, theme } = this.props; const { content } = this.props; + const popperBackgroundClassName = 'popper__background' + (theme ? ' ' + theme : ''); + return ( @@ -50,7 +58,7 @@ class Popper extends PureComponent { 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 index 47b66053085..1b6703c3627 100644 --- a/packages/grafana-ui/src/components/Tooltip/PopperController.tsx +++ b/packages/grafana-ui/src/components/Tooltip/PopperController.tsx @@ -1,5 +1,6 @@ import React from 'react'; import * as PopperJS from 'popper.js'; +import { Themes } from './Popper'; type PopperContent = string | (() => JSX.Element); @@ -9,6 +10,7 @@ export interface UsingPopperProps { content: PopperContent; children: JSX.Element; renderContent?: (content: PopperContent) => JSX.Element; + theme?: Themes; } type PopperControllerRenderProp = ( @@ -19,6 +21,7 @@ type PopperControllerRenderProp = ( placement: PopperJS.Placement; content: string | ((props: any) => JSX.Element); renderContent: (content: any) => any; + theme?: Themes; } ) => JSX.Element; @@ -27,6 +30,7 @@ interface Props { content: PopperContent; className?: string; children: PopperControllerRenderProp; + theme?: Themes; } interface State { @@ -79,7 +83,7 @@ class PopperController extends React.Component { } render() { - const { children, content } = this.props; + const { children, content, theme } = this.props; const { show, placement } = this.state; return children(this.showPopper, this.hidePopper, { @@ -87,6 +91,7 @@ class PopperController extends React.Component { placement, content, renderContent: this.renderContent, + theme, }); } } diff --git a/packages/grafana-ui/src/components/Tooltip/_Tooltip.scss b/packages/grafana-ui/src/components/Tooltip/_Tooltip.scss index d869d52b92f..afa629d4043 100644 --- a/packages/grafana-ui/src/components/Tooltip/_Tooltip.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/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/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/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..fa21276d7a2 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -1,5 +1,7 @@ // Library import React, { Component } from 'react'; +import Tooltip from 'app/core/components/Tooltip/Tooltip'; +import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary'; // Services import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource_srv'; @@ -10,6 +12,9 @@ import kbn from 'app/core/utils/kbn'; // Types import { DataQueryOptions, DataQueryResponse } from 'app/types'; import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui'; +import { Themes } from 'app/core/components/Tooltip/Popper'; + +const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; interface RenderProps { loading: LoadingState; @@ -33,6 +38,7 @@ export interface Props { export interface State { isFirstLoad: boolean; loading: LoadingState; + errorMessage: string; response: DataQueryResponse; } @@ -51,6 +57,7 @@ export class DataPanel extends Component { this.state = { loading: LoadingState.NotStarted, + errorMessage: '', response: { data: [], }, @@ -90,7 +97,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,10 +135,20 @@ 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 + }); + } + } + render() { const { queries } = this.props; const { response, loading, isFirstLoad } = this.state; @@ -139,7 +156,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 +169,48 @@ 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/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 22921a4f98e..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'; 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/plugins/panel/graph2/GraphPanel.tsx b/public/app/plugins/panel/graph2/GraphPanel.tsx index a08276e5179..020c33f7d38 100644 --- a/public/app/plugins/panel/graph2/GraphPanel.tsx +++ b/public/app/plugins/panel/graph2/GraphPanel.tsx @@ -10,10 +10,6 @@ import { Options } from './types'; interface Props extends PanelProps {} export class GraphPanel extends PureComponent { - constructor(props) { - super(props); - } - render() { const { timeSeries, timeRange, width, height } = this.props; const { showLines, showBars, showPoints } = this.props.options; diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 70db51a0fb2..ded17e6ecfe 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -103,6 +103,7 @@ $panel-bg: #212124; $panel-border-color: $dark-1; $panel-border: solid 1px $panel-border-color; $panel-header-hover-bg: $dark-4; +$panel-corner: $panel-bg; // page header $page-header-bg: linear-gradient(90deg, #292a2d, black); @@ -302,12 +303,14 @@ $popover-error-bg: $btn-danger-bg; // Tooltips and popovers // ------------------------- $tooltipColor: $popover-help-color; -$tooltipBackground: $popover-help-bg; $tooltipArrowWidth: 5px; -$tooltipArrowColor: $tooltipBackground; $tooltipLinkColor: $link-color; $graph-tooltip-bg: $dark-1; +$tooltipBackground: $popover-help-bg; +$tooltipArrowColor: $tooltipBackground; +$tooltipBackgroundError: $brand-danger; + // images $checkboxImageUrl: '../img/checkbox.png'; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 6afd087a849..1e7a2e9cfbe 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -102,6 +102,7 @@ $panel-bg: $white; $panel-border-color: $gray-5; $panel-border: solid 1px $panel-border-color; $panel-header-hover-bg: $gray-6; +$panel-corner: $gray-4; // Page header $page-header-bg: linear-gradient(90deg, $white, $gray-7); @@ -307,12 +308,14 @@ $popover-error-bg: $btn-danger-bg; // Tooltips and popovers // ------------------------- $tooltipColor: $popover-help-color; -$tooltipBackground: $popover-help-bg; $tooltipArrowWidth: 5px; -$tooltipArrowColor: $tooltipBackground; $tooltipLinkColor: lighten($popover-help-color, 5%); $graph-tooltip-bg: $gray-5; +$tooltipBackground: $popover-help-bg; +$tooltipArrowColor: $tooltipBackground; // Used by Angular tooltip +$tooltipBackgroundError: $brand-danger; + // images $checkboxImageUrl: '../img/checkbox_white.png'; diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 589012bff3f..a0ff9fd877c 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -214,7 +214,7 @@ div.flot-text { &--info { display: block; - @include panel-corner-color(lighten($panel-bg, 4%)); + @include panel-corner-color(lighten($panel-corner, 4%)); .fa:before { content: '\f129'; } @@ -222,7 +222,7 @@ div.flot-text { &--links { display: block; - @include panel-corner-color(lighten($panel-bg, 4%)); + @include panel-corner-color(lighten($panel-corner, 4%)); .fa { left: 4px; } @@ -233,7 +233,7 @@ div.flot-text { &--error { display: block; - color: $text-color; + color: $white; @include panel-corner-color($popover-error-bg); .fa:before { content: '\f12a';