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:
Kristina 2024-02-16 08:10:22 -06:00 committed by GitHub
parent 9e04fd0fb7
commit 7b4dd4fe47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 64 additions and 30 deletions

View File

@ -55,11 +55,16 @@ export interface FolderRequestOptions {
const GRAFANA_TRACEID_HEADER = 'grafana-trace-id';
export interface InspectorStream {
response: FetchResponse | FetchError;
requestId?: string;
}
export class BackendSrv implements BackendService {
private inFlightRequests: Subject<string> = new Subject<string>();
private HTTP_REQUEST_CANCELED = -1;
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 responseQueue: ResponseQueue;
private _tokenRotationInProgress?: Observable<FetchResponse> | null = null;
@ -333,7 +338,7 @@ export class BackendSrv implements BackendService {
}, 50);
}
this.inspectorStream.next(err);
this.inspectorStream.next({ response: err, requestId: options.requestId });
return err;
}
@ -356,7 +361,7 @@ export class BackendSrv implements BackendService {
}),
tap((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;
}

View File

@ -664,7 +664,7 @@ describe('backendSrv', () => {
let inspectorPacket: FetchResponse | FetchError;
backendSrv.getInspectorStream().subscribe({
next: (rsp) => (inspectorPacket = rsp),
next: (rsp) => (inspectorPacket = rsp.response),
});
await backendSrv.datasourceRequest(options).catch((error) => {

View File

@ -101,6 +101,10 @@ export async function getExploreUrl(args: GetExploreUrlArguments): Promise<strin
return urlUtil.renderUrl('/explore', { panes: exploreState, schemaVersion: 1 });
}
export function requestIdGenerator(exploreId: string) {
return `explore_${exploreId}`;
}
export function buildQueryTransaction(
exploreId: string,
queries: DataQuery[],
@ -123,7 +127,7 @@ export function buildQueryTransaction(
panelId,
targets: queries, // Datasources rely on DataQueries being passed under the targets key.
range,
requestId: 'explore_' + exploreId,
requestId: requestIdGenerator(exploreId),
rangeRaw: range.raw,
scopedVars: {
__interval: { text: interval, value: interval },

View File

@ -682,6 +682,7 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
width={width}
onClose={this.toggleShowQueryInspector}
timeZone={timeZone}
isMixed={datasourceInstance.meta.mixed || false}
/>
)}
</ErrorBoundaryAlert>

View File

@ -4,6 +4,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { Observable } from 'rxjs';
import { LoadingState, InternalTimeZones, getDefaultTimeRange } from '@grafana/data';
import { InspectorStream } from 'app/core/services/backend_srv';
import { ExploreQueryInspector } from './ExploreQueryInspector';
@ -50,6 +51,7 @@ const setup = (propOverrides = {}) => {
exploreId: 'left',
onClose: jest.fn(),
timeZone: InternalTimeZones.utc,
isMixed: false,
queryResponse: {
state: LoadingState.Done,
series: [],
@ -143,15 +145,16 @@ describe('ExploreQueryInspector', () => {
});
});
const response = (hideFromInspector = false) => ({
const response = (hideFromInspector = false): InspectorStream => {
return {
response: {
status: 1,
statusText: '',
ok: true,
headers: {},
headers: new Headers(),
redirected: false,
type: 'basic',
url: '',
request: {},
data: {
test: {
testKey: 'Very unique test value',
@ -161,4 +164,7 @@ const response = (hideFromInspector = false) => ({
url: '',
hideFromInspector,
},
});
},
requestId: 'explore_left',
};
};

View File

@ -5,12 +5,14 @@ import { CoreApp, LoadingState } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime/src';
import { defaultTimeZone, TimeZone } from '@grafana/schema';
import { TabbedContainer, TabConfig } from '@grafana/ui';
import { requestIdGenerator } from 'app/core/utils/explore';
import { ExploreDrawer } from 'app/features/explore/ExploreDrawer';
import { InspectDataTab } from 'app/features/inspector/InspectDataTab';
import { InspectErrorTab } from 'app/features/inspector/InspectErrorTab';
import { InspectJSONTab } from 'app/features/inspector/InspectJSONTab';
import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab';
import { QueryInspector } from 'app/features/inspector/QueryInspector';
import { mixedRequestId } from 'app/plugins/datasource/mixed/MixedDataSource';
import { StoreState, ExploreItemState } from 'app/types';
import { GetDataOptions } from '../query/state/PanelQueryRunner';
@ -22,12 +24,13 @@ interface DispatchProps {
exploreId: string;
timeZone: TimeZone;
onClose: () => void;
isMixed: boolean;
}
type Props = DispatchProps & ConnectedProps<typeof connector>;
export function ExploreQueryInspector(props: Props) {
const { width, onClose, queryResponse, timeZone } = props;
const { width, onClose, queryResponse, timeZone, isMixed, exploreId } = props;
const [dataOptions, setDataOptions] = useState<GetDataOptions>({
withTransforms: false,
withFieldConfig: true,
@ -79,7 +82,11 @@ export function ExploreQueryInspector(props: Props) {
value: 'query',
icon: 'info-circle',
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 })}
/>
),
};

View File

@ -19,6 +19,7 @@ interface ExecutedQueryInfo {
}
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;
onRefreshQuery: () => void;
}
@ -49,7 +50,15 @@ export class QueryInspector extends PureComponent<Props, State> {
componentDidMount() {
this.subs.add(
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);
}
},
})
);
}

View File

@ -15,6 +15,8 @@ import { getDataSourceSrv, toDataQueryError } from '@grafana/runtime';
export const MIXED_DATASOURCE_NAME = '-- Mixed --';
export const mixedRequestId = (queryIdx: number, requestId?: string) => `mixed-${queryIdx}-${requestId || ''}`;
export interface BatchedQueries {
datasource: Promise<DataSourceApi>;
targets: DataQuery[];
@ -61,7 +63,7 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
from(query.datasource).pipe(
mergeMap((api: DataSourceApi) => {
const dsRequest = cloneDeep(request);
dsRequest.requestId = `mixed-${i}-${dsRequest.requestId || ''}`;
dsRequest.requestId = mixedRequestId(i, dsRequest.requestId);
dsRequest.targets = query.targets;
return from(api.query(dsRequest)).pipe(
@ -70,7 +72,7 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
...response,
data: response.data || [],
state: LoadingState.Loading,
key: `mixed-${i}-${response.key || ''}`,
key: mixedRequestId(i, response.key),
};
}),
toArray(),
@ -83,7 +85,7 @@ export class MixedDatasource extends DataSourceApi<DataQuery> {
data: [],
state: LoadingState.Error,
error: err,
key: `mixed-${i}-${dsRequest.requestId || ''}`,
key: mixedRequestId(i, dsRequest.requestId),
},
]);
})