Commit 2bc26a01 by Torkel Ödegaard

Fixes to error handling and clearing, also publishing of legacy events so old…

Fixes to error handling and clearing, also publishing of legacy events so old query editors work with react panels fully
parent cc61d9c5
...@@ -29,6 +29,16 @@ export interface DataQuery { ...@@ -29,6 +29,16 @@ export interface DataQuery {
datasource?: string | null; datasource?: string | null;
} }
export interface DataQueryError {
data?: {
message?: string;
error?: string;
};
message?: string;
status?: string;
statusText?: string;
}
export interface DataQueryOptions<TQuery extends DataQuery = DataQuery> { export interface DataQueryOptions<TQuery extends DataQuery = DataQuery> {
timezone: string; timezone: string;
range: TimeRange; range: TimeRange;
......
...@@ -27,7 +27,9 @@ export class AngularLoader { ...@@ -27,7 +27,9 @@ export class AngularLoader {
compiledElem.remove(); compiledElem.remove();
}, },
digest: () => { digest: () => {
scope.$digest(); if (!scope.$$phase) {
scope.$digest();
}
}, },
getScope: () => { getScope: () => {
return scope; return scope;
......
...@@ -9,6 +9,7 @@ import kbn from 'app/core/utils/kbn'; ...@@ -9,6 +9,7 @@ import kbn from 'app/core/utils/kbn';
import { import {
DataQueryOptions, DataQueryOptions,
DataQueryResponse, DataQueryResponse,
DataQueryError,
LoadingState, LoadingState,
PanelData, PanelData,
TableData, TableData,
...@@ -34,7 +35,7 @@ export interface Props { ...@@ -34,7 +35,7 @@ export interface Props {
maxDataPoints?: number; maxDataPoints?: number;
children: (r: RenderProps) => JSX.Element; children: (r: RenderProps) => JSX.Element;
onDataResponse?: (data: DataQueryResponse) => void; onDataResponse?: (data: DataQueryResponse) => void;
onError: (errorMessage: string) => void; onError: (message: string, error: DataQueryError) => void;
} }
export interface State { export interface State {
...@@ -146,11 +147,20 @@ export class DataPanel extends Component<Props, State> { ...@@ -146,11 +147,20 @@ export class DataPanel extends Component<Props, State> {
isFirstLoad: false, isFirstLoad: false,
}); });
} catch (err) { } catch (err) {
console.log('Loading error', err); console.log('DataPanel error', err);
let message = 'Query error';
if (err.message) {
message = err.message;
} else if (err.data && err.data.message) {
message = err.data.message;
} else if (err.data && err.data.error) {
message = err.data.error;
}
onError(message, err);
this.setState({ isFirstLoad: false }); this.setState({ isFirstLoad: false });
onError(`Query error
status: ${err.status}
message: ${err.statusText}`);
} }
}; };
......
...@@ -18,7 +18,7 @@ import { profiler } from 'app/core/profiler'; ...@@ -18,7 +18,7 @@ import { profiler } from 'app/core/profiler';
// Types // Types
import { DashboardModel, PanelModel } from '../state'; import { DashboardModel, PanelModel } from '../state';
import { PanelPlugin } from 'app/types'; import { PanelPlugin } from 'app/types';
import { DataQueryResponse, TimeRange, LoadingState, PanelData } from '@grafana/ui'; import { DataQueryResponse, TimeRange, LoadingState, PanelData, DataQueryError } from '@grafana/ui';
import variables from 'sass/_variables.scss'; import variables from 'sass/_variables.scss';
import templateSrv from 'app/features/templating/template_srv'; import templateSrv from 'app/features/templating/template_srv';
...@@ -36,8 +36,7 @@ export interface State { ...@@ -36,8 +36,7 @@ export interface State {
renderCounter: number; renderCounter: number;
timeInfo?: string; timeInfo?: string;
timeRange?: TimeRange; timeRange?: TimeRange;
loading: LoadingState; errorMessage: string | null;
errorMessage: string;
} }
export class PanelChrome extends PureComponent<Props, State> { export class PanelChrome extends PureComponent<Props, State> {
...@@ -49,8 +48,7 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -49,8 +48,7 @@ export class PanelChrome extends PureComponent<Props, State> {
this.state = { this.state = {
refreshCounter: 0, refreshCounter: 0,
renderCounter: 0, renderCounter: 0,
loading: LoadingState.NotStarted, errorMessage: null,
errorMessage: '',
}; };
} }
...@@ -94,8 +92,33 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -94,8 +92,33 @@ export class PanelChrome extends PureComponent<Props, State> {
if (this.props.dashboard.isSnapshot()) { if (this.props.dashboard.isSnapshot()) {
this.props.panel.snapshotData = dataQueryResponse.data; this.props.panel.snapshotData = dataQueryResponse.data;
} }
// clear error state (if any)
this.clearErrorState();
// This event is used by old query editors and panel editor options
this.props.panel.events.emit('data-received', dataQueryResponse.data);
};
onDataError = (message: string, error: DataQueryError) => {
if (this.state.errorMessage !== message) {
this.setState({ errorMessage: message });
}
// this event is used by old query editors
this.props.panel.events.emit('data-error', error);
};
onPanelError = (message: string) => {
if (this.state.errorMessage !== message) {
this.setState({ errorMessage: message });
}
}; };
clearErrorState() {
if (this.state.errorMessage) {
this.setState({ errorMessage: null });
}
}
get isVisible() { get isVisible() {
return !this.props.dashboard.otherPanelInFullscreen(this.props.panel); return !this.props.dashboard.otherPanelInFullscreen(this.props.panel);
} }
...@@ -113,15 +136,6 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -113,15 +136,6 @@ export class PanelChrome extends PureComponent<Props, State> {
return this.hasPanelSnapshot ? snapshotDataToPanelData(this.props.panel) : null; return this.hasPanelSnapshot ? snapshotDataToPanelData(this.props.panel) : null;
} }
onError = (errorMessage: string) => {
if (this.state.loading !== LoadingState.Error || this.state.errorMessage !== errorMessage) {
this.setState({
loading: LoadingState.Error,
errorMessage: errorMessage,
});
}
};
renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element { renderPanelPlugin(loading: LoadingState, panelData: PanelData, width: number, height: number): JSX.Element {
const { panel, plugin } = this.props; const { panel, plugin } = this.props;
const { timeRange, renderCounter } = this.state; const { timeRange, renderCounter } = this.state;
...@@ -165,7 +179,7 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -165,7 +179,7 @@ export class PanelChrome extends PureComponent<Props, State> {
widthPixels={width} widthPixels={width}
refreshCounter={refreshCounter} refreshCounter={refreshCounter}
onDataResponse={this.onDataResponse} onDataResponse={this.onDataResponse}
onError={this.onError} onError={this.onDataError}
> >
{({ loading, panelData }) => { {({ loading, panelData }) => {
return this.renderPanelPlugin(loading, panelData, width, height); return this.renderPanelPlugin(loading, panelData, width, height);
...@@ -206,7 +220,7 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -206,7 +220,7 @@ export class PanelChrome extends PureComponent<Props, State> {
<ErrorBoundary> <ErrorBoundary>
{({ error, errorInfo }) => { {({ error, errorInfo }) => {
if (errorInfo) { if (errorInfo) {
this.onError(error.message || DEFAULT_PLUGIN_ERROR); this.onPanelError(error.message || DEFAULT_PLUGIN_ERROR);
return null; return null;
} }
return this.renderPanelBody(width, height); return this.renderPanelBody(width, height);
......
...@@ -6,7 +6,7 @@ import templateSrv from 'app/features/templating/template_srv'; ...@@ -6,7 +6,7 @@ import templateSrv from 'app/features/templating/template_srv';
import { LinkSrv } from 'app/features/panel/panellinks/link_srv'; import { LinkSrv } from 'app/features/panel/panellinks/link_srv';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
enum InfoModes { enum InfoMode {
Error = 'Error', Error = 'Error',
Info = 'Info', Info = 'Info',
Links = 'Links', Links = 'Links',
...@@ -27,13 +27,13 @@ export class PanelHeaderCorner extends Component<Props> { ...@@ -27,13 +27,13 @@ export class PanelHeaderCorner extends Component<Props> {
getInfoMode = () => { getInfoMode = () => {
const { panel, error } = this.props; const { panel, error } = this.props;
if (error) { if (error) {
return InfoModes.Error; return InfoMode.Error;
} }
if (!!panel.description) { if (!!panel.description) {
return InfoModes.Info; return InfoMode.Info;
} }
if (panel.links && panel.links.length) { if (panel.links && panel.links.length) {
return InfoModes.Links; return InfoMode.Links;
} }
return undefined; return undefined;
...@@ -68,9 +68,10 @@ export class PanelHeaderCorner extends Component<Props> { ...@@ -68,9 +68,10 @@ export class PanelHeaderCorner extends Component<Props> {
); );
}; };
renderCornerType(infoMode: InfoModes, content: string | JSX.Element) { renderCornerType(infoMode: InfoMode, content: string | JSX.Element) {
const theme = infoMode === InfoMode.Error ? 'error' : 'info';
return ( return (
<Tooltip content={content} placement="bottom-start"> <Tooltip content={content} placement="bottom-start" theme={theme}>
<div className={`panel-info-corner panel-info-corner--${infoMode.toLowerCase()}`}> <div className={`panel-info-corner panel-info-corner--${infoMode.toLowerCase()}`}>
<i className="fa" /> <i className="fa" />
<span className="panel-info-corner-inner" /> <span className="panel-info-corner-inner" />
...@@ -80,17 +81,17 @@ export class PanelHeaderCorner extends Component<Props> { ...@@ -80,17 +81,17 @@ export class PanelHeaderCorner extends Component<Props> {
} }
render() { render() {
const infoMode: InfoModes | undefined = this.getInfoMode(); const infoMode: InfoMode | undefined = this.getInfoMode();
if (!infoMode) { if (!infoMode) {
return null; return null;
} }
if (infoMode === InfoModes.Error) { if (infoMode === InfoMode.Error) {
return this.renderCornerType(infoMode, this.props.error); return this.renderCornerType(infoMode, this.props.error);
} }
if (infoMode === InfoModes.Info) { if (infoMode === InfoMode.Info) {
return this.renderCornerType(infoMode, this.getInfoContent()); return this.renderCornerType(infoMode, this.getInfoContent());
} }
......
...@@ -28,28 +28,57 @@ interface State { ...@@ -28,28 +28,57 @@ interface State {
loadedDataSourceValue: string | null | undefined; loadedDataSourceValue: string | null | undefined;
datasource: DataSourceApi | null; datasource: DataSourceApi | null;
isCollapsed: boolean; isCollapsed: boolean;
angularScope: AngularQueryComponentScope | null; hasTextEditMode: boolean;
} }
export class QueryEditorRow extends PureComponent<Props, State> { export class QueryEditorRow extends PureComponent<Props, State> {
element: HTMLElement | null = null; element: HTMLElement | null = null;
angularScope: AngularQueryComponentScope | null;
angularQueryEditor: AngularComponent | null = null; angularQueryEditor: AngularComponent | null = null;
state: State = { state: State = {
datasource: null, datasource: null,
isCollapsed: false, isCollapsed: false,
angularScope: null,
loadedDataSourceValue: undefined, loadedDataSourceValue: undefined,
hasTextEditMode: false,
}; };
componentDidMount() { componentDidMount() {
this.loadDatasource(); this.loadDatasource();
this.props.panel.events.on('refresh', this.onPanelRefresh); this.props.panel.events.on('refresh', this.onPanelRefresh);
this.props.panel.events.on('data-error', this.onPanelDataError);
this.props.panel.events.on('data-received', this.onPanelDataReceived);
}
componentWillUnmount() {
this.props.panel.events.off('refresh', this.onPanelRefresh);
this.props.panel.events.off('data-error', this.onPanelDataError);
this.props.panel.events.off('data-received', this.onPanelDataReceived);
if (this.angularQueryEditor) {
this.angularQueryEditor.destroy();
}
} }
onPanelDataError = () => {
// Some query controllers listen to data error events and need a digest
if (this.angularQueryEditor) {
// for some reason this needs to be done in next tick
setTimeout(this.angularQueryEditor.digest);
}
};
onPanelDataReceived = () => {
// Some query controllers listen to data error events and need a digest
if (this.angularQueryEditor) {
// for some reason this needs to be done in next tick
setTimeout(this.angularQueryEditor.digest);
}
};
onPanelRefresh = () => { onPanelRefresh = () => {
if (this.state.angularScope) { if (this.angularScope) {
this.state.angularScope.range = getTimeSrv().timeRange(); this.angularScope.range = getTimeSrv().timeRange();
} }
}; };
...@@ -73,7 +102,11 @@ export class QueryEditorRow extends PureComponent<Props, State> { ...@@ -73,7 +102,11 @@ export class QueryEditorRow extends PureComponent<Props, State> {
const dataSourceSrv = getDatasourceSrv(); const dataSourceSrv = getDatasourceSrv();
const datasource = await dataSourceSrv.get(query.datasource || panel.datasource); const datasource = await dataSourceSrv.get(query.datasource || panel.datasource);
this.setState({ datasource, loadedDataSourceValue: this.props.dataSourceValue }); this.setState({
datasource,
loadedDataSourceValue: this.props.dataSourceValue,
hasTextEditMode: false,
});
} }
componentDidUpdate() { componentDidUpdate() {
...@@ -98,21 +131,14 @@ export class QueryEditorRow extends PureComponent<Props, State> { ...@@ -98,21 +131,14 @@ export class QueryEditorRow extends PureComponent<Props, State> {
const scopeProps = { ctrl: this.getAngularQueryComponentScope() }; const scopeProps = { ctrl: this.getAngularQueryComponentScope() };
this.angularQueryEditor = loader.load(this.element, scopeProps, template); this.angularQueryEditor = loader.load(this.element, scopeProps, template);
this.angularScope = scopeProps.ctrl;
// give angular time to compile // give angular time to compile
setTimeout(() => { setTimeout(() => {
this.setState({ angularScope: scopeProps.ctrl }); this.setState({ hasTextEditMode: !!this.angularScope.toggleEditorMode });
}, 10); }, 10);
} }
componentWillUnmount() {
this.props.panel.events.off('refresh', this.onPanelRefresh);
if (this.angularQueryEditor) {
this.angularQueryEditor.destroy();
}
}
onToggleCollapse = () => { onToggleCollapse = () => {
this.setState({ isCollapsed: !this.state.isCollapsed }); this.setState({ isCollapsed: !this.state.isCollapsed });
}; };
...@@ -138,10 +164,8 @@ export class QueryEditorRow extends PureComponent<Props, State> { ...@@ -138,10 +164,8 @@ export class QueryEditorRow extends PureComponent<Props, State> {
} }
onToggleEditMode = () => { onToggleEditMode = () => {
const { angularScope } = this.state; if (this.angularScope && this.angularScope.toggleEditorMode) {
this.angularScope.toggleEditorMode();
if (angularScope && angularScope.toggleEditorMode) {
angularScope.toggleEditorMode();
this.angularQueryEditor.digest(); this.angularQueryEditor.digest();
} }
...@@ -150,11 +174,6 @@ export class QueryEditorRow extends PureComponent<Props, State> { ...@@ -150,11 +174,6 @@ export class QueryEditorRow extends PureComponent<Props, State> {
} }
}; };
get hasTextEditMode() {
const { angularScope } = this.state;
return angularScope && angularScope.toggleEditorMode;
}
onRemoveQuery = () => { onRemoveQuery = () => {
this.props.onRemoveQuery(this.props.query); this.props.onRemoveQuery(this.props.query);
}; };
...@@ -171,10 +190,8 @@ export class QueryEditorRow extends PureComponent<Props, State> { ...@@ -171,10 +190,8 @@ export class QueryEditorRow extends PureComponent<Props, State> {
}; };
renderCollapsedText(): string | null { renderCollapsedText(): string | null {
const { angularScope } = this.state; if (this.angularScope && this.angularScope.getCollapsedText) {
return this.angularScope.getCollapsedText();
if (angularScope && angularScope.getCollapsedText) {
return angularScope.getCollapsedText();
} }
return null; return null;
...@@ -182,7 +199,7 @@ export class QueryEditorRow extends PureComponent<Props, State> { ...@@ -182,7 +199,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
render() { render() {
const { query, inMixedMode } = this.props; const { query, inMixedMode } = this.props;
const { datasource, isCollapsed } = this.state; const { datasource, isCollapsed, hasTextEditMode } = this.state;
const isDisabled = query.hide; const isDisabled = query.hide;
const bodyClasses = classNames('query-editor-row__body gf-form-query', { const bodyClasses = classNames('query-editor-row__body gf-form-query', {
...@@ -212,7 +229,7 @@ export class QueryEditorRow extends PureComponent<Props, State> { ...@@ -212,7 +229,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
{isCollapsed && <div>{this.renderCollapsedText()}</div>} {isCollapsed && <div>{this.renderCollapsedText()}</div>}
</div> </div>
<div className="query-editor-row__actions"> <div className="query-editor-row__actions">
{this.hasTextEditMode && ( {hasTextEditMode && (
<button <button
className="query-editor-row__action" className="query-editor-row__action"
onClick={this.onToggleEditMode} onClick={this.onToggleEditMode}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment