Commit 7fd89ff7 by Torkel Ödegaard Committed by GitHub

Merge pull request #15424 from grafana/move-error-boundry

Move error boundry from DataPanel to PanelChrome
parents 2c923659 19a080a4
...@@ -5,9 +5,7 @@ export interface Props { ...@@ -5,9 +5,7 @@ export interface Props {
} }
const EmptySearchResult: FC<Props> = ({ children }) => { const EmptySearchResult: FC<Props> = ({ children }) => {
return ( return <div className="empty-search-result">{children}</div>;
<div className="empty-search-result">{children}</div>
);
}; };
export { EmptySearchResult }; export { EmptySearchResult };
...@@ -35,8 +35,16 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> { ...@@ -35,8 +35,16 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
class Popper extends PureComponent<Props> { class Popper extends PureComponent<Props> {
render() { render() {
const { show, placement, onMouseEnter, onMouseLeave, className, wrapperClassName, renderArrow } = this.props; const {
const { content } = this.props; content,
show,
placement,
onMouseEnter,
onMouseLeave,
className,
wrapperClassName,
renderArrow,
} = this.props;
return ( return (
<Manager> <Manager>
...@@ -50,7 +58,7 @@ class Popper extends PureComponent<Props> { ...@@ -50,7 +58,7 @@ class Popper extends PureComponent<Props> {
// TODO: move modifiers config to popper controller // TODO: move modifiers config to popper controller
modifiers={{ preventOverflow: { enabled: true, boundariesElement: 'window' } }} modifiers={{ preventOverflow: { enabled: true, boundariesElement: 'window' } }}
> >
{({ ref, style, placement, arrowProps, scheduleUpdate }) => { {({ ref, style, placement, arrowProps }) => {
return ( return (
<div <div
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
...@@ -65,11 +73,7 @@ class Popper extends PureComponent<Props> { ...@@ -65,11 +73,7 @@ class Popper extends PureComponent<Props> {
className={`${wrapperClassName}`} className={`${wrapperClassName}`}
> >
<div className={className}> <div className={className}>
{typeof content === 'string' {typeof content === 'string' ? content : React.cloneElement(content)}
? content
: React.cloneElement(content, {
updatePopperPosition: scheduleUpdate,
})}
{renderArrow && {renderArrow &&
renderArrow({ renderArrow({
arrowProps, arrowProps,
......
...@@ -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: () => {
if (!scope.$$phase) {
scope.$digest(); scope.$digest();
}
}, },
getScope: () => { getScope: () => {
return scope; return scope;
......
// Library // Library
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Tooltip } from '@grafana/ui';
import ErrorBoundary from 'app/core/components/ErrorBoundary/ErrorBoundary';
// Services // Services
import { DatasourceSrv, getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { DatasourceSrv, getDatasourceSrv } from 'app/features/plugins/datasource_srv';
// Utils // Utils
...@@ -11,6 +9,7 @@ import kbn from 'app/core/utils/kbn'; ...@@ -11,6 +9,7 @@ import kbn from 'app/core/utils/kbn';
import { import {
DataQueryOptions, DataQueryOptions,
DataQueryResponse, DataQueryResponse,
DataQueryError,
LoadingState, LoadingState,
PanelData, PanelData,
TableData, TableData,
...@@ -18,8 +17,6 @@ import { ...@@ -18,8 +17,6 @@ import {
TimeSeries, TimeSeries,
} from '@grafana/ui'; } from '@grafana/ui';
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
interface RenderProps { interface RenderProps {
loading: LoadingState; loading: LoadingState;
panelData: PanelData; panelData: PanelData;
...@@ -38,12 +35,12 @@ export interface Props { ...@@ -38,12 +35,12 @@ 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: (message: string, error: DataQueryError) => void;
} }
export interface State { export interface State {
isFirstLoad: boolean; isFirstLoad: boolean;
loading: LoadingState; loading: LoadingState;
errorMessage: string;
response: DataQueryResponse; response: DataQueryResponse;
} }
...@@ -61,7 +58,6 @@ export class DataPanel extends Component<Props, State> { ...@@ -61,7 +58,6 @@ export class DataPanel extends Component<Props, State> {
this.state = { this.state = {
loading: LoadingState.NotStarted, loading: LoadingState.NotStarted,
errorMessage: '',
response: { response: {
data: [], data: [],
}, },
...@@ -100,6 +96,7 @@ export class DataPanel extends Component<Props, State> { ...@@ -100,6 +96,7 @@ export class DataPanel extends Component<Props, State> {
widthPixels, widthPixels,
maxDataPoints, maxDataPoints,
onDataResponse, onDataResponse,
onError,
} = this.props; } = this.props;
if (!isVisible) { if (!isVisible) {
...@@ -111,7 +108,7 @@ export class DataPanel extends Component<Props, State> { ...@@ -111,7 +108,7 @@ export class DataPanel extends Component<Props, State> {
return; return;
} }
this.setState({ loading: LoadingState.Loading, errorMessage: '' }); this.setState({ loading: LoadingState.Loading });
try { try {
const ds = await this.dataSourceSrv.get(datasource); const ds = await this.dataSourceSrv.get(datasource);
...@@ -150,18 +147,22 @@ export class DataPanel extends Component<Props, State> { ...@@ -150,18 +147,22 @@ 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);
this.onError('Request Error');
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;
} else if (err.status) {
message = `Query error: ${err.status} ${err.statusText}`;
} }
};
onError = (errorMessage: string) => { onError(message, err);
if (this.state.loading !== LoadingState.Error || this.state.errorMessage !== errorMessage) { this.setState({ isFirstLoad: false });
this.setState({
loading: LoadingState.Error,
isFirstLoad: false,
errorMessage: errorMessage,
});
} }
}; };
...@@ -184,11 +185,10 @@ export class DataPanel extends Component<Props, State> { ...@@ -184,11 +185,10 @@ export class DataPanel extends Component<Props, State> {
render() { render() {
const { queries } = this.props; const { queries } = this.props;
const { loading, isFirstLoad } = this.state; const { loading, isFirstLoad } = this.state;
const panelData = this.getPanelData(); const panelData = this.getPanelData();
if (isFirstLoad && loading === LoadingState.Loading) { if (isFirstLoad && loading === LoadingState.Loading) {
return this.renderLoadingStates(); return this.renderLoadingState();
} }
if (!queries.length) { if (!queries.length) {
...@@ -201,46 +201,21 @@ export class DataPanel extends Component<Props, State> { ...@@ -201,46 +201,21 @@ export class DataPanel extends Component<Props, State> {
return ( return (
<> <>
{this.renderLoadingStates()} {this.renderLoadingState()}
<ErrorBoundary> {this.props.children({ loading, panelData })}
{({ error, errorInfo }) => {
if (errorInfo) {
this.onError(error.message || DEFAULT_PLUGIN_ERROR);
return null;
}
return (
<>
{this.props.children({
loading,
panelData,
})}
</>
);
}}
</ErrorBoundary>
</> </>
); );
} }
private renderLoadingStates(): JSX.Element { private renderLoadingState(): JSX.Element {
const { loading, errorMessage } = this.state; const { loading } = 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="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;
} }
} }
...@@ -8,6 +8,7 @@ import { getTimeSrv, TimeSrv } from '../services/TimeSrv'; ...@@ -8,6 +8,7 @@ import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
// Components // Components
import { PanelHeader } from './PanelHeader/PanelHeader'; import { PanelHeader } from './PanelHeader/PanelHeader';
import { DataPanel } from './DataPanel'; import { DataPanel } from './DataPanel';
import ErrorBoundary from '../../../core/components/ErrorBoundary/ErrorBoundary';
// Utils // Utils
import { applyPanelTimeOverrides, snapshotDataToPanelData } from 'app/features/dashboard/utils/panel'; import { applyPanelTimeOverrides, snapshotDataToPanelData } from 'app/features/dashboard/utils/panel';
...@@ -17,11 +18,12 @@ import { profiler } from 'app/core/profiler'; ...@@ -17,11 +18,12 @@ 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 { 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';
import { DataQueryResponse } from '@grafana/ui/src';
const DEFAULT_PLUGIN_ERROR = 'Error in plugin';
export interface Props { export interface Props {
panel: PanelModel; panel: PanelModel;
...@@ -34,6 +36,7 @@ export interface State { ...@@ -34,6 +36,7 @@ export interface State {
renderCounter: number; renderCounter: number;
timeInfo?: string; timeInfo?: string;
timeRange?: TimeRange; timeRange?: TimeRange;
errorMessage: string | null;
} }
export class PanelChrome extends PureComponent<Props, State> { export class PanelChrome extends PureComponent<Props, State> {
...@@ -45,6 +48,7 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -45,6 +48,7 @@ export class PanelChrome extends PureComponent<Props, State> {
this.state = { this.state = {
refreshCounter: 0, refreshCounter: 0,
renderCounter: 0, renderCounter: 0,
errorMessage: null,
}; };
} }
...@@ -88,8 +92,33 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -88,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);
} }
...@@ -150,6 +179,7 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -150,6 +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.onDataError}
> >
{({ loading, panelData }) => { {({ loading, panelData }) => {
return this.renderPanelPlugin(loading, panelData, width, height); return this.renderPanelPlugin(loading, panelData, width, height);
...@@ -164,7 +194,7 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -164,7 +194,7 @@ export class PanelChrome extends PureComponent<Props, State> {
render() { render() {
const { dashboard, panel } = this.props; const { dashboard, panel } = this.props;
const { timeInfo } = this.state; const { errorMessage, timeInfo } = this.state;
const { transparent } = panel; const { transparent } = panel;
const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`; const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`;
...@@ -185,8 +215,17 @@ export class PanelChrome extends PureComponent<Props, State> { ...@@ -185,8 +215,17 @@ export class PanelChrome extends PureComponent<Props, State> {
description={panel.description} description={panel.description}
scopedVars={panel.scopedVars} scopedVars={panel.scopedVars}
links={panel.links} links={panel.links}
error={errorMessage}
/> />
{this.renderPanelBody(width, height)} <ErrorBoundary>
{({ error, errorInfo }) => {
if (errorInfo) {
this.onPanelError(error.message || DEFAULT_PLUGIN_ERROR);
return null;
}
return this.renderPanelBody(width, height);
}}
</ErrorBoundary>
</div> </div>
); );
}} }}
......
...@@ -18,6 +18,7 @@ export interface Props { ...@@ -18,6 +18,7 @@ export interface Props {
description?: string; description?: string;
scopedVars?: string; scopedVars?: string;
links?: []; links?: [];
error?: string;
} }
interface ClickCoordinates { interface ClickCoordinates {
...@@ -71,7 +72,7 @@ export class PanelHeader extends Component<Props, State> { ...@@ -71,7 +72,7 @@ export class PanelHeader extends Component<Props, State> {
const isFullscreen = false; const isFullscreen = false;
const isLoading = false; const isLoading = false;
const panelHeaderClass = classNames({ 'panel-header': true, 'grid-drag-handle': !isFullscreen }); const panelHeaderClass = classNames({ 'panel-header': true, 'grid-drag-handle': !isFullscreen });
const { panel, dashboard, timeInfo, scopedVars } = this.props; const { panel, dashboard, timeInfo, scopedVars, error } = this.props;
const title = templateSrv.replaceWithText(panel.title, scopedVars); const title = templateSrv.replaceWithText(panel.title, scopedVars);
return ( return (
...@@ -82,6 +83,7 @@ export class PanelHeader extends Component<Props, State> { ...@@ -82,6 +83,7 @@ export class PanelHeader extends Component<Props, State> {
description={panel.description} description={panel.description}
scopedVars={panel.scopedVars} scopedVars={panel.scopedVars}
links={panel.links} links={panel.links}
error={error}
/> />
<div className={panelHeaderClass}> <div className={panelHeaderClass}>
{isLoading && ( {isLoading && (
......
...@@ -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',
...@@ -18,18 +18,22 @@ interface Props { ...@@ -18,18 +18,22 @@ interface Props {
description?: string; description?: string;
scopedVars?: string; scopedVars?: string;
links?: []; links?: [];
error?: string;
} }
export class PanelHeaderCorner extends Component<Props> { export class PanelHeaderCorner extends Component<Props> {
timeSrv: TimeSrv = getTimeSrv(); timeSrv: TimeSrv = getTimeSrv();
getInfoMode = () => { getInfoMode = () => {
const { panel } = this.props; const { panel, error } = this.props;
if (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;
...@@ -42,7 +46,7 @@ export class PanelHeaderCorner extends Component<Props> { ...@@ -42,7 +46,7 @@ export class PanelHeaderCorner extends Component<Props> {
const interpolatedMarkdown = templateSrv.replace(markdown, panel.scopedVars); const interpolatedMarkdown = templateSrv.replace(markdown, panel.scopedVars);
const remarkableInterpolatedMarkdown = new Remarkable().render(interpolatedMarkdown); const remarkableInterpolatedMarkdown = new Remarkable().render(interpolatedMarkdown);
const html = ( return (
<div className="markdown-html"> <div className="markdown-html">
<div dangerouslySetInnerHTML={{ __html: remarkableInterpolatedMarkdown }} /> <div dangerouslySetInnerHTML={{ __html: remarkableInterpolatedMarkdown }} />
{panel.links && {panel.links &&
...@@ -62,30 +66,37 @@ export class PanelHeaderCorner extends Component<Props> { ...@@ -62,30 +66,37 @@ export class PanelHeaderCorner extends Component<Props> {
)} )}
</div> </div>
); );
return html;
}; };
render() { renderCornerType(infoMode: InfoMode, content: string | JSX.Element) {
const infoMode: InfoModes | undefined = this.getInfoMode(); const theme = infoMode === InfoMode.Error ? 'error' : 'info';
if (!infoMode) {
return null;
}
return ( return (
<> <Tooltip content={content} placement="bottom-start" theme={theme}>
{infoMode === InfoModes.Info || infoMode === InfoModes.Links ? (
<Tooltip content={this.getInfoContent()} placement="bottom-start">
<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" />
</div> </div>
</Tooltip> </Tooltip>
) : null}
</>
); );
} }
render() {
const infoMode: InfoMode | undefined = this.getInfoMode();
if (!infoMode) {
return null;
}
if (infoMode === InfoMode.Error) {
return this.renderCornerType(infoMode, this.props.error);
}
if (infoMode === InfoMode.Info) {
return this.renderCornerType(infoMode, this.getInfoContent());
}
return null;
}
} }
export default PanelHeaderCorner; export default PanelHeaderCorner;
...@@ -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