mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Explore: Only update pane's instance of Inspector (#80106)
* send instance ID to query inspector, ensure requestId match before updating data * Extract logic for mixed request ID, use in Explore prefix when appropriate * Change query inspector to get passed request ID * Fix test
This commit is contained in:
parent
9e04fd0fb7
commit
7b4dd4fe47
@ -55,11 +55,16 @@ export interface FolderRequestOptions {
|
|||||||
|
|
||||||
const GRAFANA_TRACEID_HEADER = 'grafana-trace-id';
|
const GRAFANA_TRACEID_HEADER = 'grafana-trace-id';
|
||||||
|
|
||||||
|
export interface InspectorStream {
|
||||||
|
response: FetchResponse | FetchError;
|
||||||
|
requestId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class BackendSrv implements BackendService {
|
export class BackendSrv implements BackendService {
|
||||||
private inFlightRequests: Subject<string> = new Subject<string>();
|
private inFlightRequests: Subject<string> = new Subject<string>();
|
||||||
private HTTP_REQUEST_CANCELED = -1;
|
private HTTP_REQUEST_CANCELED = -1;
|
||||||
private noBackendCache: boolean;
|
private noBackendCache: boolean;
|
||||||
private inspectorStream: Subject<FetchResponse | FetchError> = new Subject<FetchResponse | FetchError>();
|
private inspectorStream: Subject<InspectorStream> = new Subject<InspectorStream>();
|
||||||
private readonly fetchQueue: FetchQueue;
|
private readonly fetchQueue: FetchQueue;
|
||||||
private readonly responseQueue: ResponseQueue;
|
private readonly responseQueue: ResponseQueue;
|
||||||
private _tokenRotationInProgress?: Observable<FetchResponse> | null = null;
|
private _tokenRotationInProgress?: Observable<FetchResponse> | null = null;
|
||||||
@ -333,7 +338,7 @@ export class BackendSrv implements BackendService {
|
|||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.inspectorStream.next(err);
|
this.inspectorStream.next({ response: err, requestId: options.requestId });
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,7 +361,7 @@ export class BackendSrv implements BackendService {
|
|||||||
}),
|
}),
|
||||||
tap((response) => {
|
tap((response) => {
|
||||||
this.showSuccessAlert(response);
|
this.showSuccessAlert(response);
|
||||||
this.inspectorStream.next(response);
|
this.inspectorStream.next({ response: response, requestId: options.requestId });
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -446,7 +451,7 @@ export class BackendSrv implements BackendService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getInspectorStream(): Observable<FetchResponse | FetchError> {
|
getInspectorStream(): Observable<InspectorStream> {
|
||||||
return this.inspectorStream;
|
return this.inspectorStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,7 +664,7 @@ describe('backendSrv', () => {
|
|||||||
|
|
||||||
let inspectorPacket: FetchResponse | FetchError;
|
let inspectorPacket: FetchResponse | FetchError;
|
||||||
backendSrv.getInspectorStream().subscribe({
|
backendSrv.getInspectorStream().subscribe({
|
||||||
next: (rsp) => (inspectorPacket = rsp),
|
next: (rsp) => (inspectorPacket = rsp.response),
|
||||||
});
|
});
|
||||||
|
|
||||||
await backendSrv.datasourceRequest(options).catch((error) => {
|
await backendSrv.datasourceRequest(options).catch((error) => {
|
||||||
|
@ -101,6 +101,10 @@ export async function getExploreUrl(args: GetExploreUrlArguments): Promise<strin
|
|||||||
return urlUtil.renderUrl('/explore', { panes: exploreState, schemaVersion: 1 });
|
return urlUtil.renderUrl('/explore', { panes: exploreState, schemaVersion: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function requestIdGenerator(exploreId: string) {
|
||||||
|
return `explore_${exploreId}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildQueryTransaction(
|
export function buildQueryTransaction(
|
||||||
exploreId: string,
|
exploreId: string,
|
||||||
queries: DataQuery[],
|
queries: DataQuery[],
|
||||||
@ -123,7 +127,7 @@ export function buildQueryTransaction(
|
|||||||
panelId,
|
panelId,
|
||||||
targets: queries, // Datasources rely on DataQueries being passed under the targets key.
|
targets: queries, // Datasources rely on DataQueries being passed under the targets key.
|
||||||
range,
|
range,
|
||||||
requestId: 'explore_' + exploreId,
|
requestId: requestIdGenerator(exploreId),
|
||||||
rangeRaw: range.raw,
|
rangeRaw: range.raw,
|
||||||
scopedVars: {
|
scopedVars: {
|
||||||
__interval: { text: interval, value: interval },
|
__interval: { text: interval, value: interval },
|
||||||
|
@ -682,6 +682,7 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
|||||||
width={width}
|
width={width}
|
||||||
onClose={this.toggleShowQueryInspector}
|
onClose={this.toggleShowQueryInspector}
|
||||||
timeZone={timeZone}
|
timeZone={timeZone}
|
||||||
|
isMixed={datasourceInstance.meta.mixed || false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ErrorBoundaryAlert>
|
</ErrorBoundaryAlert>
|
||||||
|
@ -4,6 +4,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { LoadingState, InternalTimeZones, getDefaultTimeRange } from '@grafana/data';
|
import { LoadingState, InternalTimeZones, getDefaultTimeRange } from '@grafana/data';
|
||||||
|
import { InspectorStream } from 'app/core/services/backend_srv';
|
||||||
|
|
||||||
import { ExploreQueryInspector } from './ExploreQueryInspector';
|
import { ExploreQueryInspector } from './ExploreQueryInspector';
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ const setup = (propOverrides = {}) => {
|
|||||||
exploreId: 'left',
|
exploreId: 'left',
|
||||||
onClose: jest.fn(),
|
onClose: jest.fn(),
|
||||||
timeZone: InternalTimeZones.utc,
|
timeZone: InternalTimeZones.utc,
|
||||||
|
isMixed: false,
|
||||||
queryResponse: {
|
queryResponse: {
|
||||||
state: LoadingState.Done,
|
state: LoadingState.Done,
|
||||||
series: [],
|
series: [],
|
||||||
@ -143,15 +145,16 @@ describe('ExploreQueryInspector', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = (hideFromInspector = false) => ({
|
const response = (hideFromInspector = false): InspectorStream => {
|
||||||
|
return {
|
||||||
|
response: {
|
||||||
status: 1,
|
status: 1,
|
||||||
statusText: '',
|
statusText: '',
|
||||||
ok: true,
|
ok: true,
|
||||||
headers: {},
|
headers: new Headers(),
|
||||||
redirected: false,
|
redirected: false,
|
||||||
type: 'basic',
|
type: 'basic',
|
||||||
url: '',
|
url: '',
|
||||||
request: {},
|
|
||||||
data: {
|
data: {
|
||||||
test: {
|
test: {
|
||||||
testKey: 'Very unique test value',
|
testKey: 'Very unique test value',
|
||||||
@ -161,4 +164,7 @@ const response = (hideFromInspector = false) => ({
|
|||||||
url: '',
|
url: '',
|
||||||
hideFromInspector,
|
hideFromInspector,
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
requestId: 'explore_left',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -5,12 +5,14 @@ import { CoreApp, LoadingState } from '@grafana/data';
|
|||||||
import { reportInteraction } from '@grafana/runtime/src';
|
import { reportInteraction } from '@grafana/runtime/src';
|
||||||
import { defaultTimeZone, TimeZone } from '@grafana/schema';
|
import { defaultTimeZone, TimeZone } from '@grafana/schema';
|
||||||
import { TabbedContainer, TabConfig } from '@grafana/ui';
|
import { TabbedContainer, TabConfig } from '@grafana/ui';
|
||||||
|
import { requestIdGenerator } from 'app/core/utils/explore';
|
||||||
import { ExploreDrawer } from 'app/features/explore/ExploreDrawer';
|
import { ExploreDrawer } from 'app/features/explore/ExploreDrawer';
|
||||||
import { InspectDataTab } from 'app/features/inspector/InspectDataTab';
|
import { InspectDataTab } from 'app/features/inspector/InspectDataTab';
|
||||||
import { InspectErrorTab } from 'app/features/inspector/InspectErrorTab';
|
import { InspectErrorTab } from 'app/features/inspector/InspectErrorTab';
|
||||||
import { InspectJSONTab } from 'app/features/inspector/InspectJSONTab';
|
import { InspectJSONTab } from 'app/features/inspector/InspectJSONTab';
|
||||||
import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab';
|
import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab';
|
||||||
import { QueryInspector } from 'app/features/inspector/QueryInspector';
|
import { QueryInspector } from 'app/features/inspector/QueryInspector';
|
||||||
|
import { mixedRequestId } from 'app/plugins/datasource/mixed/MixedDataSource';
|
||||||
import { StoreState, ExploreItemState } from 'app/types';
|
import { StoreState, ExploreItemState } from 'app/types';
|
||||||
|
|
||||||
import { GetDataOptions } from '../query/state/PanelQueryRunner';
|
import { GetDataOptions } from '../query/state/PanelQueryRunner';
|
||||||
@ -22,12 +24,13 @@ interface DispatchProps {
|
|||||||
exploreId: string;
|
exploreId: string;
|
||||||
timeZone: TimeZone;
|
timeZone: TimeZone;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
isMixed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = DispatchProps & ConnectedProps<typeof connector>;
|
type Props = DispatchProps & ConnectedProps<typeof connector>;
|
||||||
|
|
||||||
export function ExploreQueryInspector(props: Props) {
|
export function ExploreQueryInspector(props: Props) {
|
||||||
const { width, onClose, queryResponse, timeZone } = props;
|
const { width, onClose, queryResponse, timeZone, isMixed, exploreId } = props;
|
||||||
const [dataOptions, setDataOptions] = useState<GetDataOptions>({
|
const [dataOptions, setDataOptions] = useState<GetDataOptions>({
|
||||||
withTransforms: false,
|
withTransforms: false,
|
||||||
withFieldConfig: true,
|
withFieldConfig: true,
|
||||||
@ -79,7 +82,11 @@ export function ExploreQueryInspector(props: Props) {
|
|||||||
value: 'query',
|
value: 'query',
|
||||||
icon: 'info-circle',
|
icon: 'info-circle',
|
||||||
content: (
|
content: (
|
||||||
<QueryInspector data={queryResponse} onRefreshQuery={() => props.runQueries({ exploreId: props.exploreId })} />
|
<QueryInspector
|
||||||
|
instanceId={isMixed ? mixedRequestId(0, requestIdGenerator(exploreId)) : requestIdGenerator(exploreId)}
|
||||||
|
data={queryResponse}
|
||||||
|
onRefreshQuery={() => props.runQueries({ exploreId })}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ interface ExecutedQueryInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
instanceId?: string; // Must match the prefix of the requestId of the query being inspected. For updating only one instance of the inspector in case of multiple instances, ie Explore split view
|
||||||
data: PanelData;
|
data: PanelData;
|
||||||
onRefreshQuery: () => void;
|
onRefreshQuery: () => void;
|
||||||
}
|
}
|
||||||
@ -49,7 +50,15 @@ export class QueryInspector extends PureComponent<Props, State> {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.subs.add(
|
this.subs.add(
|
||||||
backendSrv.getInspectorStream().subscribe({
|
backendSrv.getInspectorStream().subscribe({
|
||||||
next: (response) => this.onDataSourceResponse(response),
|
next: (response) => {
|
||||||
|
let update = true;
|
||||||
|
if (this.props.instanceId && response?.requestId) {
|
||||||
|
update = response.requestId.startsWith(this.props.instanceId);
|
||||||
|
}
|
||||||
|
if (update) {
|
||||||
|
return this.onDataSourceResponse(response.response);
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ import { getDataSourceSrv, toDataQueryError } from '@grafana/runtime';
|
|||||||
|
|
||||||
export const MIXED_DATASOURCE_NAME = '-- Mixed --';
|
export const MIXED_DATASOURCE_NAME = '-- Mixed --';
|
||||||
|
|
||||||
|
export const mixedRequestId = (queryIdx: number, requestId?: string) => `mixed-${queryIdx}-${requestId || ''}`;
|
||||||
|
|
||||||
export interface BatchedQueries {
|
export interface BatchedQueries {
|
||||||
datasource: Promise<DataSourceApi>;
|
datasource: Promise<DataSourceApi>;
|
||||||
targets: DataQuery[];
|
targets: DataQuery[];
|
||||||
@ -61,7 +63,7 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
|
|||||||
from(query.datasource).pipe(
|
from(query.datasource).pipe(
|
||||||
mergeMap((api: DataSourceApi) => {
|
mergeMap((api: DataSourceApi) => {
|
||||||
const dsRequest = cloneDeep(request);
|
const dsRequest = cloneDeep(request);
|
||||||
dsRequest.requestId = `mixed-${i}-${dsRequest.requestId || ''}`;
|
dsRequest.requestId = mixedRequestId(i, dsRequest.requestId);
|
||||||
dsRequest.targets = query.targets;
|
dsRequest.targets = query.targets;
|
||||||
|
|
||||||
return from(api.query(dsRequest)).pipe(
|
return from(api.query(dsRequest)).pipe(
|
||||||
@ -70,7 +72,7 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
|
|||||||
...response,
|
...response,
|
||||||
data: response.data || [],
|
data: response.data || [],
|
||||||
state: LoadingState.Loading,
|
state: LoadingState.Loading,
|
||||||
key: `mixed-${i}-${response.key || ''}`,
|
key: mixedRequestId(i, response.key),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
toArray(),
|
toArray(),
|
||||||
@ -83,7 +85,7 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
|
|||||||
data: [],
|
data: [],
|
||||||
state: LoadingState.Error,
|
state: LoadingState.Error,
|
||||||
error: err,
|
error: err,
|
||||||
key: `mixed-${i}-${dsRequest.requestId || ''}`,
|
key: mixedRequestId(i, dsRequest.requestId),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user