mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Align Explore with Dashboards and Panels (#16823)
* Wip: Removes queryTransactions from state * Refactor: Adds back query failures * Refactor: Moves error parsing to datasources * Refactor: Adds back hinting for Prometheus * Refactor: removed commented out code * Refactor: Adds back QueryStatus * Refactor: Adds scanning back to Explore * Fix: Fixes prettier error * Fix: Makes sure there is an error * Merge: Merges with master * Fix: Adds safeStringifyValue to error parsing * Fix: Fixes table result calculations * Refactor: Adds ErrorContainer and generic error handling in Explore * Fix: Fixes so refIds remain consistent * Refactor: Makes it possible to return result even when there are errors * Fix: Fixes digest issue with Angular editors * Refactor: Adds tests for explore utils * Refactor: Breakes current behaviour of always returning a result even if Query fails * Fix: Fixes Prettier error * Fix: Adds back console.log for erroneous querys * Refactor: Changes console.log to console.error
This commit is contained in:
parent
8eb78ea931
commit
6dbaa704bc
@ -214,16 +214,10 @@ export interface ExploreQueryFieldProps<
|
|||||||
DSType extends DataSourceApi<TQuery, TOptions>,
|
DSType extends DataSourceApi<TQuery, TOptions>,
|
||||||
TQuery extends DataQuery = DataQuery,
|
TQuery extends DataQuery = DataQuery,
|
||||||
TOptions extends DataSourceJsonData = DataSourceJsonData
|
TOptions extends DataSourceJsonData = DataSourceJsonData
|
||||||
> {
|
> extends QueryEditorProps<DSType, TQuery, TOptions> {
|
||||||
datasource: DSType;
|
|
||||||
datasourceStatus: DataSourceStatus;
|
datasourceStatus: DataSourceStatus;
|
||||||
query: TQuery;
|
|
||||||
error?: string | JSX.Element;
|
|
||||||
hint?: QueryHint;
|
|
||||||
history: any[];
|
history: any[];
|
||||||
onExecuteQuery?: () => void;
|
onHint?: (action: QueryFixAction) => void;
|
||||||
onQueryChange?: (value: TQuery) => void;
|
|
||||||
onExecuteHint?: (action: QueryFixAction) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExploreStartPageProps {
|
export interface ExploreStartPageProps {
|
||||||
|
@ -47,6 +47,7 @@ export interface DateTimeDuration {
|
|||||||
|
|
||||||
export interface DateTime extends Object {
|
export interface DateTime extends Object {
|
||||||
add: (amount?: DateTimeInput, unit?: DurationUnit) => DateTime;
|
add: (amount?: DateTimeInput, unit?: DurationUnit) => DateTime;
|
||||||
|
diff: (amount: DateTimeInput, unit?: DurationUnit, truncate?: boolean) => number;
|
||||||
endOf: (unitOfTime: DurationUnit) => DateTime;
|
endOf: (unitOfTime: DurationUnit) => DateTime;
|
||||||
format: (formatInput?: FormatInput) => string;
|
format: (formatInput?: FormatInput) => string;
|
||||||
fromNow: (withoutSuffix?: boolean) => string;
|
fromNow: (withoutSuffix?: boolean) => string;
|
||||||
@ -59,7 +60,6 @@ export interface DateTime extends Object {
|
|||||||
subtract: (amount?: DateTimeInput, unit?: DurationUnit) => DateTime;
|
subtract: (amount?: DateTimeInput, unit?: DurationUnit) => DateTime;
|
||||||
toDate: () => Date;
|
toDate: () => Date;
|
||||||
toISOString: () => string;
|
toISOString: () => string;
|
||||||
diff: (amount: DateTimeInput, unit?: DurationUnit, truncate?: boolean) => number;
|
|
||||||
valueOf: () => number;
|
valueOf: () => number;
|
||||||
unix: () => number;
|
unix: () => number;
|
||||||
utc: () => DateTime;
|
utc: () => DateTime;
|
||||||
|
@ -5,10 +5,15 @@ import {
|
|||||||
updateHistory,
|
updateHistory,
|
||||||
clearHistory,
|
clearHistory,
|
||||||
hasNonEmptyQuery,
|
hasNonEmptyQuery,
|
||||||
|
instanceOfDataQueryError,
|
||||||
|
getValueWithRefId,
|
||||||
|
getFirstQueryErrorWithoutRefId,
|
||||||
|
getRefIds,
|
||||||
} from './explore';
|
} from './explore';
|
||||||
import { ExploreUrlState } from 'app/types/explore';
|
import { ExploreUrlState } from 'app/types/explore';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
import { LogsDedupStrategy } from 'app/core/logs_model';
|
import { LogsDedupStrategy } from 'app/core/logs_model';
|
||||||
|
import { DataQueryError } from '@grafana/ui';
|
||||||
|
|
||||||
const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
|
const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
|
||||||
datasource: null,
|
datasource: null,
|
||||||
@ -188,3 +193,164 @@ describe('hasNonEmptyQuery', () => {
|
|||||||
expect(hasNonEmptyQuery([])).toBeFalsy();
|
expect(hasNonEmptyQuery([])).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('instanceOfDataQueryError', () => {
|
||||||
|
describe('when called with a DataQueryError', () => {
|
||||||
|
it('then it should return true', () => {
|
||||||
|
const error: DataQueryError = {
|
||||||
|
message: 'A message',
|
||||||
|
status: '200',
|
||||||
|
statusText: 'Ok',
|
||||||
|
};
|
||||||
|
const result = instanceOfDataQueryError(error);
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with a non DataQueryError', () => {
|
||||||
|
it('then it should return false', () => {
|
||||||
|
const error = {};
|
||||||
|
const result = instanceOfDataQueryError(error);
|
||||||
|
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasRefId', () => {
|
||||||
|
describe('when called with a null value', () => {
|
||||||
|
it('then it should return null', () => {
|
||||||
|
const input = null;
|
||||||
|
const result = getValueWithRefId(input);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with a non object value', () => {
|
||||||
|
it('then it should return null', () => {
|
||||||
|
const input = 123;
|
||||||
|
const result = getValueWithRefId(input);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with an object that has refId', () => {
|
||||||
|
it('then it should return the object', () => {
|
||||||
|
const input = { refId: 'A' };
|
||||||
|
const result = getValueWithRefId(input);
|
||||||
|
|
||||||
|
expect(result).toBe(input);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with an array that has refId', () => {
|
||||||
|
it('then it should return the object', () => {
|
||||||
|
const input = [123, null, {}, { refId: 'A' }];
|
||||||
|
const result = getValueWithRefId(input);
|
||||||
|
|
||||||
|
expect(result).toBe(input[3]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with an object that has refId somewhere in the object tree', () => {
|
||||||
|
it('then it should return the object', () => {
|
||||||
|
const input: any = { data: [123, null, {}, { series: [123, null, {}, { refId: 'A' }] }] };
|
||||||
|
const result = getValueWithRefId(input);
|
||||||
|
|
||||||
|
expect(result).toBe(input.data[3].series[3]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getFirstQueryErrorWithoutRefId', () => {
|
||||||
|
describe('when called with a null value', () => {
|
||||||
|
it('then it should return null', () => {
|
||||||
|
const errors: DataQueryError[] = null;
|
||||||
|
const result = getFirstQueryErrorWithoutRefId(errors);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with an array with only refIds', () => {
|
||||||
|
it('then it should return undefined', () => {
|
||||||
|
const errors: DataQueryError[] = [{ refId: 'A' }, { refId: 'B' }];
|
||||||
|
const result = getFirstQueryErrorWithoutRefId(errors);
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with an array with and without refIds', () => {
|
||||||
|
it('then it should return undefined', () => {
|
||||||
|
const errors: DataQueryError[] = [
|
||||||
|
{ refId: 'A' },
|
||||||
|
{ message: 'A message' },
|
||||||
|
{ refId: 'B' },
|
||||||
|
{ message: 'B message' },
|
||||||
|
];
|
||||||
|
const result = getFirstQueryErrorWithoutRefId(errors);
|
||||||
|
|
||||||
|
expect(result).toBe(errors[1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getRefIds', () => {
|
||||||
|
describe('when called with a null value', () => {
|
||||||
|
it('then it should return empty array', () => {
|
||||||
|
const input = null;
|
||||||
|
const result = getRefIds(input);
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with a non object value', () => {
|
||||||
|
it('then it should return empty array', () => {
|
||||||
|
const input = 123;
|
||||||
|
const result = getRefIds(input);
|
||||||
|
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with an object that has refId', () => {
|
||||||
|
it('then it should return an array with that refId', () => {
|
||||||
|
const input = { refId: 'A' };
|
||||||
|
const result = getRefIds(input);
|
||||||
|
|
||||||
|
expect(result).toEqual(['A']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with an array that has refIds', () => {
|
||||||
|
it('then it should return an array with unique refIds', () => {
|
||||||
|
const input = [123, null, {}, { refId: 'A' }, { refId: 'A' }, { refId: 'B' }];
|
||||||
|
const result = getRefIds(input);
|
||||||
|
|
||||||
|
expect(result).toEqual(['A', 'B']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when called with an object that has refIds somewhere in the object tree', () => {
|
||||||
|
it('then it should return return an array with unique refIds', () => {
|
||||||
|
const input: any = {
|
||||||
|
data: [
|
||||||
|
123,
|
||||||
|
null,
|
||||||
|
{ refId: 'B', series: [{ refId: 'X' }] },
|
||||||
|
{ refId: 'B' },
|
||||||
|
{},
|
||||||
|
{ series: [123, null, {}, { refId: 'A' }] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result = getRefIds(input);
|
||||||
|
|
||||||
|
expect(result).toEqual(['B', 'X', 'A']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
toSeriesData,
|
toSeriesData,
|
||||||
guessFieldTypes,
|
guessFieldTypes,
|
||||||
TimeFragment,
|
TimeFragment,
|
||||||
|
DataQueryError,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import TimeSeries from 'app/core/time_series2';
|
import TimeSeries from 'app/core/time_series2';
|
||||||
import {
|
import {
|
||||||
@ -110,8 +111,7 @@ export async function getExploreUrl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildQueryTransaction(
|
export function buildQueryTransaction(
|
||||||
query: DataQuery,
|
queries: DataQuery[],
|
||||||
rowIndex: number,
|
|
||||||
resultType: ResultType,
|
resultType: ResultType,
|
||||||
queryOptions: QueryOptions,
|
queryOptions: QueryOptions,
|
||||||
range: TimeRange,
|
range: TimeRange,
|
||||||
@ -120,12 +120,11 @@ export function buildQueryTransaction(
|
|||||||
): QueryTransaction {
|
): QueryTransaction {
|
||||||
const { interval, intervalMs } = queryIntervals;
|
const { interval, intervalMs } = queryIntervals;
|
||||||
|
|
||||||
const configuredQueries = [
|
const configuredQueries = queries.map(query => ({ ...query, ...queryOptions }));
|
||||||
{
|
const key = queries.reduce((combinedKey, query) => {
|
||||||
...query,
|
combinedKey += query.key;
|
||||||
...queryOptions,
|
return combinedKey;
|
||||||
},
|
}, '');
|
||||||
];
|
|
||||||
|
|
||||||
// Clone range for query request
|
// Clone range for query request
|
||||||
// const queryRange: RawTimeRange = { ...range };
|
// const queryRange: RawTimeRange = { ...range };
|
||||||
@ -134,7 +133,7 @@ export function buildQueryTransaction(
|
|||||||
// Using `format` here because it relates to the view panel that the request is for.
|
// Using `format` here because it relates to the view panel that the request is for.
|
||||||
// However, some datasources don't use `panelId + query.refId`, but only `panelId`.
|
// However, some datasources don't use `panelId + query.refId`, but only `panelId`.
|
||||||
// Therefore panel id has to be unique.
|
// Therefore panel id has to be unique.
|
||||||
const panelId = `${queryOptions.format}-${query.key}`;
|
const panelId = `${queryOptions.format}-${key}`;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
interval,
|
interval,
|
||||||
@ -151,10 +150,9 @@ export function buildQueryTransaction(
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
queries,
|
||||||
options,
|
options,
|
||||||
query,
|
|
||||||
resultType,
|
resultType,
|
||||||
rowIndex,
|
|
||||||
scanning,
|
scanning,
|
||||||
id: generateKey(), // reusing for unique ID
|
id: generateKey(), // reusing for unique ID
|
||||||
done: false,
|
done: false,
|
||||||
@ -195,6 +193,20 @@ export const safeParseJson = (text: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const safeStringifyValue = (value: any, space?: number) => {
|
||||||
|
if (!value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.stringify(value, null, space);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
export function parseUrlState(initial: string | undefined): ExploreUrlState {
|
export function parseUrlState(initial: string | undefined): ExploreUrlState {
|
||||||
const parsed = safeParseJson(initial);
|
const parsed = safeParseJson(initial);
|
||||||
const errorResult = {
|
const errorResult = {
|
||||||
@ -265,12 +277,34 @@ export function generateEmptyQuery(queries: DataQuery[], index = 0): DataQuery {
|
|||||||
return { refId: getNextRefIdChar(queries), key: generateKey(index) };
|
return { refId: getNextRefIdChar(queries), key: generateKey(index) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const generateNewKeyAndAddRefIdIfMissing = (target: DataQuery, queries: DataQuery[], index = 0): DataQuery => {
|
||||||
|
const key = generateKey(index);
|
||||||
|
const refId = target.refId || getNextRefIdChar(queries);
|
||||||
|
|
||||||
|
return { ...target, refId, key };
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure at least one target exists and that targets have the necessary keys
|
* Ensure at least one target exists and that targets have the necessary keys
|
||||||
*/
|
*/
|
||||||
export function ensureQueries(queries?: DataQuery[]): DataQuery[] {
|
export function ensureQueries(queries?: DataQuery[]): DataQuery[] {
|
||||||
if (queries && typeof queries === 'object' && queries.length > 0) {
|
if (queries && typeof queries === 'object' && queries.length > 0) {
|
||||||
return queries.map((query, i) => ({ ...query, ...generateEmptyQuery(queries, i) }));
|
const allQueries = [];
|
||||||
|
for (let index = 0; index < queries.length; index++) {
|
||||||
|
const query = queries[index];
|
||||||
|
const key = generateKey(index);
|
||||||
|
let refId = query.refId;
|
||||||
|
if (!refId) {
|
||||||
|
refId = getNextRefIdChar(allQueries);
|
||||||
|
}
|
||||||
|
|
||||||
|
allQueries.push({
|
||||||
|
...query,
|
||||||
|
refId,
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return allQueries;
|
||||||
}
|
}
|
||||||
return [{ ...generateEmptyQuery(queries) }];
|
return [{ ...generateEmptyQuery(queries) }];
|
||||||
}
|
}
|
||||||
@ -290,26 +324,20 @@ export function hasNonEmptyQuery<TQuery extends DataQuery = any>(queries: TQuery
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateResultsFromQueryTransactions(
|
export function calculateResultsFromQueryTransactions(result: any, resultType: ResultType, graphInterval: number) {
|
||||||
queryTransactions: QueryTransaction[],
|
const flattenedResult: any[] = _.flatten(result);
|
||||||
datasource: any,
|
const graphResult = resultType === 'Graph' && result ? result : null;
|
||||||
graphInterval: number
|
const tableResult =
|
||||||
) {
|
resultType === 'Table' && result
|
||||||
const graphResult = _.flatten(
|
? mergeTablesIntoModel(
|
||||||
queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result)
|
|
||||||
);
|
|
||||||
const tableResult = mergeTablesIntoModel(
|
|
||||||
new TableModel(),
|
new TableModel(),
|
||||||
...queryTransactions
|
...flattenedResult.filter((r: any) => r.columns && r.rows).map((r: any) => r as TableModel)
|
||||||
.filter(qt => qt.resultType === 'Table' && qt.done && qt.result && qt.result.columns && qt.result.rows)
|
)
|
||||||
.map(qt => qt.result)
|
: mergeTablesIntoModel(new TableModel());
|
||||||
);
|
const logsResult =
|
||||||
const logsResult = seriesDataToLogsModel(
|
resultType === 'Logs' && result
|
||||||
_.flatten(
|
? seriesDataToLogsModel(flattenedResult.map(r => guessFieldTypes(toSeriesData(r))), graphInterval)
|
||||||
queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result)
|
: null;
|
||||||
).map(r => guessFieldTypes(toSeriesData(r))),
|
|
||||||
graphInterval
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
graphResult,
|
graphResult,
|
||||||
@ -441,3 +469,63 @@ export const getTimeRangeFromUrl = (range: RawTimeRange, timeZone: TimeZone): Ti
|
|||||||
raw,
|
raw,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const instanceOfDataQueryError = (value: any): value is DataQueryError => {
|
||||||
|
return value.message !== undefined && value.status !== undefined && value.statusText !== undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getValueWithRefId = (value: any): any | null => {
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== 'object') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.refId) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = Object.keys(value);
|
||||||
|
for (let index = 0; index < keys.length; index++) {
|
||||||
|
const key = keys[index];
|
||||||
|
const refId = getValueWithRefId(value[key]);
|
||||||
|
if (refId) {
|
||||||
|
return refId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFirstQueryErrorWithoutRefId = (errors: DataQueryError[]) => {
|
||||||
|
if (!errors) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.filter(error => (error.refId ? false : true))[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRefIds = (value: any): string[] => {
|
||||||
|
if (!value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== 'object') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = Object.keys(value);
|
||||||
|
const refIds = [];
|
||||||
|
for (let index = 0; index < keys.length; index++) {
|
||||||
|
const key = keys[index];
|
||||||
|
if (key === 'refId') {
|
||||||
|
refIds.push(value[key]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
refIds.push(getRefIds(value[key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.uniq(_.flatten(refIds));
|
||||||
|
};
|
||||||
|
32
public/app/features/explore/ErrorContainer.tsx
Normal file
32
public/app/features/explore/ErrorContainer.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React, { FunctionComponent } from 'react';
|
||||||
|
import { DataQueryError } from '@grafana/ui';
|
||||||
|
import { FadeIn } from 'app/core/components/Animations/FadeIn';
|
||||||
|
import { getFirstQueryErrorWithoutRefId, getValueWithRefId } from 'app/core/utils/explore';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
queryErrors: DataQueryError[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ErrorContainer: FunctionComponent<Props> = props => {
|
||||||
|
const { queryErrors } = props;
|
||||||
|
const refId = getValueWithRefId(queryErrors);
|
||||||
|
const queryError = refId ? null : getFirstQueryErrorWithoutRefId(queryErrors);
|
||||||
|
const showError = queryError ? true : false;
|
||||||
|
const duration = showError ? 100 : 10;
|
||||||
|
const message = queryError ? queryError.message : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FadeIn in={showError} duration={duration}>
|
||||||
|
<div className="alert-container">
|
||||||
|
<div className="alert-error alert">
|
||||||
|
<div className="alert-icon">
|
||||||
|
<i className="fa fa-exclamation-triangle" />
|
||||||
|
</div>
|
||||||
|
<div className="alert-body">
|
||||||
|
<div className="alert-title">{message}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FadeIn>
|
||||||
|
);
|
||||||
|
};
|
@ -31,7 +31,7 @@ import {
|
|||||||
} from './state/actions';
|
} from './state/actions';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { RawTimeRange, DataQuery, ExploreStartPageProps, ExploreDataSourceApi } from '@grafana/ui';
|
import { RawTimeRange, DataQuery, ExploreStartPageProps, ExploreDataSourceApi, DataQueryError } from '@grafana/ui';
|
||||||
import {
|
import {
|
||||||
ExploreItemState,
|
ExploreItemState,
|
||||||
ExploreUrlState,
|
ExploreUrlState,
|
||||||
@ -54,6 +54,7 @@ import { scanStopAction } from './state/actionTypes';
|
|||||||
import { NoDataSourceCallToAction } from './NoDataSourceCallToAction';
|
import { NoDataSourceCallToAction } from './NoDataSourceCallToAction';
|
||||||
import { FadeIn } from 'app/core/components/Animations/FadeIn';
|
import { FadeIn } from 'app/core/components/Animations/FadeIn';
|
||||||
import { getTimeZone } from '../profile/state/selectors';
|
import { getTimeZone } from '../profile/state/selectors';
|
||||||
|
import { ErrorContainer } from './ErrorContainer';
|
||||||
|
|
||||||
interface ExploreProps {
|
interface ExploreProps {
|
||||||
StartPage?: ComponentClass<ExploreStartPageProps>;
|
StartPage?: ComponentClass<ExploreStartPageProps>;
|
||||||
@ -86,6 +87,7 @@ interface ExploreProps {
|
|||||||
initialQueries: DataQuery[];
|
initialQueries: DataQuery[];
|
||||||
initialRange: RawTimeRange;
|
initialRange: RawTimeRange;
|
||||||
initialUI: ExploreUIState;
|
initialUI: ExploreUIState;
|
||||||
|
queryErrors: DataQueryError[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -236,6 +238,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
supportsLogs,
|
supportsLogs,
|
||||||
supportsTable,
|
supportsTable,
|
||||||
queryKeys,
|
queryKeys,
|
||||||
|
queryErrors,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const exploreClass = split ? 'explore explore-split' : 'explore';
|
const exploreClass = split ? 'explore explore-split' : 'explore';
|
||||||
|
|
||||||
@ -257,6 +260,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
{datasourceInstance && (
|
{datasourceInstance && (
|
||||||
<div className="explore-container">
|
<div className="explore-container">
|
||||||
<QueryRows exploreEvents={this.exploreEvents} exploreId={exploreId} queryKeys={queryKeys} />
|
<QueryRows exploreEvents={this.exploreEvents} exploreId={exploreId} queryKeys={queryKeys} />
|
||||||
|
<ErrorContainer queryErrors={queryErrors} />
|
||||||
<AutoSizer onResize={this.onResize} disableHeight>
|
<AutoSizer onResize={this.onResize} disableHeight>
|
||||||
{({ width }) => {
|
{({ width }) => {
|
||||||
if (width === 0) {
|
if (width === 0) {
|
||||||
@ -313,6 +317,7 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) {
|
|||||||
queryKeys,
|
queryKeys,
|
||||||
urlState,
|
urlState,
|
||||||
update,
|
update,
|
||||||
|
queryErrors,
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
const { datasource, queries, range: urlRange, ui } = (urlState || {}) as ExploreUrlState;
|
const { datasource, queries, range: urlRange, ui } = (urlState || {}) as ExploreUrlState;
|
||||||
@ -339,6 +344,7 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps) {
|
|||||||
initialQueries,
|
initialQueries,
|
||||||
initialRange,
|
initialRange,
|
||||||
initialUI,
|
initialUI,
|
||||||
|
queryErrors,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,14 +203,16 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps
|
|||||||
datasourceInstance,
|
datasourceInstance,
|
||||||
datasourceMissing,
|
datasourceMissing,
|
||||||
exploreDatasources,
|
exploreDatasources,
|
||||||
queryTransactions,
|
|
||||||
range,
|
range,
|
||||||
refreshInterval,
|
refreshInterval,
|
||||||
|
graphIsLoading,
|
||||||
|
logIsLoading,
|
||||||
|
tableIsLoading,
|
||||||
} = exploreItem;
|
} = exploreItem;
|
||||||
const selectedDatasource = datasourceInstance
|
const selectedDatasource = datasourceInstance
|
||||||
? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name)
|
? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name)
|
||||||
: undefined;
|
: undefined;
|
||||||
const loading = queryTransactions.some(qt => !qt.done);
|
const loading = graphIsLoading || logIsLoading || tableIsLoading;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
datasourceMissing,
|
datasourceMissing,
|
||||||
|
@ -71,8 +71,8 @@ function mapStateToProps(state: StoreState, { exploreId }) {
|
|||||||
const explore = state.explore;
|
const explore = state.explore;
|
||||||
const { split } = explore;
|
const { split } = explore;
|
||||||
const item: ExploreItemState = explore[exploreId];
|
const item: ExploreItemState = explore[exploreId];
|
||||||
const { graphResult, queryTransactions, range, showingGraph, showingTable } = item;
|
const { graphResult, graphIsLoading, range, showingGraph, showingTable } = item;
|
||||||
const loading = queryTransactions.some(qt => qt.resultType === 'Graph' && !qt.done);
|
const loading = graphIsLoading;
|
||||||
return { graphResult, loading, range, showingGraph, showingTable, split, timeZone: getTimeZone(state.user) };
|
return { graphResult, loading, range, showingGraph, showingTable, split, timeZone: getTimeZone(state.user) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,8 +113,8 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
|
|||||||
function mapStateToProps(state: StoreState, { exploreId }) {
|
function mapStateToProps(state: StoreState, { exploreId }) {
|
||||||
const explore = state.explore;
|
const explore = state.explore;
|
||||||
const item: ExploreItemState = explore[exploreId];
|
const item: ExploreItemState = explore[exploreId];
|
||||||
const { logsHighlighterExpressions, logsResult, queryTransactions, scanning, scanRange, range } = item;
|
const { logsHighlighterExpressions, logsResult, logIsLoading, scanning, scanRange, range } = item;
|
||||||
const loading = queryTransactions.some(qt => qt.resultType === 'Logs' && !qt.done);
|
const loading = logIsLoading;
|
||||||
const { showingLogs, dedupStrategy } = exploreItemUIStateSelector(item);
|
const { showingLogs, dedupStrategy } = exploreItemUIStateSelector(item);
|
||||||
const hiddenLogLevels = new Set(item.hiddenLogLevels);
|
const hiddenLogLevels = new Set(item.hiddenLogLevels);
|
||||||
const dedupedResult = deduplicatedLogsSelector(item);
|
const dedupedResult = deduplicatedLogsSelector(item);
|
||||||
|
@ -12,8 +12,8 @@ import 'app/features/plugins/plugin_loader';
|
|||||||
import { dateTime } from '@grafana/ui/src/utils/moment_wrapper';
|
import { dateTime } from '@grafana/ui/src/utils/moment_wrapper';
|
||||||
|
|
||||||
interface QueryEditorProps {
|
interface QueryEditorProps {
|
||||||
|
error?: any;
|
||||||
datasource: any;
|
datasource: any;
|
||||||
error?: string | JSX.Element;
|
|
||||||
onExecuteQuery?: () => void;
|
onExecuteQuery?: () => void;
|
||||||
onQueryChange?: (value: DataQuery) => void;
|
onQueryChange?: (value: DataQuery) => void;
|
||||||
initialQuery: DataQuery;
|
initialQuery: DataQuery;
|
||||||
@ -57,6 +57,14 @@ export default class QueryEditor extends PureComponent<QueryEditorProps, any> {
|
|||||||
this.props.onQueryChange(target);
|
this.props.onQueryChange(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: QueryEditorProps) {
|
||||||
|
if (prevProps.error !== this.props.error && this.component) {
|
||||||
|
// Some query controllers listen to data error events and need a digest
|
||||||
|
// for some reason this needs to be done in next tick
|
||||||
|
setTimeout(this.component.digest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.component) {
|
if (this.component) {
|
||||||
this.component.destroy();
|
this.component.destroy();
|
||||||
|
@ -36,8 +36,8 @@ export interface QueryFieldProps {
|
|||||||
cleanText?: (text: string) => string;
|
cleanText?: (text: string) => string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
initialQuery: string | null;
|
initialQuery: string | null;
|
||||||
onExecuteQuery?: () => void;
|
onRunQuery?: () => void;
|
||||||
onQueryChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput;
|
onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput;
|
||||||
onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string;
|
onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@ -149,7 +149,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
if (documentChanged) {
|
if (documentChanged) {
|
||||||
const textChanged = Plain.serialize(prevValue) !== Plain.serialize(value);
|
const textChanged = Plain.serialize(prevValue) !== Plain.serialize(value);
|
||||||
if (textChanged && invokeParentOnValueChanged) {
|
if (textChanged && invokeParentOnValueChanged) {
|
||||||
this.executeOnQueryChangeAndExecuteQueries();
|
this.executeOnChangeAndRunQueries();
|
||||||
}
|
}
|
||||||
if (textChanged && !invokeParentOnValueChanged) {
|
if (textChanged && !invokeParentOnValueChanged) {
|
||||||
this.updateLogsHighlights();
|
this.updateLogsHighlights();
|
||||||
@ -167,21 +167,21 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
};
|
};
|
||||||
|
|
||||||
updateLogsHighlights = () => {
|
updateLogsHighlights = () => {
|
||||||
const { onQueryChange } = this.props;
|
const { onChange } = this.props;
|
||||||
if (onQueryChange) {
|
if (onChange) {
|
||||||
onQueryChange(Plain.serialize(this.state.value));
|
onChange(Plain.serialize(this.state.value));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
executeOnQueryChangeAndExecuteQueries = () => {
|
executeOnChangeAndRunQueries = () => {
|
||||||
// Send text change to parent
|
// Send text change to parent
|
||||||
const { onQueryChange, onExecuteQuery } = this.props;
|
const { onChange, onRunQuery } = this.props;
|
||||||
if (onQueryChange) {
|
if (onChange) {
|
||||||
onQueryChange(Plain.serialize(this.state.value));
|
onChange(Plain.serialize(this.state.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onExecuteQuery) {
|
if (onRunQuery) {
|
||||||
onExecuteQuery();
|
onRunQuery();
|
||||||
this.setState({ lastExecutedValue: this.state.value });
|
this.setState({ lastExecutedValue: this.state.value });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -330,7 +330,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this.executeOnQueryChangeAndExecuteQueries();
|
this.executeOnChangeAndRunQueries();
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -413,7 +413,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
this.placeholdersBuffer.clearPlaceholders();
|
this.placeholdersBuffer.clearPlaceholders();
|
||||||
|
|
||||||
if (previousValue !== currentValue) {
|
if (previousValue !== currentValue) {
|
||||||
this.executeOnQueryChangeAndExecuteQueries();
|
this.executeOnChangeAndRunQueries();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,25 +7,26 @@ import { connect } from 'react-redux';
|
|||||||
|
|
||||||
// Components
|
// Components
|
||||||
import QueryEditor from './QueryEditor';
|
import QueryEditor from './QueryEditor';
|
||||||
import QueryTransactionStatus from './QueryTransactionStatus';
|
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
import { changeQuery, modifyQueries, runQueries, addQueryRow } from './state/actions';
|
import { changeQuery, modifyQueries, runQueries, addQueryRow } from './state/actions';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
import { DataQuery, ExploreDataSourceApi, QueryHint, QueryFixAction, DataSourceStatus, TimeRange } from '@grafana/ui';
|
import {
|
||||||
import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore';
|
TimeRange,
|
||||||
|
DataQuery,
|
||||||
|
ExploreDataSourceApi,
|
||||||
|
QueryFixAction,
|
||||||
|
DataSourceStatus,
|
||||||
|
PanelData,
|
||||||
|
LoadingState,
|
||||||
|
DataQueryError,
|
||||||
|
} from '@grafana/ui';
|
||||||
|
import { HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore';
|
||||||
import { Emitter } from 'app/core/utils/emitter';
|
import { Emitter } from 'app/core/utils/emitter';
|
||||||
import { highlightLogsExpressionAction, removeQueryRowAction } from './state/actionTypes';
|
import { highlightLogsExpressionAction, removeQueryRowAction } from './state/actionTypes';
|
||||||
|
import QueryStatus from './QueryStatus';
|
||||||
function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint {
|
|
||||||
const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0);
|
|
||||||
if (transaction) {
|
|
||||||
return transaction.hints[0];
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface QueryRowProps {
|
interface QueryRowProps {
|
||||||
addQueryRow: typeof addQueryRow;
|
addQueryRow: typeof addQueryRow;
|
||||||
@ -39,20 +40,22 @@ interface QueryRowProps {
|
|||||||
index: number;
|
index: number;
|
||||||
query: DataQuery;
|
query: DataQuery;
|
||||||
modifyQueries: typeof modifyQueries;
|
modifyQueries: typeof modifyQueries;
|
||||||
queryTransactions: QueryTransaction[];
|
|
||||||
exploreEvents: Emitter;
|
exploreEvents: Emitter;
|
||||||
range: TimeRange;
|
range: TimeRange;
|
||||||
removeQueryRowAction: typeof removeQueryRowAction;
|
removeQueryRowAction: typeof removeQueryRowAction;
|
||||||
runQueries: typeof runQueries;
|
runQueries: typeof runQueries;
|
||||||
|
queryResponse: PanelData;
|
||||||
|
latency: number;
|
||||||
|
queryErrors: DataQueryError[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryRow extends PureComponent<QueryRowProps> {
|
export class QueryRow extends PureComponent<QueryRowProps> {
|
||||||
onExecuteQuery = () => {
|
onRunQuery = () => {
|
||||||
const { exploreId } = this.props;
|
const { exploreId } = this.props;
|
||||||
this.props.runQueries(exploreId);
|
this.props.runQueries(exploreId);
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeQuery = (query: DataQuery, override?: boolean) => {
|
onChange = (query: DataQuery, override?: boolean) => {
|
||||||
const { datasourceInstance, exploreId, index } = this.props;
|
const { datasourceInstance, exploreId, index } = this.props;
|
||||||
this.props.changeQuery(exploreId, query, index, override);
|
this.props.changeQuery(exploreId, query, index, override);
|
||||||
if (query && !override && datasourceInstance.getHighlighterExpression && index === 0) {
|
if (query && !override && datasourceInstance.getHighlighterExpression && index === 0) {
|
||||||
@ -71,7 +74,7 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onClickClearButton = () => {
|
onClickClearButton = () => {
|
||||||
this.onChangeQuery(null, true);
|
this.onChange(null, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickHintFix = (action: QueryFixAction) => {
|
onClickHintFix = (action: QueryFixAction) => {
|
||||||
@ -85,6 +88,7 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
|||||||
onClickRemoveButton = () => {
|
onClickRemoveButton = () => {
|
||||||
const { exploreId, index } = this.props;
|
const { exploreId, index } = this.props;
|
||||||
this.props.removeQueryRowAction({ exploreId, index });
|
this.props.removeQueryRowAction({ exploreId, index });
|
||||||
|
this.props.runQueries(exploreId);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateLogsHighlights = _.debounce((value: DataQuery) => {
|
updateLogsHighlights = _.debounce((value: DataQuery) => {
|
||||||
@ -100,24 +104,20 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
|||||||
const {
|
const {
|
||||||
datasourceInstance,
|
datasourceInstance,
|
||||||
history,
|
history,
|
||||||
index,
|
|
||||||
query,
|
query,
|
||||||
queryTransactions,
|
|
||||||
exploreEvents,
|
exploreEvents,
|
||||||
range,
|
range,
|
||||||
datasourceStatus,
|
datasourceStatus,
|
||||||
|
queryResponse,
|
||||||
|
latency,
|
||||||
|
queryErrors,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const transactions = queryTransactions.filter(t => t.rowIndex === index);
|
|
||||||
const transactionWithError = transactions.find(t => t.error !== undefined);
|
|
||||||
const hint = getFirstHintFromTransactions(transactions);
|
|
||||||
const queryError = transactionWithError ? transactionWithError.error : null;
|
|
||||||
const QueryField = datasourceInstance.components.ExploreQueryField;
|
const QueryField = datasourceInstance.components.ExploreQueryField;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="query-row">
|
<div className="query-row">
|
||||||
<div className="query-row-status">
|
<div className="query-row-status">
|
||||||
<QueryTransactionStatus transactions={transactions} />
|
<QueryStatus queryResponse={queryResponse} latency={latency} />
|
||||||
</div>
|
</div>
|
||||||
<div className="query-row-field flex-shrink-1">
|
<div className="query-row-field flex-shrink-1">
|
||||||
{QueryField ? (
|
{QueryField ? (
|
||||||
@ -125,19 +125,19 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
|||||||
datasource={datasourceInstance}
|
datasource={datasourceInstance}
|
||||||
datasourceStatus={datasourceStatus}
|
datasourceStatus={datasourceStatus}
|
||||||
query={query}
|
query={query}
|
||||||
error={queryError}
|
|
||||||
hint={hint}
|
|
||||||
history={history}
|
history={history}
|
||||||
onExecuteQuery={this.onExecuteQuery}
|
onRunQuery={this.onRunQuery}
|
||||||
onExecuteHint={this.onClickHintFix}
|
onHint={this.onClickHintFix}
|
||||||
onQueryChange={this.onChangeQuery}
|
onChange={this.onChange}
|
||||||
|
panelData={null}
|
||||||
|
queryResponse={queryResponse}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<QueryEditor
|
<QueryEditor
|
||||||
|
error={queryErrors}
|
||||||
datasource={datasourceInstance}
|
datasource={datasourceInstance}
|
||||||
error={queryError}
|
onQueryChange={this.onChange}
|
||||||
onQueryChange={this.onChangeQuery}
|
onExecuteQuery={this.onRunQuery}
|
||||||
onExecuteQuery={this.onExecuteQuery}
|
|
||||||
initialQuery={query}
|
initialQuery={query}
|
||||||
exploreEvents={exploreEvents}
|
exploreEvents={exploreEvents}
|
||||||
range={range}
|
range={range}
|
||||||
@ -169,15 +169,44 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
|||||||
function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps) {
|
function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps) {
|
||||||
const explore = state.explore;
|
const explore = state.explore;
|
||||||
const item: ExploreItemState = explore[exploreId];
|
const item: ExploreItemState = explore[exploreId];
|
||||||
const { datasourceInstance, history, queries, queryTransactions, range, datasourceError } = item;
|
const {
|
||||||
|
datasourceInstance,
|
||||||
|
history,
|
||||||
|
queries,
|
||||||
|
range,
|
||||||
|
datasourceError,
|
||||||
|
graphResult,
|
||||||
|
graphIsLoading,
|
||||||
|
tableIsLoading,
|
||||||
|
logIsLoading,
|
||||||
|
latency,
|
||||||
|
queryErrors,
|
||||||
|
} = item;
|
||||||
const query = queries[index];
|
const query = queries[index];
|
||||||
|
const datasourceStatus = datasourceError ? DataSourceStatus.Disconnected : DataSourceStatus.Connected;
|
||||||
|
const error = queryErrors.filter(queryError => queryError.refId === query.refId)[0];
|
||||||
|
const series = graphResult ? graphResult : []; // TODO: use SeriesData
|
||||||
|
const queryResponseState =
|
||||||
|
graphIsLoading || tableIsLoading || logIsLoading
|
||||||
|
? LoadingState.Loading
|
||||||
|
: error
|
||||||
|
? LoadingState.Error
|
||||||
|
: LoadingState.Done;
|
||||||
|
const queryResponse: PanelData = {
|
||||||
|
series,
|
||||||
|
state: queryResponseState,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
datasourceInstance,
|
datasourceInstance,
|
||||||
history,
|
history,
|
||||||
query,
|
query,
|
||||||
queryTransactions,
|
|
||||||
range,
|
range,
|
||||||
datasourceStatus: datasourceError ? DataSourceStatus.Disconnected : DataSourceStatus.Connected,
|
datasourceStatus,
|
||||||
|
queryResponse,
|
||||||
|
latency,
|
||||||
|
queryErrors,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
47
public/app/features/explore/QueryStatus.tsx
Normal file
47
public/app/features/explore/QueryStatus.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
import ElapsedTime from './ElapsedTime';
|
||||||
|
import { PanelData, LoadingState } from '@grafana/ui';
|
||||||
|
|
||||||
|
function formatLatency(value) {
|
||||||
|
return `${(value / 1000).toFixed(1)}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryStatusItemProps {
|
||||||
|
queryResponse: PanelData;
|
||||||
|
latency: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class QueryStatusItem extends PureComponent<QueryStatusItemProps> {
|
||||||
|
render() {
|
||||||
|
const { queryResponse, latency } = this.props;
|
||||||
|
const className =
|
||||||
|
queryResponse.state === LoadingState.Done || LoadingState.Error
|
||||||
|
? 'query-transaction'
|
||||||
|
: 'query-transaction query-transaction--loading';
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
{/* <div className="query-transaction__type">{transaction.resultType}:</div> */}
|
||||||
|
<div className="query-transaction__duration">
|
||||||
|
{queryResponse.state === LoadingState.Done || LoadingState.Error ? formatLatency(latency) : <ElapsedTime />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryStatusProps {
|
||||||
|
queryResponse: PanelData;
|
||||||
|
latency: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class QueryStatus extends PureComponent<QueryStatusProps> {
|
||||||
|
render() {
|
||||||
|
const { queryResponse, latency } = this.props;
|
||||||
|
return (
|
||||||
|
<div className="query-transactions">
|
||||||
|
{queryResponse && <QueryStatusItem queryResponse={queryResponse} latency={latency} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,44 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { QueryTransaction } from 'app/types/explore';
|
|
||||||
import ElapsedTime from './ElapsedTime';
|
|
||||||
|
|
||||||
function formatLatency(value) {
|
|
||||||
return `${(value / 1000).toFixed(1)}s`;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface QueryTransactionStatusItemProps {
|
|
||||||
transaction: QueryTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
class QueryTransactionStatusItem extends PureComponent<QueryTransactionStatusItemProps> {
|
|
||||||
render() {
|
|
||||||
const { transaction } = this.props;
|
|
||||||
const className = transaction.done ? 'query-transaction' : 'query-transaction query-transaction--loading';
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
<div className="query-transaction__type">{transaction.resultType}:</div>
|
|
||||||
<div className="query-transaction__duration">
|
|
||||||
{transaction.done ? formatLatency(transaction.latency) : <ElapsedTime />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface QueryTransactionStatusProps {
|
|
||||||
transactions: QueryTransaction[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class QueryTransactionStatus extends PureComponent<QueryTransactionStatusProps> {
|
|
||||||
render() {
|
|
||||||
const { transactions } = this.props;
|
|
||||||
return (
|
|
||||||
<div className="query-transactions">
|
|
||||||
{transactions.map((t, i) => (
|
|
||||||
<QueryTransactionStatusItem key={`${t.rowIndex}:${t.resultType}`} transaction={t} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -42,8 +42,8 @@ export class TableContainer extends PureComponent<TableContainerProps> {
|
|||||||
function mapStateToProps(state: StoreState, { exploreId }) {
|
function mapStateToProps(state: StoreState, { exploreId }) {
|
||||||
const explore = state.explore;
|
const explore = state.explore;
|
||||||
const item: ExploreItemState = explore[exploreId];
|
const item: ExploreItemState = explore[exploreId];
|
||||||
const { queryTransactions, showingTable, tableResult } = item;
|
const { tableIsLoading, showingTable, tableResult } = item;
|
||||||
const loading = queryTransactions.some(qt => qt.resultType === 'Table' && !qt.done);
|
const loading = tableIsLoading;
|
||||||
return { loading, showingTable, tableResult };
|
return { loading, showingTable, tableResult };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
QueryFixAction,
|
QueryFixAction,
|
||||||
LogLevel,
|
LogLevel,
|
||||||
TimeRange,
|
TimeRange,
|
||||||
|
DataQueryError,
|
||||||
} from '@grafana/ui/src/types';
|
} from '@grafana/ui/src/types';
|
||||||
import {
|
import {
|
||||||
ExploreId,
|
ExploreId,
|
||||||
@ -132,22 +133,29 @@ export interface ModifyQueriesPayload {
|
|||||||
modifier: (query: DataQuery, modification: QueryFixAction) => DataQuery;
|
modifier: (query: DataQuery, modification: QueryFixAction) => DataQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryTransactionFailurePayload {
|
export interface QueryFailurePayload {
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
queryTransactions: QueryTransaction[];
|
response: DataQueryError;
|
||||||
|
resultType: ResultType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryTransactionStartPayload {
|
export interface QueryStartPayload {
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
resultType: ResultType;
|
resultType: ResultType;
|
||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
transaction: QueryTransaction;
|
transaction: QueryTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryTransactionSuccessPayload {
|
export interface QuerySuccessPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
result: any;
|
||||||
|
resultType: ResultType;
|
||||||
|
latency: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HistoryUpdatedPayload {
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
history: HistoryItem[];
|
history: HistoryItem[];
|
||||||
queryTransactions: QueryTransaction[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RemoveQueryRowPayload {
|
export interface RemoveQueryRowPayload {
|
||||||
@ -222,6 +230,11 @@ export interface RunQueriesPayload {
|
|||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResetQueryErrorPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
refIds: string[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a query row after the row with the given index.
|
* Adds a query row after the row with the given index.
|
||||||
*/
|
*/
|
||||||
@ -310,9 +323,7 @@ export const modifyQueriesAction = actionCreatorFactory<ModifyQueriesPayload>('e
|
|||||||
* Mark a query transaction as failed with an error extracted from the query response.
|
* Mark a query transaction as failed with an error extracted from the query response.
|
||||||
* The transaction will be marked as `done`.
|
* The transaction will be marked as `done`.
|
||||||
*/
|
*/
|
||||||
export const queryTransactionFailureAction = actionCreatorFactory<QueryTransactionFailurePayload>(
|
export const queryFailureAction = actionCreatorFactory<QueryFailurePayload>('explore/QUERY_FAILURE').create();
|
||||||
'explore/QUERY_TRANSACTION_FAILURE'
|
|
||||||
).create();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a query transaction for the given result type.
|
* Start a query transaction for the given result type.
|
||||||
@ -321,9 +332,7 @@ export const queryTransactionFailureAction = actionCreatorFactory<QueryTransacti
|
|||||||
* @param resultType Associate the transaction with a result viewer, e.g., Graph
|
* @param resultType Associate the transaction with a result viewer, e.g., Graph
|
||||||
* @param rowIndex Index is used to associate latency for this transaction with a query row
|
* @param rowIndex Index is used to associate latency for this transaction with a query row
|
||||||
*/
|
*/
|
||||||
export const queryTransactionStartAction = actionCreatorFactory<QueryTransactionStartPayload>(
|
export const queryStartAction = actionCreatorFactory<QueryStartPayload>('explore/QUERY_START').create();
|
||||||
'explore/QUERY_TRANSACTION_START'
|
|
||||||
).create();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete a query transaction, mark the transaction as `done` and store query state in URL.
|
* Complete a query transaction, mark the transaction as `done` and store query state in URL.
|
||||||
@ -336,9 +345,7 @@ export const queryTransactionStartAction = actionCreatorFactory<QueryTransaction
|
|||||||
* @param queries Queries from all query rows
|
* @param queries Queries from all query rows
|
||||||
* @param datasourceId Origin datasource instance, used to discard results if current datasource is different
|
* @param datasourceId Origin datasource instance, used to discard results if current datasource is different
|
||||||
*/
|
*/
|
||||||
export const queryTransactionSuccessAction = actionCreatorFactory<QueryTransactionSuccessPayload>(
|
export const querySuccessAction = actionCreatorFactory<QuerySuccessPayload>('explore/QUERY_SUCCESS').create();
|
||||||
'explore/QUERY_TRANSACTION_SUCCESS'
|
|
||||||
).create();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove query row of the given index, as well as associated query results.
|
* Remove query row of the given index, as well as associated query results.
|
||||||
@ -426,6 +433,10 @@ export const loadExploreDatasources = actionCreatorFactory<LoadExploreDataSource
|
|||||||
'explore/LOAD_EXPLORE_DATASOURCES'
|
'explore/LOAD_EXPLORE_DATASOURCES'
|
||||||
).create();
|
).create();
|
||||||
|
|
||||||
|
export const historyUpdatedAction = actionCreatorFactory<HistoryUpdatedPayload>('explore/HISTORY_UPDATED').create();
|
||||||
|
|
||||||
|
export const resetQueryErrorAction = actionCreatorFactory<ResetQueryErrorPayload>('explore/RESET_QUERY_ERROR').create();
|
||||||
|
|
||||||
export type HigherOrderAction =
|
export type HigherOrderAction =
|
||||||
| ActionOf<SplitCloseActionPayload>
|
| ActionOf<SplitCloseActionPayload>
|
||||||
| SplitOpenAction
|
| SplitOpenAction
|
||||||
|
@ -18,20 +18,21 @@ import {
|
|||||||
parseUrlState,
|
parseUrlState,
|
||||||
getTimeRange,
|
getTimeRange,
|
||||||
getTimeRangeFromUrl,
|
getTimeRangeFromUrl,
|
||||||
|
generateNewKeyAndAddRefIdIfMissing,
|
||||||
|
instanceOfDataQueryError,
|
||||||
|
getRefIds,
|
||||||
} from 'app/core/utils/explore';
|
} from 'app/core/utils/explore';
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
import { updateLocation } from 'app/core/actions';
|
import { updateLocation } from 'app/core/actions';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { ResultGetter } from 'app/types/explore';
|
|
||||||
import { ThunkResult } from 'app/types';
|
import { ThunkResult } from 'app/types';
|
||||||
import {
|
import {
|
||||||
RawTimeRange,
|
RawTimeRange,
|
||||||
DataSourceApi,
|
DataSourceApi,
|
||||||
DataQuery,
|
DataQuery,
|
||||||
DataSourceSelectItem,
|
DataSourceSelectItem,
|
||||||
QueryHint,
|
|
||||||
QueryFixAction,
|
QueryFixAction,
|
||||||
TimeRange,
|
TimeRange,
|
||||||
} from '@grafana/ui/src/types';
|
} from '@grafana/ui/src/types';
|
||||||
@ -61,9 +62,8 @@ import {
|
|||||||
LoadDatasourceReadyPayload,
|
LoadDatasourceReadyPayload,
|
||||||
loadDatasourceReadyAction,
|
loadDatasourceReadyAction,
|
||||||
modifyQueriesAction,
|
modifyQueriesAction,
|
||||||
queryTransactionFailureAction,
|
queryFailureAction,
|
||||||
queryTransactionStartAction,
|
querySuccessAction,
|
||||||
queryTransactionSuccessAction,
|
|
||||||
scanRangeAction,
|
scanRangeAction,
|
||||||
scanStartAction,
|
scanStartAction,
|
||||||
setQueriesAction,
|
setQueriesAction,
|
||||||
@ -82,11 +82,15 @@ import {
|
|||||||
testDataSourceSuccessAction,
|
testDataSourceSuccessAction,
|
||||||
testDataSourceFailureAction,
|
testDataSourceFailureAction,
|
||||||
loadExploreDatasources,
|
loadExploreDatasources,
|
||||||
|
queryStartAction,
|
||||||
|
historyUpdatedAction,
|
||||||
|
resetQueryErrorAction,
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
|
import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
|
||||||
import { LogsDedupStrategy } from 'app/core/logs_model';
|
import { LogsDedupStrategy } from 'app/core/logs_model';
|
||||||
import { getTimeZone } from 'app/features/profile/state/selectors';
|
import { getTimeZone } from 'app/features/profile/state/selectors';
|
||||||
import { isDateTime } from '@grafana/ui/src/utils/moment_wrapper';
|
import { isDateTime } from '@grafana/ui/src/utils/moment_wrapper';
|
||||||
|
import { toDataQueryError } from 'app/features/dashboard/state/PanelQueryState';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates UI state and save it to the URL
|
* Updates UI state and save it to the URL
|
||||||
@ -103,7 +107,8 @@ const updateExploreUIState = (exploreId: ExploreId, uiStateFragment: Partial<Exp
|
|||||||
*/
|
*/
|
||||||
export function addQueryRow(exploreId: ExploreId, index: number): ThunkResult<void> {
|
export function addQueryRow(exploreId: ExploreId, index: number): ThunkResult<void> {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const query = generateEmptyQuery(getState().explore[exploreId].queries, index);
|
const queries = getState().explore[exploreId].queries;
|
||||||
|
const query = generateEmptyQuery(queries, index);
|
||||||
|
|
||||||
dispatch(addQueryRowAction({ exploreId, index, query }));
|
dispatch(addQueryRowAction({ exploreId, index, query }));
|
||||||
};
|
};
|
||||||
@ -148,7 +153,9 @@ export function changeQuery(
|
|||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
// Null query means reset
|
// Null query means reset
|
||||||
if (query === null) {
|
if (query === null) {
|
||||||
query = { ...generateEmptyQuery(getState().explore[exploreId].queries) };
|
const queries = getState().explore[exploreId].queries;
|
||||||
|
const { refId, key } = queries[index];
|
||||||
|
query = generateNewKeyAndAddRefIdIfMissing({ refId, key }, queries, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(changeQueryAction({ exploreId, query, index, override }));
|
dispatch(changeQueryAction({ exploreId, query, index, override }));
|
||||||
@ -306,10 +313,7 @@ export function importQueries(
|
|||||||
importedQueries = ensureQueries();
|
importedQueries = ensureQueries();
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextQueries = importedQueries.map((q, i) => ({
|
const nextQueries = ensureQueries(importedQueries);
|
||||||
...q,
|
|
||||||
...generateEmptyQuery(queries),
|
|
||||||
}));
|
|
||||||
|
|
||||||
dispatch(queriesImportedAction({ exploreId, queries: nextQueries }));
|
dispatch(queriesImportedAction({ exploreId, queries: nextQueries }));
|
||||||
};
|
};
|
||||||
@ -368,7 +372,11 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (instance.init) {
|
if (instance.init) {
|
||||||
|
try {
|
||||||
instance.init();
|
instance.init();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) {
|
if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) {
|
||||||
@ -401,140 +409,87 @@ export function modifyQueries(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function processQueryErrors(
|
||||||
* Mark a query transaction as failed with an error extracted from the query response.
|
|
||||||
* The transaction will be marked as `done`.
|
|
||||||
*/
|
|
||||||
export function queryTransactionFailure(
|
|
||||||
exploreId: ExploreId,
|
exploreId: ExploreId,
|
||||||
transactionId: string,
|
|
||||||
response: any,
|
response: any,
|
||||||
|
resultType: ResultType,
|
||||||
datasourceId: string
|
datasourceId: string
|
||||||
): ThunkResult<void> {
|
): ThunkResult<void> {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { datasourceInstance, queryTransactions } = getState().explore[exploreId];
|
const { datasourceInstance } = getState().explore[exploreId];
|
||||||
|
|
||||||
if (datasourceInstance.meta.id !== datasourceId || response.cancelled) {
|
if (datasourceInstance.meta.id !== datasourceId || response.cancelled) {
|
||||||
// Navigated away, queries did not matter
|
// Navigated away, queries did not matter
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transaction might have been discarded
|
console.error(response); // To help finding problems with query syntax
|
||||||
if (!queryTransactions.find(qt => qt.id === transactionId)) {
|
|
||||||
return;
|
if (!instanceOfDataQueryError(response)) {
|
||||||
|
response = toDataQueryError(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(response);
|
dispatch(
|
||||||
|
queryFailureAction({
|
||||||
let error: string;
|
exploreId,
|
||||||
let errorDetails: string;
|
response,
|
||||||
if (response.data) {
|
resultType,
|
||||||
if (typeof response.data === 'string') {
|
})
|
||||||
error = response.data;
|
);
|
||||||
} else if (response.data.error) {
|
|
||||||
error = response.data.error;
|
|
||||||
if (response.data.response) {
|
|
||||||
errorDetails = response.data.response;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('Could not handle error response');
|
|
||||||
}
|
|
||||||
} else if (response.message) {
|
|
||||||
error = response.message;
|
|
||||||
} else if (typeof response === 'string') {
|
|
||||||
error = response;
|
|
||||||
} else {
|
|
||||||
error = 'Unknown error during query transaction. Please check JS console logs.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark transactions as complete
|
|
||||||
const nextQueryTransactions = queryTransactions.map(qt => {
|
|
||||||
if (qt.id === transactionId) {
|
|
||||||
return {
|
|
||||||
...qt,
|
|
||||||
error,
|
|
||||||
errorDetails,
|
|
||||||
done: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return qt;
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(queryTransactionFailureAction({ exploreId, queryTransactions: nextQueryTransactions }));
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete a query transaction, mark the transaction as `done` and store query state in URL.
|
|
||||||
* If the transaction was started by a scanner, it keeps on scanning for more results.
|
|
||||||
* Side-effect: the query is stored in localStorage.
|
|
||||||
* @param exploreId Explore area
|
* @param exploreId Explore area
|
||||||
* @param transactionId ID
|
* @param response Response from `datasourceInstance.query()`
|
||||||
* @param result Response from `datasourceInstance.query()`
|
|
||||||
* @param latency Duration between request and response
|
* @param latency Duration between request and response
|
||||||
* @param queries Queries from all query rows
|
* @param resultType The type of result
|
||||||
* @param datasourceId Origin datasource instance, used to discard results if current datasource is different
|
* @param datasourceId Origin datasource instance, used to discard results if current datasource is different
|
||||||
*/
|
*/
|
||||||
export function queryTransactionSuccess(
|
export function processQueryResults(
|
||||||
exploreId: ExploreId,
|
exploreId: ExploreId,
|
||||||
transactionId: string,
|
response: any,
|
||||||
result: any,
|
|
||||||
latency: number,
|
latency: number,
|
||||||
queries: DataQuery[],
|
resultType: ResultType,
|
||||||
datasourceId: string
|
datasourceId: string
|
||||||
): ThunkResult<void> {
|
): ThunkResult<void> {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { datasourceInstance, history, queryTransactions, scanner, scanning } = getState().explore[exploreId];
|
const { datasourceInstance, scanning, scanner } = getState().explore[exploreId];
|
||||||
|
|
||||||
// If datasource already changed, results do not matter
|
// If datasource already changed, results do not matter
|
||||||
if (datasourceInstance.meta.id !== datasourceId) {
|
if (datasourceInstance.meta.id !== datasourceId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transaction might have been discarded
|
const series: any[] = response.data;
|
||||||
const transaction = queryTransactions.find(qt => qt.id === transactionId);
|
const refIds = getRefIds(series);
|
||||||
if (!transaction) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get query hints
|
// Clears any previous errors that now have a successful query, important so Angular editors are updated correctly
|
||||||
let hints: QueryHint[];
|
dispatch(
|
||||||
if (datasourceInstance.getQueryHints) {
|
resetQueryErrorAction({
|
||||||
hints = datasourceInstance.getQueryHints(transaction.query, result);
|
exploreId,
|
||||||
}
|
refIds,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Mark transactions as complete and attach result
|
const resultGetter =
|
||||||
const nextQueryTransactions = queryTransactions.map(qt => {
|
resultType === 'Graph' ? makeTimeSeriesList : resultType === 'Table' ? (data: any[]) => data : null;
|
||||||
if (qt.id === transactionId) {
|
const result = resultGetter ? resultGetter(series, null, []) : series;
|
||||||
return {
|
|
||||||
...qt,
|
|
||||||
hints,
|
|
||||||
latency,
|
|
||||||
result,
|
|
||||||
done: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return qt;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Side-effect: Saving history in localstorage
|
|
||||||
const nextHistory = updateHistory(history, datasourceId, queries);
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
queryTransactionSuccessAction({
|
querySuccessAction({
|
||||||
exploreId,
|
exploreId,
|
||||||
history: nextHistory,
|
result,
|
||||||
queryTransactions: nextQueryTransactions,
|
resultType,
|
||||||
|
latency,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Keep scanning for results if this was the last scanning transaction
|
// Keep scanning for results if this was the last scanning transaction
|
||||||
if (scanning) {
|
if (scanning) {
|
||||||
if (_.size(result) === 0) {
|
if (_.size(result) === 0) {
|
||||||
const other = nextQueryTransactions.find(qt => qt.scanning && !qt.done);
|
|
||||||
if (!other) {
|
|
||||||
const range = scanner();
|
const range = scanner();
|
||||||
dispatch(scanRangeAction({ exploreId, range }));
|
dispatch(scanRangeAction({ exploreId, range }));
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// We can stop scanning if we have a result
|
// We can stop scanning if we have a result
|
||||||
dispatch(scanStopAction({ exploreId }));
|
dispatch(scanStopAction({ exploreId }));
|
||||||
@ -580,32 +535,22 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkRe
|
|||||||
// Keep table queries first since they need to return quickly
|
// Keep table queries first since they need to return quickly
|
||||||
if ((ignoreUIState || showingTable) && supportsTable) {
|
if ((ignoreUIState || showingTable) && supportsTable) {
|
||||||
dispatch(
|
dispatch(
|
||||||
runQueriesForType(
|
runQueriesForType(exploreId, 'Table', {
|
||||||
exploreId,
|
|
||||||
'Table',
|
|
||||||
{
|
|
||||||
interval,
|
interval,
|
||||||
format: 'table',
|
format: 'table',
|
||||||
instant: true,
|
instant: true,
|
||||||
valueWithRefId: true,
|
valueWithRefId: true,
|
||||||
},
|
})
|
||||||
(data: any[]) => data[0]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if ((ignoreUIState || showingGraph) && supportsGraph) {
|
if ((ignoreUIState || showingGraph) && supportsGraph) {
|
||||||
dispatch(
|
dispatch(
|
||||||
runQueriesForType(
|
runQueriesForType(exploreId, 'Graph', {
|
||||||
exploreId,
|
|
||||||
'Graph',
|
|
||||||
{
|
|
||||||
interval,
|
interval,
|
||||||
format: 'time_series',
|
format: 'time_series',
|
||||||
instant: false,
|
instant: false,
|
||||||
maxDataPoints: containerWidth,
|
maxDataPoints: containerWidth,
|
||||||
},
|
})
|
||||||
makeTimeSeriesList
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if ((ignoreUIState || showingLogs) && supportsLogs) {
|
if ((ignoreUIState || showingLogs) && supportsLogs) {
|
||||||
@ -626,37 +571,27 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkRe
|
|||||||
function runQueriesForType(
|
function runQueriesForType(
|
||||||
exploreId: ExploreId,
|
exploreId: ExploreId,
|
||||||
resultType: ResultType,
|
resultType: ResultType,
|
||||||
queryOptions: QueryOptions,
|
queryOptions: QueryOptions
|
||||||
resultGetter?: ResultGetter
|
|
||||||
): ThunkResult<void> {
|
): ThunkResult<void> {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const { datasourceInstance, eventBridge, queries, queryIntervals, range, scanning } = getState().explore[exploreId];
|
const { datasourceInstance, eventBridge, queries, queryIntervals, range, scanning, history } = getState().explore[
|
||||||
|
exploreId
|
||||||
|
];
|
||||||
const datasourceId = datasourceInstance.meta.id;
|
const datasourceId = datasourceInstance.meta.id;
|
||||||
// Run all queries concurrently
|
const transaction = buildQueryTransaction(queries, resultType, queryOptions, range, queryIntervals, scanning);
|
||||||
for (let rowIndex = 0; rowIndex < queries.length; rowIndex++) {
|
dispatch(queryStartAction({ exploreId, resultType, rowIndex: 0, transaction }));
|
||||||
const query = queries[rowIndex];
|
|
||||||
const transaction = buildQueryTransaction(
|
|
||||||
query,
|
|
||||||
rowIndex,
|
|
||||||
resultType,
|
|
||||||
queryOptions,
|
|
||||||
range,
|
|
||||||
queryIntervals,
|
|
||||||
scanning
|
|
||||||
);
|
|
||||||
dispatch(queryTransactionStartAction({ exploreId, resultType, rowIndex, transaction }));
|
|
||||||
try {
|
try {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const res = await datasourceInstance.query(transaction.options);
|
const response = await datasourceInstance.query(transaction.options);
|
||||||
eventBridge.emit('data-received', res.data || []);
|
eventBridge.emit('data-received', response.data || []);
|
||||||
const latency = Date.now() - now;
|
const latency = Date.now() - now;
|
||||||
const { queryTransactions } = getState().explore[exploreId];
|
// Side-effect: Saving history in localstorage
|
||||||
const results = resultGetter ? resultGetter(res.data, transaction, queryTransactions) : res.data;
|
const nextHistory = updateHistory(history, datasourceId, queries);
|
||||||
dispatch(queryTransactionSuccess(exploreId, transaction.id, results, latency, queries, datasourceId));
|
dispatch(historyUpdatedAction({ exploreId, history: nextHistory }));
|
||||||
} catch (response) {
|
dispatch(processQueryResults(exploreId, response, latency, resultType, datasourceId));
|
||||||
eventBridge.emit('data-error', response);
|
} catch (err) {
|
||||||
dispatch(queryTransactionFailure(exploreId, transaction.id, response, datasourceId));
|
eventBridge.emit('data-error', err);
|
||||||
}
|
dispatch(processQueryErrors(exploreId, err, resultType, datasourceId));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -684,8 +619,9 @@ export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkRes
|
|||||||
export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): ThunkResult<void> {
|
export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): ThunkResult<void> {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
// Inject react keys into query objects
|
// Inject react keys into query objects
|
||||||
const queries = rawQueries.map(q => ({ ...q, ...generateEmptyQuery(getState().explore[exploreId].queries) }));
|
const queries = getState().explore[exploreId].queries;
|
||||||
dispatch(setQueriesAction({ exploreId, queries }));
|
const nextQueries = rawQueries.map((query, index) => generateNewKeyAndAddRefIdIfMissing(query, queries, index));
|
||||||
|
dispatch(setQueriesAction({ exploreId, queries: nextQueries }));
|
||||||
dispatch(runQueries(exploreId));
|
dispatch(runQueries(exploreId));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -849,7 +785,11 @@ export function refreshExplore(exploreId: ExploreId): ThunkResult<void> {
|
|||||||
|
|
||||||
const { urlState, update, containerWidth, eventBridge } = itemState;
|
const { urlState, update, containerWidth, eventBridge } = itemState;
|
||||||
const { datasource, queries, range: urlRange, ui } = urlState;
|
const { datasource, queries, range: urlRange, ui } = urlState;
|
||||||
const refreshQueries = queries.map(q => ({ ...q, ...generateEmptyQuery(itemState.queries) }));
|
const refreshQueries: DataQuery[] = [];
|
||||||
|
for (let index = 0; index < queries.length; index++) {
|
||||||
|
const query = queries[index];
|
||||||
|
refreshQueries.push(generateNewKeyAndAddRefIdIfMissing(query, refreshQueries, index));
|
||||||
|
}
|
||||||
const timeZone = getTimeZone(getState().user);
|
const timeZone = getTimeZone(getState().user);
|
||||||
const range = getTimeRangeFromUrl(urlRange, timeZone);
|
const range = getTimeRangeFromUrl(urlRange, timeZone);
|
||||||
|
|
||||||
|
@ -97,7 +97,6 @@ describe('Explore item reducer', () => {
|
|||||||
const queryTransactions: QueryTransaction[] = [];
|
const queryTransactions: QueryTransaction[] = [];
|
||||||
const initalState: Partial<ExploreItemState> = {
|
const initalState: Partial<ExploreItemState> = {
|
||||||
datasourceError: null,
|
datasourceError: null,
|
||||||
queryTransactions: [{} as QueryTransaction],
|
|
||||||
graphResult: [],
|
graphResult: [],
|
||||||
tableResult: {} as TableModel,
|
tableResult: {} as TableModel,
|
||||||
logsResult: {} as LogsModel,
|
logsResult: {} as LogsModel,
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {
|
import {
|
||||||
calculateResultsFromQueryTransactions,
|
calculateResultsFromQueryTransactions,
|
||||||
generateEmptyQuery,
|
|
||||||
getIntervals,
|
getIntervals,
|
||||||
ensureQueries,
|
ensureQueries,
|
||||||
getQueryKeys,
|
getQueryKeys,
|
||||||
parseUrlState,
|
parseUrlState,
|
||||||
DEFAULT_UI_STATE,
|
DEFAULT_UI_STATE,
|
||||||
|
generateNewKeyAndAddRefIdIfMissing,
|
||||||
} from 'app/core/utils/explore';
|
} from 'app/core/utils/explore';
|
||||||
import { ExploreItemState, ExploreState, QueryTransaction, ExploreId, ExploreUpdateState } from 'app/types/explore';
|
import { ExploreItemState, ExploreState, ExploreId, ExploreUpdateState } from 'app/types/explore';
|
||||||
import { DataQuery } from '@grafana/ui/src/types';
|
import { DataQuery } from '@grafana/ui/src/types';
|
||||||
import {
|
import {
|
||||||
HigherOrderAction,
|
HigherOrderAction,
|
||||||
@ -20,6 +20,8 @@ import {
|
|||||||
SplitCloseActionPayload,
|
SplitCloseActionPayload,
|
||||||
loadExploreDatasources,
|
loadExploreDatasources,
|
||||||
runQueriesAction,
|
runQueriesAction,
|
||||||
|
historyUpdatedAction,
|
||||||
|
resetQueryErrorAction,
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import { reducerFactory } from 'app/core/redux';
|
import { reducerFactory } from 'app/core/redux';
|
||||||
import {
|
import {
|
||||||
@ -36,16 +38,14 @@ import {
|
|||||||
loadDatasourcePendingAction,
|
loadDatasourcePendingAction,
|
||||||
loadDatasourceReadyAction,
|
loadDatasourceReadyAction,
|
||||||
modifyQueriesAction,
|
modifyQueriesAction,
|
||||||
queryTransactionFailureAction,
|
queryFailureAction,
|
||||||
queryTransactionStartAction,
|
queryStartAction,
|
||||||
queryTransactionSuccessAction,
|
querySuccessAction,
|
||||||
removeQueryRowAction,
|
removeQueryRowAction,
|
||||||
scanRangeAction,
|
scanRangeAction,
|
||||||
scanStartAction,
|
scanStartAction,
|
||||||
scanStopAction,
|
scanStopAction,
|
||||||
setQueriesAction,
|
setQueriesAction,
|
||||||
toggleGraphAction,
|
|
||||||
toggleLogsAction,
|
|
||||||
toggleTableAction,
|
toggleTableAction,
|
||||||
queriesImportedAction,
|
queriesImportedAction,
|
||||||
updateUIStateAction,
|
updateUIStateAction,
|
||||||
@ -53,6 +53,7 @@ import {
|
|||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import { updateLocation } from 'app/core/actions/location';
|
import { updateLocation } from 'app/core/actions/location';
|
||||||
import { LocationUpdate } from 'app/types';
|
import { LocationUpdate } from 'app/types';
|
||||||
|
import TableModel from 'app/core/table_model';
|
||||||
|
|
||||||
export const DEFAULT_RANGE = {
|
export const DEFAULT_RANGE = {
|
||||||
from: 'now-6h',
|
from: 'now-6h',
|
||||||
@ -84,7 +85,6 @@ export const makeExploreItemState = (): ExploreItemState => ({
|
|||||||
history: [],
|
history: [],
|
||||||
queries: [],
|
queries: [],
|
||||||
initialized: false,
|
initialized: false,
|
||||||
queryTransactions: [],
|
|
||||||
queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL },
|
queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL },
|
||||||
range: {
|
range: {
|
||||||
from: null,
|
from: null,
|
||||||
@ -96,12 +96,17 @@ export const makeExploreItemState = (): ExploreItemState => ({
|
|||||||
showingGraph: true,
|
showingGraph: true,
|
||||||
showingLogs: true,
|
showingLogs: true,
|
||||||
showingTable: true,
|
showingTable: true,
|
||||||
|
graphIsLoading: false,
|
||||||
|
logIsLoading: false,
|
||||||
|
tableIsLoading: false,
|
||||||
supportsGraph: null,
|
supportsGraph: null,
|
||||||
supportsLogs: null,
|
supportsLogs: null,
|
||||||
supportsTable: null,
|
supportsTable: null,
|
||||||
queryKeys: [],
|
queryKeys: [],
|
||||||
urlState: null,
|
urlState: null,
|
||||||
update: makeInitialUpdateState(),
|
update: makeInitialUpdateState(),
|
||||||
|
queryErrors: [],
|
||||||
|
latency: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,28 +126,16 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
.addMapper({
|
.addMapper({
|
||||||
filter: addQueryRowAction,
|
filter: addQueryRowAction,
|
||||||
mapper: (state, action): ExploreItemState => {
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { queries, queryTransactions } = state;
|
const { queries } = state;
|
||||||
const { index, query } = action.payload;
|
const { index, query } = action.payload;
|
||||||
|
|
||||||
// Add to queries, which will cause a new row to be rendered
|
// Add to queries, which will cause a new row to be rendered
|
||||||
const nextQueries = [...queries.slice(0, index + 1), { ...query }, ...queries.slice(index + 1)];
|
const nextQueries = [...queries.slice(0, index + 1), { ...query }, ...queries.slice(index + 1)];
|
||||||
|
|
||||||
// Ongoing transactions need to update their row indices
|
|
||||||
const nextQueryTransactions = queryTransactions.map(qt => {
|
|
||||||
if (qt.rowIndex > index) {
|
|
||||||
return {
|
|
||||||
...qt,
|
|
||||||
rowIndex: qt.rowIndex + 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return qt;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
queries: nextQueries,
|
queries: nextQueries,
|
||||||
logsHighlighterExpressions: undefined,
|
logsHighlighterExpressions: undefined,
|
||||||
queryTransactions: nextQueryTransactions,
|
|
||||||
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -150,21 +143,17 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
.addMapper({
|
.addMapper({
|
||||||
filter: changeQueryAction,
|
filter: changeQueryAction,
|
||||||
mapper: (state, action): ExploreItemState => {
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { queries, queryTransactions } = state;
|
const { queries } = state;
|
||||||
const { query, index } = action.payload;
|
const { query, index } = action.payload;
|
||||||
|
|
||||||
// Override path: queries are completely reset
|
// Override path: queries are completely reset
|
||||||
const nextQuery: DataQuery = { ...query, ...generateEmptyQuery(state.queries) };
|
const nextQuery: DataQuery = generateNewKeyAndAddRefIdIfMissing(query, queries, index);
|
||||||
const nextQueries = [...queries];
|
const nextQueries = [...queries];
|
||||||
nextQueries[index] = nextQuery;
|
nextQueries[index] = nextQuery;
|
||||||
|
|
||||||
// Discard ongoing transaction related to row query
|
|
||||||
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
queries: nextQueries,
|
queries: nextQueries,
|
||||||
queryTransactions: nextQueryTransactions,
|
|
||||||
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -199,7 +188,6 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
queries: queries.slice(),
|
queries: queries.slice(),
|
||||||
queryTransactions: [],
|
|
||||||
showingStartPage: Boolean(state.StartPage),
|
showingStartPage: Boolean(state.StartPage),
|
||||||
queryKeys: getQueryKeys(queries, state.datasourceInstance),
|
queryKeys: getQueryKeys(queries, state.datasourceInstance),
|
||||||
};
|
};
|
||||||
@ -244,6 +232,11 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
datasourceInstance,
|
datasourceInstance,
|
||||||
|
queryErrors: [],
|
||||||
|
latency: 0,
|
||||||
|
graphIsLoading: false,
|
||||||
|
logIsLoading: false,
|
||||||
|
tableIsLoading: false,
|
||||||
supportsGraph,
|
supportsGraph,
|
||||||
supportsLogs,
|
supportsLogs,
|
||||||
supportsTable,
|
supportsTable,
|
||||||
@ -284,7 +277,6 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
datasourceLoading: false,
|
datasourceLoading: false,
|
||||||
datasourceMissing: false,
|
datasourceMissing: false,
|
||||||
logsHighlighterExpressions: undefined,
|
logsHighlighterExpressions: undefined,
|
||||||
queryTransactions: [],
|
|
||||||
update: makeInitialUpdateState(),
|
update: makeInitialUpdateState(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -292,95 +284,87 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
.addMapper({
|
.addMapper({
|
||||||
filter: modifyQueriesAction,
|
filter: modifyQueriesAction,
|
||||||
mapper: (state, action): ExploreItemState => {
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { queries, queryTransactions } = state;
|
const { queries } = state;
|
||||||
const { modification, index, modifier } = action.payload;
|
const { modification, index, modifier } = action.payload;
|
||||||
let nextQueries: DataQuery[];
|
let nextQueries: DataQuery[];
|
||||||
let nextQueryTransactions: QueryTransaction[];
|
|
||||||
if (index === undefined) {
|
if (index === undefined) {
|
||||||
// Modify all queries
|
// Modify all queries
|
||||||
nextQueries = queries.map((query, i) => ({
|
nextQueries = queries.map((query, i) => {
|
||||||
...modifier({ ...query }, modification),
|
const nextQuery = modifier({ ...query }, modification);
|
||||||
...generateEmptyQuery(state.queries),
|
return generateNewKeyAndAddRefIdIfMissing(nextQuery, queries, i);
|
||||||
}));
|
});
|
||||||
// Discard all ongoing transactions
|
|
||||||
nextQueryTransactions = [];
|
|
||||||
} else {
|
} else {
|
||||||
// Modify query only at index
|
// Modify query only at index
|
||||||
nextQueries = queries.map((query, i) => {
|
nextQueries = queries.map((query, i) => {
|
||||||
// Synchronize all queries with local query cache to ensure consistency
|
if (i === index) {
|
||||||
// TODO still needed?
|
const nextQuery = modifier({ ...query }, modification);
|
||||||
return i === index
|
return generateNewKeyAndAddRefIdIfMissing(nextQuery, queries, i);
|
||||||
? { ...modifier({ ...query }, modification), ...generateEmptyQuery(state.queries) }
|
|
||||||
: query;
|
|
||||||
});
|
|
||||||
nextQueryTransactions = queryTransactions
|
|
||||||
// Consume the hint corresponding to the action
|
|
||||||
.map(qt => {
|
|
||||||
if (qt.hints != null && qt.rowIndex === index) {
|
|
||||||
qt.hints = qt.hints.filter(hint => hint.fix.action !== modification);
|
|
||||||
}
|
}
|
||||||
return qt;
|
|
||||||
})
|
return query;
|
||||||
// Preserve previous row query transaction to keep results visible if next query is incomplete
|
});
|
||||||
.filter(qt => modification.preventSubmit || qt.rowIndex !== index);
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
queries: nextQueries,
|
queries: nextQueries,
|
||||||
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
||||||
queryTransactions: nextQueryTransactions,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.addMapper({
|
.addMapper({
|
||||||
filter: queryTransactionFailureAction,
|
filter: queryFailureAction,
|
||||||
mapper: (state, action): ExploreItemState => {
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { queryTransactions } = action.payload;
|
const { resultType, response } = action.payload;
|
||||||
|
const queryErrors = state.queryErrors.concat(response);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
queryTransactions,
|
graphResult: resultType === 'Graph' ? null : state.graphResult,
|
||||||
|
tableResult: resultType === 'Table' ? null : state.tableResult,
|
||||||
|
logsResult: resultType === 'Logs' ? null : state.logsResult,
|
||||||
|
latency: 0,
|
||||||
|
queryErrors,
|
||||||
|
showingStartPage: false,
|
||||||
|
graphIsLoading: resultType === 'Graph' ? false : state.graphIsLoading,
|
||||||
|
logIsLoading: resultType === 'Logs' ? false : state.logIsLoading,
|
||||||
|
tableIsLoading: resultType === 'Table' ? false : state.tableIsLoading,
|
||||||
|
update: makeInitialUpdateState(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addMapper({
|
||||||
|
filter: queryStartAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
|
const { resultType } = action.payload;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
queryErrors: [],
|
||||||
|
latency: 0,
|
||||||
|
graphIsLoading: resultType === 'Graph' ? true : state.graphIsLoading,
|
||||||
|
logIsLoading: resultType === 'Logs' ? true : state.logIsLoading,
|
||||||
|
tableIsLoading: resultType === 'Table' ? true : state.tableIsLoading,
|
||||||
showingStartPage: false,
|
showingStartPage: false,
|
||||||
update: makeInitialUpdateState(),
|
update: makeInitialUpdateState(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.addMapper({
|
.addMapper({
|
||||||
filter: queryTransactionStartAction,
|
filter: querySuccessAction,
|
||||||
mapper: (state, action): ExploreItemState => {
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { queryTransactions } = state;
|
const { queryIntervals } = state;
|
||||||
const { resultType, rowIndex, transaction } = action.payload;
|
const { result, resultType, latency } = action.payload;
|
||||||
// Discarding existing transactions of same type
|
const results = calculateResultsFromQueryTransactions(result, resultType, queryIntervals.intervalMs);
|
||||||
const remainingTransactions = queryTransactions.filter(
|
|
||||||
qt => !(qt.resultType === resultType && qt.rowIndex === rowIndex)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Append new transaction
|
|
||||||
const nextQueryTransactions: QueryTransaction[] = [...remainingTransactions, transaction];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
queryTransactions: nextQueryTransactions,
|
graphResult: resultType === 'Graph' ? results.graphResult : state.graphResult,
|
||||||
showingStartPage: false,
|
tableResult: resultType === 'Table' ? results.tableResult : state.tableResult,
|
||||||
update: makeInitialUpdateState(),
|
logsResult: resultType === 'Logs' ? results.logsResult : state.logsResult,
|
||||||
};
|
latency,
|
||||||
},
|
graphIsLoading: false,
|
||||||
})
|
logIsLoading: false,
|
||||||
.addMapper({
|
tableIsLoading: false,
|
||||||
filter: queryTransactionSuccessAction,
|
|
||||||
mapper: (state, action): ExploreItemState => {
|
|
||||||
const { datasourceInstance, queryIntervals } = state;
|
|
||||||
const { history, queryTransactions } = action.payload;
|
|
||||||
const results = calculateResultsFromQueryTransactions(
|
|
||||||
queryTransactions,
|
|
||||||
datasourceInstance,
|
|
||||||
queryIntervals.intervalMs
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
...results,
|
|
||||||
history,
|
|
||||||
queryTransactions,
|
|
||||||
showingStartPage: false,
|
showingStartPage: false,
|
||||||
update: makeInitialUpdateState(),
|
update: makeInitialUpdateState(),
|
||||||
};
|
};
|
||||||
@ -389,7 +373,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
.addMapper({
|
.addMapper({
|
||||||
filter: removeQueryRowAction,
|
filter: removeQueryRowAction,
|
||||||
mapper: (state, action): ExploreItemState => {
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { datasourceInstance, queries, queryIntervals, queryTransactions, queryKeys } = state;
|
const { queries, queryKeys } = state;
|
||||||
const { index } = action.payload;
|
const { index } = action.payload;
|
||||||
|
|
||||||
if (queries.length <= 1) {
|
if (queries.length <= 1) {
|
||||||
@ -399,20 +383,10 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
|
const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
|
||||||
const nextQueryKeys = [...queryKeys.slice(0, index), ...queryKeys.slice(index + 1)];
|
const nextQueryKeys = [...queryKeys.slice(0, index), ...queryKeys.slice(index + 1)];
|
||||||
|
|
||||||
// Discard transactions related to row query
|
|
||||||
const nextQueryTransactions = queryTransactions.filter(qt => nextQueries.some(nq => nq.key === qt.query.key));
|
|
||||||
const results = calculateResultsFromQueryTransactions(
|
|
||||||
nextQueryTransactions,
|
|
||||||
datasourceInstance,
|
|
||||||
queryIntervals.intervalMs
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...results,
|
|
||||||
queries: nextQueries,
|
queries: nextQueries,
|
||||||
logsHighlighterExpressions: undefined,
|
logsHighlighterExpressions: undefined,
|
||||||
queryTransactions: nextQueryTransactions,
|
|
||||||
queryKeys: nextQueryKeys,
|
queryKeys: nextQueryKeys,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -432,11 +406,8 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
.addMapper({
|
.addMapper({
|
||||||
filter: scanStopAction,
|
filter: scanStopAction,
|
||||||
mapper: (state): ExploreItemState => {
|
mapper: (state): ExploreItemState => {
|
||||||
const { queryTransactions } = state;
|
|
||||||
const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done);
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
queryTransactions: nextQueryTransactions,
|
|
||||||
scanning: false,
|
scanning: false,
|
||||||
scanRange: undefined,
|
scanRange: undefined,
|
||||||
scanner: undefined,
|
scanner: undefined,
|
||||||
@ -461,47 +432,15 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
return { ...state, ...action.payload };
|
return { ...state, ...action.payload };
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.addMapper({
|
|
||||||
filter: toggleGraphAction,
|
|
||||||
mapper: (state): ExploreItemState => {
|
|
||||||
const showingGraph = !state.showingGraph;
|
|
||||||
let nextQueryTransactions = state.queryTransactions;
|
|
||||||
if (!showingGraph) {
|
|
||||||
// Discard transactions related to Graph query
|
|
||||||
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
|
|
||||||
}
|
|
||||||
return { ...state, queryTransactions: nextQueryTransactions };
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.addMapper({
|
|
||||||
filter: toggleLogsAction,
|
|
||||||
mapper: (state): ExploreItemState => {
|
|
||||||
const showingLogs = !state.showingLogs;
|
|
||||||
let nextQueryTransactions = state.queryTransactions;
|
|
||||||
if (!showingLogs) {
|
|
||||||
// Discard transactions related to Logs query
|
|
||||||
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
|
|
||||||
}
|
|
||||||
return { ...state, queryTransactions: nextQueryTransactions };
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.addMapper({
|
.addMapper({
|
||||||
filter: toggleTableAction,
|
filter: toggleTableAction,
|
||||||
mapper: (state): ExploreItemState => {
|
mapper: (state): ExploreItemState => {
|
||||||
const showingTable = !state.showingTable;
|
const showingTable = !state.showingTable;
|
||||||
if (showingTable) {
|
if (showingTable) {
|
||||||
return { ...state, queryTransactions: state.queryTransactions };
|
return { ...state };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle off needs discarding of table queries and results
|
return { ...state, tableResult: new TableModel() };
|
||||||
const nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Table');
|
|
||||||
const results = calculateResultsFromQueryTransactions(
|
|
||||||
nextQueryTransactions,
|
|
||||||
state.datasourceInstance,
|
|
||||||
state.queryIntervals.intervalMs
|
|
||||||
);
|
|
||||||
|
|
||||||
return { ...state, ...results, queryTransactions: nextQueryTransactions };
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.addMapper({
|
.addMapper({
|
||||||
@ -549,7 +488,6 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
datasourceError: action.payload.error,
|
datasourceError: action.payload.error,
|
||||||
queryTransactions: [],
|
|
||||||
graphResult: undefined,
|
graphResult: undefined,
|
||||||
tableResult: undefined,
|
tableResult: undefined,
|
||||||
logsResult: undefined,
|
logsResult: undefined,
|
||||||
@ -581,6 +519,33 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
.addMapper({
|
||||||
|
filter: historyUpdatedAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
history: action.payload.history,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addMapper({
|
||||||
|
filter: resetQueryErrorAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
|
const { refIds } = action.payload;
|
||||||
|
const queryErrors = state.queryErrors.reduce((allErrors, error) => {
|
||||||
|
if (error.refId && refIds.indexOf(error.refId) !== -1) {
|
||||||
|
return allErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrors.concat(error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
queryErrors,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
export const updateChildRefreshState = (
|
export const updateChildRefreshState = (
|
||||||
|
@ -31,7 +31,7 @@ export default (props: any) => (
|
|||||||
{item.expression && (
|
{item.expression && (
|
||||||
<div
|
<div
|
||||||
className="cheat-sheet-item__expression"
|
className="cheat-sheet-item__expression"
|
||||||
onClick={e => props.onClickExample({ refId: '1', expr: item.expression })}
|
onClick={e => props.onClickExample({ refId: 'A', expr: item.expression })}
|
||||||
>
|
>
|
||||||
<code>{item.expression}</code>
|
<code>{item.expression}</code>
|
||||||
</div>
|
</div>
|
||||||
|
@ -86,14 +86,14 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
|
|||||||
|
|
||||||
this.plugins = [
|
this.plugins = [
|
||||||
BracesPlugin(),
|
BracesPlugin(),
|
||||||
RunnerPlugin({ handler: props.onExecuteQuery }),
|
RunnerPlugin({ handler: props.onRunQuery }),
|
||||||
PluginPrism({
|
PluginPrism({
|
||||||
onlyIn: (node: any) => node.type === 'code_block',
|
onlyIn: (node: any) => node.type === 'code_block',
|
||||||
getSyntax: (node: any) => 'promql',
|
getSyntax: (node: any) => 'promql',
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
this.pluginsSearch = [RunnerPlugin({ handler: props.onExecuteQuery })];
|
this.pluginsSearch = [RunnerPlugin({ handler: props.onRunQuery })];
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOptions = (selectedOptions: CascaderOption[]) => {
|
loadOptions = (selectedOptions: CascaderOption[]) => {
|
||||||
@ -111,24 +111,17 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
|
|||||||
|
|
||||||
onChangeQuery = (value: string, override?: boolean) => {
|
onChangeQuery = (value: string, override?: boolean) => {
|
||||||
// Send text change to parent
|
// Send text change to parent
|
||||||
const { query, onQueryChange, onExecuteQuery } = this.props;
|
const { query, onChange, onRunQuery } = this.props;
|
||||||
if (onQueryChange) {
|
if (onChange) {
|
||||||
const nextQuery = { ...query, expr: value };
|
const nextQuery = { ...query, expr: value };
|
||||||
onQueryChange(nextQuery);
|
onChange(nextQuery);
|
||||||
|
|
||||||
if (override && onExecuteQuery) {
|
if (override && onRunQuery) {
|
||||||
onExecuteQuery();
|
onRunQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickHintFix = () => {
|
|
||||||
const { hint, onExecuteHint } = this.props;
|
|
||||||
if (onExecuteHint && hint && hint.fix) {
|
|
||||||
onExecuteHint(hint.fix.action);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onTypeahead = (typeahead: TypeaheadInput): TypeaheadOutput => {
|
onTypeahead = (typeahead: TypeaheadInput): TypeaheadOutput => {
|
||||||
const { datasource } = this.props;
|
const { datasource } = this.props;
|
||||||
if (!datasource.languageProvider) {
|
if (!datasource.languageProvider) {
|
||||||
@ -156,8 +149,7 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
error,
|
queryResponse,
|
||||||
hint,
|
|
||||||
query,
|
query,
|
||||||
syntaxLoaded,
|
syntaxLoaded,
|
||||||
logLabelOptions,
|
logLabelOptions,
|
||||||
@ -197,8 +189,8 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
|
|||||||
initialQuery={query.expr}
|
initialQuery={query.expr}
|
||||||
onTypeahead={this.onTypeahead}
|
onTypeahead={this.onTypeahead}
|
||||||
onWillApplySuggestion={willApplySuggestion}
|
onWillApplySuggestion={willApplySuggestion}
|
||||||
onQueryChange={this.onChangeQuery}
|
onChange={this.onChangeQuery}
|
||||||
onExecuteQuery={this.props.onExecuteQuery}
|
onRunQuery={this.props.onRunQuery}
|
||||||
placeholder="Enter a Loki query"
|
placeholder="Enter a Loki query"
|
||||||
portalOrigin="loki"
|
portalOrigin="loki"
|
||||||
syntaxLoaded={syntaxLoaded}
|
syntaxLoaded={syntaxLoaded}
|
||||||
@ -206,16 +198,8 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{error ? <div className="prom-query-field-info text-error">{error}</div> : null}
|
{queryResponse && queryResponse.error ? (
|
||||||
{hint ? (
|
<div className="prom-query-field-info text-error">{queryResponse.error.message}</div>
|
||||||
<div className="prom-query-field-info text-warning">
|
|
||||||
{hint.label}{' '}
|
|
||||||
{hint.fix ? (
|
|
||||||
<a className="text-link muted" onClick={this.onClickHintFix}>
|
|
||||||
{hint.fix.label}
|
|
||||||
</a>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -15,10 +15,12 @@ import {
|
|||||||
SeriesData,
|
SeriesData,
|
||||||
DataSourceApi,
|
DataSourceApi,
|
||||||
DataSourceInstanceSettings,
|
DataSourceInstanceSettings,
|
||||||
|
DataQueryError,
|
||||||
} from '@grafana/ui/src/types';
|
} from '@grafana/ui/src/types';
|
||||||
import { LokiQuery, LokiOptions } from './types';
|
import { LokiQuery, LokiOptions } from './types';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
import { safeStringifyValue } from 'app/core/utils/explore';
|
||||||
|
|
||||||
export const DEFAULT_MAX_LINES = 1000;
|
export const DEFAULT_MAX_LINES = 1000;
|
||||||
|
|
||||||
@ -65,16 +67,18 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
return this.backendSrv.datasourceRequest(req);
|
return this.backendSrv.datasourceRequest(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareQueryTarget(target, options) {
|
prepareQueryTarget(target: LokiQuery, options: DataQueryRequest<LokiQuery>) {
|
||||||
const interpolated = this.templateSrv.replace(target.expr);
|
const interpolated = this.templateSrv.replace(target.expr);
|
||||||
const start = this.getTime(options.range.from, false);
|
const start = this.getTime(options.range.from, false);
|
||||||
const end = this.getTime(options.range.to, true);
|
const end = this.getTime(options.range.to, true);
|
||||||
|
const refId = target.refId;
|
||||||
return {
|
return {
|
||||||
...DEFAULT_QUERY_PARAMS,
|
...DEFAULT_QUERY_PARAMS,
|
||||||
...parseQuery(interpolated),
|
...parseQuery(interpolated),
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
limit: this.maxLines,
|
limit: this.maxLines,
|
||||||
|
refId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,16 +91,47 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
|
|||||||
return Promise.resolve({ data: [] });
|
return Promise.resolve({ data: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
const queries = queryTargets.map(target => this._request('/api/prom/query', target));
|
const queries = queryTargets.map(target =>
|
||||||
|
this._request('/api/prom/query', target).catch((err: any) => {
|
||||||
|
if (err.cancelled) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const error: DataQueryError = {
|
||||||
|
message: 'Unknown error during query transaction. Please check JS console logs.',
|
||||||
|
refId: target.refId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (err.data) {
|
||||||
|
if (typeof err.data === 'string') {
|
||||||
|
error.message = err.data;
|
||||||
|
} else if (err.data.error) {
|
||||||
|
error.message = safeStringifyValue(err.data.error);
|
||||||
|
}
|
||||||
|
} else if (err.message) {
|
||||||
|
error.message = err.message;
|
||||||
|
} else if (typeof err === 'string') {
|
||||||
|
error.message = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
error.status = err.status;
|
||||||
|
error.statusText = err.statusText;
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return Promise.all(queries).then((results: any[]) => {
|
return Promise.all(queries).then((results: any[]) => {
|
||||||
const series: SeriesData[] = [];
|
const series: Array<SeriesData | DataQueryError> = [];
|
||||||
|
|
||||||
for (let i = 0; i < results.length; i++) {
|
for (let i = 0; i < results.length; i++) {
|
||||||
const result = results[i];
|
const result = results[i];
|
||||||
|
|
||||||
if (result.data) {
|
if (result.data) {
|
||||||
|
const refId = queryTargets[i].refId;
|
||||||
for (const stream of result.data.streams || []) {
|
for (const stream of result.data.streams || []) {
|
||||||
const seriesData = logStreamToSeriesData(stream);
|
const seriesData = logStreamToSeriesData(stream);
|
||||||
|
seriesData.refId = refId;
|
||||||
seriesData.meta = {
|
seriesData.meta = {
|
||||||
search: queryTargets[i].regexp,
|
search: queryTargets[i].regexp,
|
||||||
limit: this.maxLines,
|
limit: this.maxLines,
|
||||||
|
@ -27,7 +27,7 @@ export default (props: any) => (
|
|||||||
<div className="cheat-sheet-item__title">{item.title}</div>
|
<div className="cheat-sheet-item__title">{item.title}</div>
|
||||||
<div
|
<div
|
||||||
className="cheat-sheet-item__expression"
|
className="cheat-sheet-item__expression"
|
||||||
onClick={e => props.onClickExample({ refId: '1', expr: item.expression })}
|
onClick={e => props.onClickExample({ refId: 'A', expr: item.expression })}
|
||||||
>
|
>
|
||||||
<code>{item.expression}</code>
|
<code>{item.expression}</code>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,7 +16,7 @@ import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
|
|||||||
import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
|
import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
|
||||||
import { PromQuery } from '../types';
|
import { PromQuery } from '../types';
|
||||||
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
|
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
|
||||||
import { ExploreDataSourceApi, ExploreQueryFieldProps, DataSourceStatus } from '@grafana/ui';
|
import { ExploreDataSourceApi, ExploreQueryFieldProps, DataSourceStatus, QueryHint } from '@grafana/ui';
|
||||||
|
|
||||||
const HISTOGRAM_GROUP = '__histograms__';
|
const HISTOGRAM_GROUP = '__histograms__';
|
||||||
const METRIC_MARK = 'metric';
|
const METRIC_MARK = 'metric';
|
||||||
@ -109,6 +109,7 @@ interface PromQueryFieldProps extends ExploreQueryFieldProps<ExploreDataSourceAp
|
|||||||
interface PromQueryFieldState {
|
interface PromQueryFieldState {
|
||||||
metricsOptions: any[];
|
metricsOptions: any[];
|
||||||
syntaxLoaded: boolean;
|
syntaxLoaded: boolean;
|
||||||
|
hint: QueryHint;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryFieldState> {
|
class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryFieldState> {
|
||||||
@ -125,7 +126,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
|
|
||||||
this.plugins = [
|
this.plugins = [
|
||||||
BracesPlugin(),
|
BracesPlugin(),
|
||||||
RunnerPlugin({ handler: props.onExecuteQuery }),
|
RunnerPlugin({ handler: props.onRunQuery }),
|
||||||
PluginPrism({
|
PluginPrism({
|
||||||
onlyIn: (node: any) => node.type === 'code_block',
|
onlyIn: (node: any) => node.type === 'code_block',
|
||||||
getSyntax: (node: any) => 'promql',
|
getSyntax: (node: any) => 'promql',
|
||||||
@ -135,6 +136,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
this.state = {
|
this.state = {
|
||||||
metricsOptions: [],
|
metricsOptions: [],
|
||||||
syntaxLoaded: false,
|
syntaxLoaded: false,
|
||||||
|
hint: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +144,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
if (this.languageProvider) {
|
if (this.languageProvider) {
|
||||||
this.refreshMetrics(makePromiseCancelable(this.languageProvider.start()));
|
this.refreshMetrics(makePromiseCancelable(this.languageProvider.start()));
|
||||||
}
|
}
|
||||||
|
this.refreshHint();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@ -151,6 +154,11 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: PromQueryFieldProps) {
|
componentDidUpdate(prevProps: PromQueryFieldProps) {
|
||||||
|
const currentHasSeries = this.props.queryResponse.series && this.props.queryResponse.series.length > 0;
|
||||||
|
if (currentHasSeries && prevProps.queryResponse.series !== this.props.queryResponse.series) {
|
||||||
|
this.refreshHint();
|
||||||
|
}
|
||||||
|
|
||||||
const reconnected =
|
const reconnected =
|
||||||
prevProps.datasourceStatus === DataSourceStatus.Disconnected &&
|
prevProps.datasourceStatus === DataSourceStatus.Disconnected &&
|
||||||
this.props.datasourceStatus === DataSourceStatus.Connected;
|
this.props.datasourceStatus === DataSourceStatus.Connected;
|
||||||
@ -167,6 +175,17 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshHint = () => {
|
||||||
|
const { datasource, query, queryResponse } = this.props;
|
||||||
|
if (queryResponse.series && queryResponse.series.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hints = datasource.getQueryHints(query, queryResponse.series);
|
||||||
|
const hint = hints && hints.length > 0 ? hints[0] : null;
|
||||||
|
this.setState({ hint });
|
||||||
|
};
|
||||||
|
|
||||||
refreshMetrics = (cancelablePromise: CancelablePromise<any>) => {
|
refreshMetrics = (cancelablePromise: CancelablePromise<any>) => {
|
||||||
this.languageProviderInitializationPromise = cancelablePromise;
|
this.languageProviderInitializationPromise = cancelablePromise;
|
||||||
this.languageProviderInitializationPromise.promise
|
this.languageProviderInitializationPromise.promise
|
||||||
@ -204,21 +223,22 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
|
|
||||||
onChangeQuery = (value: string, override?: boolean) => {
|
onChangeQuery = (value: string, override?: boolean) => {
|
||||||
// Send text change to parent
|
// Send text change to parent
|
||||||
const { query, onQueryChange, onExecuteQuery } = this.props;
|
const { query, onChange, onRunQuery } = this.props;
|
||||||
if (onQueryChange) {
|
if (onChange) {
|
||||||
const nextQuery: PromQuery = { ...query, expr: value };
|
const nextQuery: PromQuery = { ...query, expr: value };
|
||||||
onQueryChange(nextQuery);
|
onChange(nextQuery);
|
||||||
|
|
||||||
if (override && onExecuteQuery) {
|
if (override && onRunQuery) {
|
||||||
onExecuteQuery();
|
onRunQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickHintFix = () => {
|
onClickHintFix = () => {
|
||||||
const { hint, onExecuteHint } = this.props;
|
const { hint } = this.state;
|
||||||
if (onExecuteHint && hint && hint.fix) {
|
const { onHint } = this.props;
|
||||||
onExecuteHint(hint.fix.action);
|
if (onHint && hint && hint.fix) {
|
||||||
|
onHint(hint.fix.action);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -273,8 +293,8 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { error, hint, query, datasourceStatus } = this.props;
|
const { queryResponse, query, datasourceStatus } = this.props;
|
||||||
const { metricsOptions, syntaxLoaded } = this.state;
|
const { metricsOptions, syntaxLoaded, hint } = this.state;
|
||||||
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
|
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
|
||||||
const chooserText = getChooserText(syntaxLoaded, datasourceStatus);
|
const chooserText = getChooserText(syntaxLoaded, datasourceStatus);
|
||||||
const buttonDisabled = !syntaxLoaded || datasourceStatus === DataSourceStatus.Disconnected;
|
const buttonDisabled = !syntaxLoaded || datasourceStatus === DataSourceStatus.Disconnected;
|
||||||
@ -296,15 +316,17 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
initialQuery={query.expr}
|
initialQuery={query.expr}
|
||||||
onTypeahead={this.onTypeahead}
|
onTypeahead={this.onTypeahead}
|
||||||
onWillApplySuggestion={willApplySuggestion}
|
onWillApplySuggestion={willApplySuggestion}
|
||||||
onQueryChange={this.onChangeQuery}
|
onChange={this.onChangeQuery}
|
||||||
onExecuteQuery={this.props.onExecuteQuery}
|
onRunQuery={this.props.onRunQuery}
|
||||||
placeholder="Enter a PromQL query"
|
placeholder="Enter a PromQL query"
|
||||||
portalOrigin="prometheus"
|
portalOrigin="prometheus"
|
||||||
syntaxLoaded={syntaxLoaded}
|
syntaxLoaded={syntaxLoaded}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{error ? <div className="prom-query-field-info text-error">{error}</div> : null}
|
{queryResponse && queryResponse.error ? (
|
||||||
|
<div className="prom-query-field-info text-error">{queryResponse.error.message}</div>
|
||||||
|
) : null}
|
||||||
{hint ? (
|
{hint ? (
|
||||||
<div className="prom-query-field-info text-warning">
|
<div className="prom-query-field-info text-warning">
|
||||||
{hint.label}{' '}
|
{hint.label}{' '}
|
||||||
|
@ -15,8 +15,15 @@ import { expandRecordingRules } from './language_utils';
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { PromQuery, PromOptions } from './types';
|
import { PromQuery, PromOptions } from './types';
|
||||||
import { DataQueryRequest, DataSourceApi, AnnotationEvent, DataSourceInstanceSettings } from '@grafana/ui/src/types';
|
import {
|
||||||
|
DataQueryRequest,
|
||||||
|
DataSourceApi,
|
||||||
|
AnnotationEvent,
|
||||||
|
DataSourceInstanceSettings,
|
||||||
|
DataQueryError,
|
||||||
|
} from '@grafana/ui/src/types';
|
||||||
import { ExploreUrlState } from 'app/types/explore';
|
import { ExploreUrlState } from 'app/types/explore';
|
||||||
|
import { safeStringifyValue } from 'app/core/utils/explore';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
|
|
||||||
@ -38,7 +45,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
instanceSettings: DataSourceInstanceSettings<PromOptions>,
|
instanceSettings: DataSourceInstanceSettings<PromOptions>,
|
||||||
private $q,
|
private $q: angular.IQService,
|
||||||
private backendSrv: BackendSrv,
|
private backendSrv: BackendSrv,
|
||||||
private templateSrv: TemplateSrv,
|
private templateSrv: TemplateSrv,
|
||||||
private timeSrv: TimeSrv
|
private timeSrv: TimeSrv
|
||||||
@ -134,7 +141,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
return this.templateSrv.variableExists(target.expr);
|
return this.templateSrv.variableExists(target.expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
query(options: DataQueryRequest<PromQuery>) {
|
query(options: DataQueryRequest<PromQuery>): Promise<{ data: any }> {
|
||||||
const start = this.getPrometheusTime(options.range.from, false);
|
const start = this.getPrometheusTime(options.range.from, false);
|
||||||
const end = this.getPrometheusTime(options.range.to, true);
|
const end = this.getPrometheusTime(options.range.to, true);
|
||||||
|
|
||||||
@ -154,7 +161,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
|
|
||||||
// No valid targets, return the empty result to save a round trip.
|
// No valid targets, return the empty result to save a round trip.
|
||||||
if (_.isEmpty(queries)) {
|
if (_.isEmpty(queries)) {
|
||||||
return this.$q.when({ data: [] });
|
return this.$q.when({ data: [] }) as Promise<{ data: any }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allQueryPromise = _.map(queries, query => {
|
const allQueryPromise = _.map(queries, query => {
|
||||||
@ -165,16 +172,12 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.$q.all(allQueryPromise).then(responseList => {
|
const allPromise = this.$q.all(allQueryPromise).then((responseList: any) => {
|
||||||
let result = [];
|
let result = [];
|
||||||
|
|
||||||
_.each(responseList, (response, index) => {
|
_.each(responseList, (response, index) => {
|
||||||
if (response.status === 'error') {
|
if (response.cancelled) {
|
||||||
const error = {
|
return;
|
||||||
index,
|
|
||||||
...response.error,
|
|
||||||
};
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keeping original start/end for transformers
|
// Keeping original start/end for transformers
|
||||||
@ -195,6 +198,8 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
|
|
||||||
return { data: result };
|
return { data: result };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return allPromise as Promise<{ data: any }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
createQuery(target, options, start, end) {
|
createQuery(target, options, start, end) {
|
||||||
@ -241,6 +246,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
// Only replace vars in expression after having (possibly) updated interval vars
|
// Only replace vars in expression after having (possibly) updated interval vars
|
||||||
query.expr = this.templateSrv.replace(expr, scopedVars, this.interpolateQueryExpr);
|
query.expr = this.templateSrv.replace(expr, scopedVars, this.interpolateQueryExpr);
|
||||||
query.requestId = options.panelId + target.refId;
|
query.requestId = options.panelId + target.refId;
|
||||||
|
query.refId = target.refId;
|
||||||
|
|
||||||
// Align query interval with step to allow query caching and to ensure
|
// Align query interval with step to allow query caching and to ensure
|
||||||
// that about-same-time query results look the same.
|
// that about-same-time query results look the same.
|
||||||
@ -276,7 +282,9 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
if (this.queryTimeout) {
|
if (this.queryTimeout) {
|
||||||
data['timeout'] = this.queryTimeout;
|
data['timeout'] = this.queryTimeout;
|
||||||
}
|
}
|
||||||
return this._request(url, data, { requestId: query.requestId, headers: query.headers });
|
return this._request(url, data, { requestId: query.requestId, headers: query.headers }).catch((err: any) =>
|
||||||
|
this.handleErrors(err, query)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
performInstantQuery(query, time) {
|
performInstantQuery(query, time) {
|
||||||
@ -288,9 +296,39 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
|||||||
if (this.queryTimeout) {
|
if (this.queryTimeout) {
|
||||||
data['timeout'] = this.queryTimeout;
|
data['timeout'] = this.queryTimeout;
|
||||||
}
|
}
|
||||||
return this._request(url, data, { requestId: query.requestId, headers: query.headers });
|
return this._request(url, data, { requestId: query.requestId, headers: query.headers }).catch((err: any) =>
|
||||||
|
this.handleErrors(err, query)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleErrors = (err: any, target: PromQuery) => {
|
||||||
|
if (err.cancelled) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const error: DataQueryError = {
|
||||||
|
message: 'Unknown error during query transaction. Please check JS console logs.',
|
||||||
|
refId: target.refId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (err.data) {
|
||||||
|
if (typeof err.data === 'string') {
|
||||||
|
error.message = err.data;
|
||||||
|
} else if (err.data.error) {
|
||||||
|
error.message = safeStringifyValue(err.data.error);
|
||||||
|
}
|
||||||
|
} else if (err.message) {
|
||||||
|
error.message = err.message;
|
||||||
|
} else if (typeof err === 'string') {
|
||||||
|
error.message = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
error.status = err.status;
|
||||||
|
error.statusText = err.statusText;
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
};
|
||||||
|
|
||||||
performSuggestQuery(query, cache = false) {
|
performSuggestQuery(query, cache = false) {
|
||||||
const url = '/api/v1/label/__name__/values';
|
const url = '/api/v1/label/__name__/values';
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import { DataSourceInstanceSettings } from '@grafana/ui';
|
|||||||
import { PromOptions } from '../types';
|
import { PromOptions } from '../types';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
|
import { IQService } from 'angular';
|
||||||
jest.mock('../datasource');
|
jest.mock('../datasource');
|
||||||
jest.mock('app/core/services/backend_srv');
|
jest.mock('app/core/services/backend_srv');
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ describe('Prometheus editor completer', () => {
|
|||||||
const backendSrv = {} as BackendSrv;
|
const backendSrv = {} as BackendSrv;
|
||||||
const datasourceStub = new PrometheusDatasource(
|
const datasourceStub = new PrometheusDatasource(
|
||||||
{} as DataSourceInstanceSettings<PromOptions>,
|
{} as DataSourceInstanceSettings<PromOptions>,
|
||||||
{},
|
{} as IQService,
|
||||||
backendSrv,
|
backendSrv,
|
||||||
{} as TemplateSrv,
|
{} as TemplateSrv,
|
||||||
{} as TimeSrv
|
{} as TimeSrv
|
||||||
|
@ -401,7 +401,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
|
|
||||||
await ctx.ds.query(query).then(data => {
|
await ctx.ds.query(query).then(data => {
|
||||||
results = data;
|
results = data;
|
||||||
@ -451,7 +451,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
|
|
||||||
await ctx.ds.query(query).then(data => {
|
await ctx.ds.query(query).then(data => {
|
||||||
results = data;
|
results = data;
|
||||||
@ -512,7 +512,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
|
|
||||||
await ctx.ds.query(query).then(data => {
|
await ctx.ds.query(query).then(data => {
|
||||||
results = data;
|
results = data;
|
||||||
@ -569,7 +569,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
options.annotation.useValueForTime = false;
|
options.annotation.useValueForTime = false;
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
|
|
||||||
await ctx.ds.annotationQuery(options).then(data => {
|
await ctx.ds.annotationQuery(options).then(data => {
|
||||||
results = data;
|
results = data;
|
||||||
@ -589,7 +589,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
options.annotation.useValueForTime = true;
|
options.annotation.useValueForTime = true;
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
|
|
||||||
await ctx.ds.annotationQuery(options).then(data => {
|
await ctx.ds.annotationQuery(options).then(data => {
|
||||||
results = data;
|
results = data;
|
||||||
@ -604,7 +604,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
describe('step parameter', () => {
|
describe('step parameter', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use default step for short range if no interval is given', () => {
|
it('should use default step for short range if no interval is given', () => {
|
||||||
@ -700,7 +700,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query).then(data => {
|
await ctx.ds.query(query).then(data => {
|
||||||
results = data;
|
results = data;
|
||||||
});
|
});
|
||||||
@ -737,7 +737,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
|
const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
|
||||||
|
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -753,7 +753,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
};
|
};
|
||||||
const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=1';
|
const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=1';
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -774,7 +774,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
};
|
};
|
||||||
const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
|
const urlExpected = 'proxied/api/v1/query_range?query=test&start=60&end=420&step=10';
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -791,7 +791,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
const start = 60 * 60;
|
const start = 60 * 60;
|
||||||
const urlExpected = 'proxied/api/v1/query_range?query=test&start=' + start + '&end=' + end + '&step=2';
|
const urlExpected = 'proxied/api/v1/query_range?query=test&start=' + start + '&end=' + end + '&step=2';
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -813,7 +813,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
// times get rounded up to interval
|
// times get rounded up to interval
|
||||||
const urlExpected = 'proxied/api/v1/query_range?query=test&start=50&end=400&step=50';
|
const urlExpected = 'proxied/api/v1/query_range?query=test&start=50&end=400&step=50';
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -834,7 +834,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
};
|
};
|
||||||
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=60&end=420&step=15';
|
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=60&end=420&step=15';
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -856,7 +856,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
// times get aligned to interval
|
// times get aligned to interval
|
||||||
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=0&end=400&step=100';
|
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=0&end=400&step=100';
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -878,7 +878,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
const start = 0;
|
const start = 0;
|
||||||
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=100';
|
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=100';
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -900,7 +900,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
const start = 0;
|
const start = 0;
|
||||||
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=60';
|
const urlExpected = 'proxied/api/v1/query_range?query=test' + '&start=' + start + '&end=' + end + '&step=60';
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -943,7 +943,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
|
|
||||||
templateSrv.replace = jest.fn(str => str);
|
templateSrv.replace = jest.fn(str => str);
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -983,7 +983,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
'&start=60&end=420&step=10';
|
'&start=60&end=420&step=10';
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
templateSrv.replace = jest.fn(str => str);
|
templateSrv.replace = jest.fn(str => str);
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -1024,7 +1024,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
'&start=0&end=400&step=100';
|
'&start=0&end=400&step=100';
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
templateSrv.replace = jest.fn(str => str);
|
templateSrv.replace = jest.fn(str => str);
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -1071,7 +1071,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
|
|
||||||
templateSrv.replace = jest.fn(str => str);
|
templateSrv.replace = jest.fn(str => str);
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -1112,7 +1112,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
'&start=60&end=420&step=15';
|
'&start=60&end=420&step=15';
|
||||||
|
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -1158,7 +1158,7 @@ describe('PrometheusDatasource', () => {
|
|||||||
'&step=60';
|
'&step=60';
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
templateSrv.replace = jest.fn(str => str);
|
templateSrv.replace = jest.fn(str => str);
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query);
|
await ctx.ds.query(query);
|
||||||
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
const res = backendSrv.datasourceRequest.mock.calls[0][0];
|
||||||
expect(res.method).toBe('GET');
|
expect(res.method).toBe('GET');
|
||||||
@ -1220,7 +1220,7 @@ describe('PrometheusDatasource for POST', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
backendSrv.datasourceRequest = jest.fn(() => Promise.resolve(response));
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
await ctx.ds.query(query).then(data => {
|
await ctx.ds.query(query).then(data => {
|
||||||
results = data;
|
results = data;
|
||||||
});
|
});
|
||||||
@ -1245,7 +1245,7 @@ describe('PrometheusDatasource for POST', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it('with proxy access tracing headers should be added', () => {
|
it('with proxy access tracing headers should be added', () => {
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
ctx.ds._addTracingHeaders(httpOptions, options);
|
ctx.ds._addTracingHeaders(httpOptions, options);
|
||||||
expect(httpOptions.headers['X-Dashboard-Id']).toBe(1);
|
expect(httpOptions.headers['X-Dashboard-Id']).toBe(1);
|
||||||
expect(httpOptions.headers['X-Panel-Id']).toBe(2);
|
expect(httpOptions.headers['X-Panel-Id']).toBe(2);
|
||||||
@ -1253,7 +1253,7 @@ describe('PrometheusDatasource for POST', () => {
|
|||||||
|
|
||||||
it('with direct access tracing headers should not be added', () => {
|
it('with direct access tracing headers should not be added', () => {
|
||||||
instanceSettings.url = 'http://127.0.0.1:8000';
|
instanceSettings.url = 'http://127.0.0.1:8000';
|
||||||
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv, timeSrv);
|
ctx.ds = new PrometheusDatasource(instanceSettings, q, backendSrv as any, templateSrv as any, timeSrv as any);
|
||||||
ctx.ds._addTracingHeaders(httpOptions, options);
|
ctx.ds._addTracingHeaders(httpOptions, options);
|
||||||
expect(httpOptions.headers['X-Dashboard-Id']).toBe(undefined);
|
expect(httpOptions.headers['X-Dashboard-Id']).toBe(undefined);
|
||||||
expect(httpOptions.headers['X-Panel-Id']).toBe(undefined);
|
expect(httpOptions.headers['X-Panel-Id']).toBe(undefined);
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
ExploreStartPageProps,
|
ExploreStartPageProps,
|
||||||
LogLevel,
|
LogLevel,
|
||||||
TimeRange,
|
TimeRange,
|
||||||
|
DataQueryError,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
|
|
||||||
import { Emitter, TimeSeries } from 'app/core/core';
|
import { Emitter, TimeSeries } from 'app/core/core';
|
||||||
@ -178,14 +179,6 @@ export interface ExploreItemState {
|
|||||||
* Needs to be updated when `datasourceInstance` or `containerWidth` is changed.
|
* Needs to be updated when `datasourceInstance` or `containerWidth` is changed.
|
||||||
*/
|
*/
|
||||||
queryIntervals: QueryIntervals;
|
queryIntervals: QueryIntervals;
|
||||||
/**
|
|
||||||
* List of query transaction to track query duration and query result.
|
|
||||||
* Graph/Logs/Table results are calculated on the fly from the transaction,
|
|
||||||
* based on the transaction's result types. Transaction also holds the row index
|
|
||||||
* so that results can be dropped and re-computed without running queries again
|
|
||||||
* when query rows are removed.
|
|
||||||
*/
|
|
||||||
queryTransactions: QueryTransaction[];
|
|
||||||
/**
|
/**
|
||||||
* Time range for this Explore. Managed by the time picker and used by all query runs.
|
* Time range for this Explore. Managed by the time picker and used by all query runs.
|
||||||
*/
|
*/
|
||||||
@ -230,6 +223,10 @@ export interface ExploreItemState {
|
|||||||
* True if `datasourceInstance` supports table queries.
|
* True if `datasourceInstance` supports table queries.
|
||||||
*/
|
*/
|
||||||
supportsTable: boolean | null;
|
supportsTable: boolean | null;
|
||||||
|
|
||||||
|
graphIsLoading: boolean;
|
||||||
|
logIsLoading: boolean;
|
||||||
|
tableIsLoading: boolean;
|
||||||
/**
|
/**
|
||||||
* Table model that combines all query table results into a single table.
|
* Table model that combines all query table results into a single table.
|
||||||
*/
|
*/
|
||||||
@ -258,6 +255,9 @@ export interface ExploreItemState {
|
|||||||
urlState: ExploreUrlState;
|
urlState: ExploreUrlState;
|
||||||
|
|
||||||
update: ExploreUpdateState;
|
update: ExploreUpdateState;
|
||||||
|
|
||||||
|
queryErrors: DataQueryError[];
|
||||||
|
latency: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExploreUpdateState {
|
export interface ExploreUpdateState {
|
||||||
@ -332,10 +332,9 @@ export interface QueryTransaction {
|
|||||||
hints?: QueryHint[];
|
hints?: QueryHint[];
|
||||||
latency: number;
|
latency: number;
|
||||||
options: any;
|
options: any;
|
||||||
query: DataQuery;
|
queries: DataQuery[];
|
||||||
result?: any; // Table model / Timeseries[] / Logs
|
result?: any; // Table model / Timeseries[] / Logs
|
||||||
resultType: ResultType;
|
resultType: ResultType;
|
||||||
rowIndex: number;
|
|
||||||
scanning?: boolean;
|
scanning?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user