mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #13842 from grafana/davkal/explore-error-handling
Explore: error handling and time fixes
This commit is contained in:
commit
b00e709aee
@ -83,6 +83,10 @@ function areRowsMatching(columns, row, otherRow) {
|
|||||||
export function mergeTablesIntoModel(dst?: TableModel, ...tables: TableModel[]): TableModel {
|
export function mergeTablesIntoModel(dst?: TableModel, ...tables: TableModel[]): TableModel {
|
||||||
const model = dst || new TableModel();
|
const model = dst || new TableModel();
|
||||||
|
|
||||||
|
if (arguments.length === 1) {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
// Single query returns data columns and rows as is
|
// Single query returns data columns and rows as is
|
||||||
if (arguments.length === 2) {
|
if (arguments.length === 2) {
|
||||||
model.columns = [...tables[0].columns];
|
model.columns = [...tables[0].columns];
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import { RawTimeRange } from 'app/types/series';
|
||||||
|
|
||||||
import * as dateMath from './datemath';
|
import * as dateMath from './datemath';
|
||||||
|
|
||||||
const spans = {
|
const spans = {
|
||||||
@ -129,7 +132,7 @@ export function describeTextRange(expr: any) {
|
|||||||
return opt;
|
return opt;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function describeTimeRange(range) {
|
export function describeTimeRange(range: RawTimeRange): string {
|
||||||
const option = rangeIndex[range.from.toString() + ' to ' + range.to.toString()];
|
const option = rangeIndex[range.from.toString() + ' to ' + range.to.toString()];
|
||||||
if (option) {
|
if (option) {
|
||||||
return option.display;
|
return option.display;
|
||||||
|
34
public/app/features/explore/ErrorBoundary.tsx
Normal file
34
public/app/features/explore/ErrorBoundary.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
export default class ErrorBoundary extends Component<{}, any> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { error: null, errorInfo: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error, errorInfo) {
|
||||||
|
// Catch errors in any components below and re-render with error message
|
||||||
|
this.setState({
|
||||||
|
error: error,
|
||||||
|
errorInfo: errorInfo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.errorInfo) {
|
||||||
|
// Error path
|
||||||
|
return (
|
||||||
|
<div className="explore-container">
|
||||||
|
<h3>An unexpected error happened.</h3>
|
||||||
|
<details style={{ whiteSpace: 'pre-wrap' }}>
|
||||||
|
{this.state.error && this.state.error.toString()}
|
||||||
|
<br />
|
||||||
|
{this.state.errorInfo.componentStack}
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Normally, just render children
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
@ -3,15 +3,7 @@ import { hot } from 'react-hot-loader';
|
|||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import {
|
import { ExploreState, ExploreUrlState, HistoryItem, Query, QueryTransaction, ResultType } from 'app/types/explore';
|
||||||
ExploreState,
|
|
||||||
ExploreUrlState,
|
|
||||||
HistoryItem,
|
|
||||||
Query,
|
|
||||||
QueryTransaction,
|
|
||||||
Range,
|
|
||||||
ResultType,
|
|
||||||
} from 'app/types/explore';
|
|
||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import colors from 'app/core/utils/colors';
|
import colors from 'app/core/utils/colors';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
@ -24,12 +16,14 @@ import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer'
|
|||||||
import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage';
|
import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage';
|
||||||
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
|
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
|
||||||
|
|
||||||
|
import ErrorBoundary from './ErrorBoundary';
|
||||||
import QueryRows from './QueryRows';
|
import QueryRows from './QueryRows';
|
||||||
import Graph from './Graph';
|
import Graph from './Graph';
|
||||||
import Logs from './Logs';
|
import Logs from './Logs';
|
||||||
import Table from './Table';
|
import Table from './Table';
|
||||||
import TimePicker from './TimePicker';
|
import TimePicker from './TimePicker';
|
||||||
import { ensureQueries, generateQueryKey, hasQuery } from './utils/query';
|
import { ensureQueries, generateQueryKey, hasQuery } from './utils/query';
|
||||||
|
import { RawTimeRange } from 'app/types/series';
|
||||||
|
|
||||||
const MAX_HISTORY_ITEMS = 100;
|
const MAX_HISTORY_ITEMS = 100;
|
||||||
|
|
||||||
@ -154,11 +148,6 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error) {
|
|
||||||
this.setState({ datasourceError: error });
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setDatasource(datasource) {
|
async setDatasource(datasource) {
|
||||||
const supportsGraph = datasource.meta.metrics;
|
const supportsGraph = datasource.meta.metrics;
|
||||||
const supportsLogs = datasource.meta.logs;
|
const supportsLogs = datasource.meta.logs;
|
||||||
@ -170,7 +159,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
const testResult = await datasource.testDatasource();
|
const testResult = await datasource.testDatasource();
|
||||||
datasourceError = testResult.status === 'success' ? null : testResult.message;
|
datasourceError = testResult.status === 'success' ? null : testResult.message;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
datasourceError = (error && error.statusText) || error;
|
datasourceError = (error && error.statusText) || 'Network error';
|
||||||
}
|
}
|
||||||
|
|
||||||
const historyKey = `grafana.explore.history.${datasourceId}`;
|
const historyKey = `grafana.explore.history.${datasourceId}`;
|
||||||
@ -278,10 +267,9 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeTime = nextRange => {
|
onChangeTime = (nextRange: RawTimeRange) => {
|
||||||
const range = {
|
const range: RawTimeRange = {
|
||||||
from: nextRange.from,
|
...nextRange,
|
||||||
to: nextRange.to,
|
|
||||||
};
|
};
|
||||||
this.setState({ range }, () => this.onSubmit());
|
this.setState({ range }, () => this.onSubmit());
|
||||||
};
|
};
|
||||||
@ -471,7 +459,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
) {
|
) {
|
||||||
const { datasource, range } = this.state;
|
const { datasource, range } = this.state;
|
||||||
const resolution = this.el.offsetWidth;
|
const resolution = this.el.offsetWidth;
|
||||||
const absoluteRange = {
|
const absoluteRange: RawTimeRange = {
|
||||||
from: parseDate(range.from, false),
|
from: parseDate(range.from, false),
|
||||||
to: parseDate(range.to, true),
|
to: parseDate(range.to, true),
|
||||||
};
|
};
|
||||||
@ -486,7 +474,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Clone range for query request
|
// Clone range for query request
|
||||||
const queryRange: Range = { ...range };
|
const queryRange: RawTimeRange = { ...range };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
interval,
|
interval,
|
||||||
@ -584,13 +572,28 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
failQueryTransaction(transactionId: string, error: string, datasourceId: string) {
|
failQueryTransaction(transactionId: string, response: any, datasourceId: string) {
|
||||||
const { datasource } = this.state;
|
const { datasource } = this.state;
|
||||||
if (datasource.meta.id !== datasourceId) {
|
if (datasource.meta.id !== datasourceId) {
|
||||||
// Navigated away, queries did not matter
|
// Navigated away, queries did not matter
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.error(response);
|
||||||
|
|
||||||
|
let error: string | JSX.Element = response;
|
||||||
|
if (response.data) {
|
||||||
|
error = response.data.error;
|
||||||
|
if (response.data.response) {
|
||||||
|
error = (
|
||||||
|
<>
|
||||||
|
<span>{response.data.error}</span>
|
||||||
|
<details>{response.data.response}</details>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.setState(state => {
|
this.setState(state => {
|
||||||
// Transaction might have been discarded
|
// Transaction might have been discarded
|
||||||
if (!state.queryTransactions.find(qt => qt.id === transactionId)) {
|
if (!state.queryTransactions.find(qt => qt.id === transactionId)) {
|
||||||
@ -637,9 +640,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
||||||
this.setState({ graphRange: transaction.options.range });
|
this.setState({ graphRange: transaction.options.range });
|
||||||
} catch (response) {
|
} catch (response) {
|
||||||
console.error(response);
|
this.failQueryTransaction(transaction.id, response, datasourceId);
|
||||||
const queryError = response.data ? response.data.error : response;
|
|
||||||
this.failQueryTransaction(transaction.id, queryError, datasourceId);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.discardTransactions(rowIndex);
|
this.discardTransactions(rowIndex);
|
||||||
@ -669,9 +670,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
const results = res.data[0];
|
const results = res.data[0];
|
||||||
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
||||||
} catch (response) {
|
} catch (response) {
|
||||||
console.error(response);
|
this.failQueryTransaction(transaction.id, response, datasourceId);
|
||||||
const queryError = response.data ? response.data.error : response;
|
|
||||||
this.failQueryTransaction(transaction.id, queryError, datasourceId);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.discardTransactions(rowIndex);
|
this.discardTransactions(rowIndex);
|
||||||
@ -697,9 +696,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
const results = res.data;
|
const results = res.data;
|
||||||
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
|
||||||
} catch (response) {
|
} catch (response) {
|
||||||
console.error(response);
|
this.failQueryTransaction(transaction.id, response, datasourceId);
|
||||||
const queryError = response.data ? response.data.error : response;
|
|
||||||
this.failQueryTransaction(transaction.id, queryError, datasourceId);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.discardTransactions(rowIndex);
|
this.discardTransactions(rowIndex);
|
||||||
@ -751,15 +748,16 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
const graphLoading = queryTransactions.some(qt => qt.resultType === 'Graph' && !qt.done);
|
const graphLoading = queryTransactions.some(qt => qt.resultType === 'Graph' && !qt.done);
|
||||||
const tableLoading = queryTransactions.some(qt => qt.resultType === 'Table' && !qt.done);
|
const tableLoading = queryTransactions.some(qt => qt.resultType === 'Table' && !qt.done);
|
||||||
const logsLoading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done);
|
const logsLoading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done);
|
||||||
|
// TODO don't recreate those on each re-render
|
||||||
const graphResult = _.flatten(
|
const graphResult = _.flatten(
|
||||||
queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result)
|
queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result)
|
||||||
);
|
);
|
||||||
const tableResult = mergeTablesIntoModel(
|
const tableResult = mergeTablesIntoModel(
|
||||||
new TableModel(),
|
new TableModel(),
|
||||||
...queryTransactions.filter(qt => qt.resultType === 'Table' && qt.done).map(qt => qt.result)
|
...queryTransactions.filter(qt => qt.resultType === 'Table' && qt.done && qt.result).map(qt => qt.result)
|
||||||
);
|
);
|
||||||
const logsResult = _.flatten(
|
const logsResult = _.flatten(
|
||||||
queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done).map(qt => qt.result)
|
queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result)
|
||||||
);
|
);
|
||||||
const loading = queryTransactions.some(qt => !qt.done);
|
const loading = queryTransactions.some(qt => !qt.done);
|
||||||
|
|
||||||
@ -868,6 +866,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main className="m-t-2">
|
<main className="m-t-2">
|
||||||
|
<ErrorBoundary>
|
||||||
{supportsGraph &&
|
{supportsGraph &&
|
||||||
showingGraph && (
|
showingGraph && (
|
||||||
<Graph
|
<Graph
|
||||||
@ -885,6 +884,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{supportsLogs && showingLogs ? <Logs data={logsResult} loading={logsLoading} /> : null}
|
{supportsLogs && showingLogs ? <Logs data={logsResult} loading={logsLoading} /> : null}
|
||||||
|
</ErrorBoundary>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -6,7 +6,7 @@ import { withSize } from 'react-sizeme';
|
|||||||
import 'vendor/flot/jquery.flot';
|
import 'vendor/flot/jquery.flot';
|
||||||
import 'vendor/flot/jquery.flot.time';
|
import 'vendor/flot/jquery.flot.time';
|
||||||
|
|
||||||
import { Range } from 'app/types/explore';
|
import { RawTimeRange } from 'app/types/series';
|
||||||
import * as dateMath from 'app/core/utils/datemath';
|
import * as dateMath from 'app/core/utils/datemath';
|
||||||
import TimeSeries from 'app/core/time_series2';
|
import TimeSeries from 'app/core/time_series2';
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ interface GraphProps {
|
|||||||
height?: string; // e.g., '200px'
|
height?: string; // e.g., '200px'
|
||||||
id?: string;
|
id?: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
range: Range;
|
range: RawTimeRange;
|
||||||
split?: boolean;
|
split?: boolean;
|
||||||
size?: { width: number; height: number };
|
size?: { width: number; height: number };
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ interface CascaderOption {
|
|||||||
|
|
||||||
interface PromQueryFieldProps {
|
interface PromQueryFieldProps {
|
||||||
datasource: any;
|
datasource: any;
|
||||||
error?: string;
|
error?: string | JSX.Element;
|
||||||
hint?: any;
|
hint?: any;
|
||||||
history?: any[];
|
history?: any[];
|
||||||
initialQuery?: string | null;
|
initialQuery?: string | null;
|
||||||
|
@ -3,6 +3,7 @@ import moment from 'moment';
|
|||||||
|
|
||||||
import * as dateMath from 'app/core/utils/datemath';
|
import * as dateMath from 'app/core/utils/datemath';
|
||||||
import * as rangeUtil from 'app/core/utils/rangeutil';
|
import * as rangeUtil from 'app/core/utils/rangeutil';
|
||||||
|
import { RawTimeRange } from 'app/types/series';
|
||||||
|
|
||||||
const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||||
export const DEFAULT_RANGE = {
|
export const DEFAULT_RANGE = {
|
||||||
@ -10,77 +11,104 @@ export const DEFAULT_RANGE = {
|
|||||||
to: 'now',
|
to: 'now',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function parseTime(value, isUtc = false, asString = false) {
|
/**
|
||||||
|
* Return a human-editable string of either relative (inludes "now") or absolute local time (in the shape of DATE_FORMAT).
|
||||||
|
* @param value Epoch or relative time
|
||||||
|
*/
|
||||||
|
export function parseTime(value: string, isUtc = false): string {
|
||||||
if (value.indexOf('now') !== -1) {
|
if (value.indexOf('now') !== -1) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
if (!isNaN(value)) {
|
let time: any = value;
|
||||||
const epoch = parseInt(value, 10);
|
// Possible epoch
|
||||||
const m = isUtc ? moment.utc(epoch) : moment(epoch);
|
if (!isNaN(time)) {
|
||||||
return asString ? m.format(DATE_FORMAT) : m;
|
time = parseInt(time, 10);
|
||||||
}
|
}
|
||||||
return undefined;
|
time = isUtc ? moment.utc(time) : moment(time);
|
||||||
|
return time.format(DATE_FORMAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TimePicker extends PureComponent<any, any> {
|
interface TimePickerProps {
|
||||||
|
isOpen?: boolean;
|
||||||
|
isUtc?: boolean;
|
||||||
|
range?: RawTimeRange;
|
||||||
|
onChangeTime?: (Range) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimePickerState {
|
||||||
|
isOpen: boolean;
|
||||||
|
isUtc: boolean;
|
||||||
|
rangeString: string;
|
||||||
|
refreshInterval: string;
|
||||||
|
|
||||||
|
// Input-controlled text, keep these in a shape that is human-editable
|
||||||
|
fromRaw: string;
|
||||||
|
toRaw: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TimePicker extends PureComponent<TimePickerProps, TimePickerState> {
|
||||||
dropdownEl: any;
|
dropdownEl: any;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const fromRaw = props.range ? props.range.from : DEFAULT_RANGE.from;
|
const from = props.range ? props.range.from : DEFAULT_RANGE.from;
|
||||||
const toRaw = props.range ? props.range.to : DEFAULT_RANGE.to;
|
const to = props.range ? props.range.to : DEFAULT_RANGE.to;
|
||||||
|
|
||||||
|
// Ensure internal format
|
||||||
|
const fromRaw = parseTime(from, props.isUtc);
|
||||||
|
const toRaw = parseTime(to, props.isUtc);
|
||||||
const range = {
|
const range = {
|
||||||
from: parseTime(fromRaw),
|
from: fromRaw,
|
||||||
to: parseTime(toRaw),
|
to: toRaw,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
fromRaw: parseTime(fromRaw, props.isUtc, true),
|
fromRaw,
|
||||||
|
toRaw,
|
||||||
isOpen: props.isOpen,
|
isOpen: props.isOpen,
|
||||||
isUtc: props.isUtc,
|
isUtc: props.isUtc,
|
||||||
rangeString: rangeUtil.describeTimeRange(range),
|
rangeString: rangeUtil.describeTimeRange(range),
|
||||||
refreshInterval: '',
|
refreshInterval: '',
|
||||||
toRaw: parseTime(toRaw, props.isUtc, true),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
move(direction) {
|
move(direction: number) {
|
||||||
const { onChangeTime } = this.props;
|
const { onChangeTime } = this.props;
|
||||||
const { fromRaw, toRaw } = this.state;
|
const { fromRaw, toRaw } = this.state;
|
||||||
const range = {
|
const from = dateMath.parse(fromRaw, false);
|
||||||
from: dateMath.parse(fromRaw, false),
|
const to = dateMath.parse(toRaw, true);
|
||||||
to: dateMath.parse(toRaw, true),
|
const timespan = (to.valueOf() - from.valueOf()) / 2;
|
||||||
};
|
|
||||||
|
|
||||||
const timespan = (range.to.valueOf() - range.from.valueOf()) / 2;
|
let nextTo, nextFrom;
|
||||||
let to, from;
|
|
||||||
if (direction === -1) {
|
if (direction === -1) {
|
||||||
to = range.to.valueOf() - timespan;
|
nextTo = to.valueOf() - timespan;
|
||||||
from = range.from.valueOf() - timespan;
|
nextFrom = from.valueOf() - timespan;
|
||||||
} else if (direction === 1) {
|
} else if (direction === 1) {
|
||||||
to = range.to.valueOf() + timespan;
|
nextTo = to.valueOf() + timespan;
|
||||||
from = range.from.valueOf() + timespan;
|
nextFrom = from.valueOf() + timespan;
|
||||||
if (to > Date.now() && range.to < Date.now()) {
|
if (nextTo > Date.now() && to < Date.now()) {
|
||||||
to = Date.now();
|
nextTo = Date.now();
|
||||||
from = range.from.valueOf();
|
nextFrom = from.valueOf();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
to = range.to.valueOf();
|
nextTo = to.valueOf();
|
||||||
from = range.from.valueOf();
|
nextFrom = from.valueOf();
|
||||||
}
|
}
|
||||||
|
|
||||||
const rangeString = rangeUtil.describeTimeRange(range);
|
const nextRange = {
|
||||||
// No need to convert to UTC again
|
from: moment(nextFrom),
|
||||||
to = moment(to);
|
to: moment(nextTo),
|
||||||
from = moment(from);
|
};
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
rangeString,
|
rangeString: rangeUtil.describeTimeRange(nextRange),
|
||||||
fromRaw: from.format(DATE_FORMAT),
|
fromRaw: nextRange.from.format(DATE_FORMAT),
|
||||||
toRaw: to.format(DATE_FORMAT),
|
toRaw: nextRange.to.format(DATE_FORMAT),
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
onChangeTime({ to, from });
|
onChangeTime(nextRange);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -99,16 +127,19 @@ export default class TimePicker extends PureComponent<any, any> {
|
|||||||
|
|
||||||
handleClickApply = () => {
|
handleClickApply = () => {
|
||||||
const { onChangeTime } = this.props;
|
const { onChangeTime } = this.props;
|
||||||
|
let range;
|
||||||
|
this.setState(
|
||||||
|
state => {
|
||||||
const { toRaw, fromRaw } = this.state;
|
const { toRaw, fromRaw } = this.state;
|
||||||
const range = {
|
range = {
|
||||||
from: dateMath.parse(fromRaw, false),
|
from: dateMath.parse(fromRaw, false),
|
||||||
to: dateMath.parse(toRaw, true),
|
to: dateMath.parse(toRaw, true),
|
||||||
};
|
};
|
||||||
const rangeString = rangeUtil.describeTimeRange(range);
|
const rangeString = rangeUtil.describeTimeRange(range);
|
||||||
this.setState(
|
return {
|
||||||
{
|
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
rangeString,
|
rangeString,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
if (onChangeTime) {
|
if (onChangeTime) {
|
||||||
|
@ -7,6 +7,7 @@ import { serializeStateToUrlParam, parseUrlState } from 'app/core/utils/explore'
|
|||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
import { ExploreState } from 'app/types/explore';
|
import { ExploreState } from 'app/types/explore';
|
||||||
|
|
||||||
|
import ErrorBoundary from './ErrorBoundary';
|
||||||
import Explore from './Explore';
|
import Explore from './Explore';
|
||||||
|
|
||||||
interface WrapperProps {
|
interface WrapperProps {
|
||||||
@ -61,8 +62,10 @@ export class Wrapper extends Component<WrapperProps, WrapperState> {
|
|||||||
const { split, splitState } = this.state;
|
const { split, splitState } = this.state;
|
||||||
const urlStateLeft = parseUrlState(this.urlStates[STATE_KEY_LEFT]);
|
const urlStateLeft = parseUrlState(this.urlStates[STATE_KEY_LEFT]);
|
||||||
const urlStateRight = parseUrlState(this.urlStates[STATE_KEY_RIGHT]);
|
const urlStateRight = parseUrlState(this.urlStates[STATE_KEY_RIGHT]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="explore-wrapper">
|
<div className="explore-wrapper">
|
||||||
|
<ErrorBoundary>
|
||||||
<Explore
|
<Explore
|
||||||
datasourceSrv={datasourceSrv}
|
datasourceSrv={datasourceSrv}
|
||||||
onChangeSplit={this.onChangeSplit}
|
onChangeSplit={this.onChangeSplit}
|
||||||
@ -72,7 +75,9 @@ export class Wrapper extends Component<WrapperProps, WrapperState> {
|
|||||||
stateKey={STATE_KEY_LEFT}
|
stateKey={STATE_KEY_LEFT}
|
||||||
urlState={urlStateLeft}
|
urlState={urlStateLeft}
|
||||||
/>
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
{split && (
|
{split && (
|
||||||
|
<ErrorBoundary>
|
||||||
<Explore
|
<Explore
|
||||||
datasourceSrv={datasourceSrv}
|
datasourceSrv={datasourceSrv}
|
||||||
onChangeSplit={this.onChangeSplit}
|
onChangeSplit={this.onChangeSplit}
|
||||||
@ -83,6 +88,7 @@ export class Wrapper extends Component<WrapperProps, WrapperState> {
|
|||||||
stateKey={STATE_KEY_RIGHT}
|
stateKey={STATE_KEY_RIGHT}
|
||||||
urlState={urlStateRight}
|
urlState={urlStateRight}
|
||||||
/>
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Value } from 'slate';
|
import { Value } from 'slate';
|
||||||
|
|
||||||
|
import { RawTimeRange } from './series';
|
||||||
|
|
||||||
export interface CompletionItem {
|
export interface CompletionItem {
|
||||||
/**
|
/**
|
||||||
* The label of this completion item. By default
|
* The label of this completion item. By default
|
||||||
@ -100,11 +102,6 @@ export interface TypeaheadOutput {
|
|||||||
suggestions: CompletionItemGroup[];
|
suggestions: CompletionItemGroup[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Range {
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Query {
|
export interface Query {
|
||||||
query: string;
|
query: string;
|
||||||
key?: string;
|
key?: string;
|
||||||
@ -131,7 +128,7 @@ export interface QueryHint {
|
|||||||
export interface QueryTransaction {
|
export interface QueryTransaction {
|
||||||
id: string;
|
id: string;
|
||||||
done: boolean;
|
done: boolean;
|
||||||
error?: string;
|
error?: string | JSX.Element;
|
||||||
hints?: QueryHint[];
|
hints?: QueryHint[];
|
||||||
latency: number;
|
latency: number;
|
||||||
options: any;
|
options: any;
|
||||||
@ -155,7 +152,7 @@ export interface ExploreState {
|
|||||||
datasourceMissing: boolean;
|
datasourceMissing: boolean;
|
||||||
datasourceName?: string;
|
datasourceName?: string;
|
||||||
exploreDatasources: ExploreDatasource[];
|
exploreDatasources: ExploreDatasource[];
|
||||||
graphRange: Range;
|
graphRange: RawTimeRange;
|
||||||
history: HistoryItem[];
|
history: HistoryItem[];
|
||||||
/**
|
/**
|
||||||
* Initial rows of queries to push down the tree.
|
* Initial rows of queries to push down the tree.
|
||||||
@ -167,7 +164,7 @@ export interface ExploreState {
|
|||||||
* Hints gathered for the query row.
|
* Hints gathered for the query row.
|
||||||
*/
|
*/
|
||||||
queryTransactions: QueryTransaction[];
|
queryTransactions: QueryTransaction[];
|
||||||
range: Range;
|
range: RawTimeRange;
|
||||||
showingGraph: boolean;
|
showingGraph: boolean;
|
||||||
showingLogs: boolean;
|
showingLogs: boolean;
|
||||||
showingTable: boolean;
|
showingTable: boolean;
|
||||||
@ -179,7 +176,7 @@ export interface ExploreState {
|
|||||||
export interface ExploreUrlState {
|
export interface ExploreUrlState {
|
||||||
datasource: string;
|
datasource: string;
|
||||||
queries: Query[];
|
queries: Query[];
|
||||||
range: Range;
|
range: RawTimeRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ResultType = 'Graph' | 'Logs' | 'Table';
|
export type ResultType = 'Graph' | 'Logs' | 'Table';
|
||||||
|
@ -258,6 +258,11 @@
|
|||||||
|
|
||||||
.prom-query-field-info {
|
.prom-query-field-info {
|
||||||
margin: 0.25em 0.5em 0.5em;
|
margin: 0.25em 0.5em 0.5em;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
details {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user