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:
		| @@ -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; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -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) => { | ||||
|   | ||||
| @@ -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 }, | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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,22 +145,26 @@ describe('ExploreQueryInspector', () => { | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| const response = (hideFromInspector = false) => ({ | ||||
|   status: 1, | ||||
|   statusText: '', | ||||
|   ok: true, | ||||
|   headers: {}, | ||||
|   redirected: false, | ||||
|   type: 'basic', | ||||
|   url: '', | ||||
|   request: {}, | ||||
|   data: { | ||||
|     test: { | ||||
|       testKey: 'Very unique test value', | ||||
| const response = (hideFromInspector = false): InspectorStream => { | ||||
|   return { | ||||
|     response: { | ||||
|       status: 1, | ||||
|       statusText: '', | ||||
|       ok: true, | ||||
|       headers: new Headers(), | ||||
|       redirected: false, | ||||
|       type: 'basic', | ||||
|       url: '', | ||||
|       data: { | ||||
|         test: { | ||||
|           testKey: 'Very unique test value', | ||||
|         }, | ||||
|       }, | ||||
|       config: { | ||||
|         url: '', | ||||
|         hideFromInspector, | ||||
|       }, | ||||
|     }, | ||||
|   }, | ||||
|   config: { | ||||
|     url: '', | ||||
|     hideFromInspector, | ||||
|   }, | ||||
| }); | ||||
|     requestId: 'explore_left', | ||||
|   }; | ||||
| }; | ||||
|   | ||||
| @@ -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 })} | ||||
|       /> | ||||
|     ), | ||||
|   }; | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|           } | ||||
|         }, | ||||
|       }) | ||||
|     ); | ||||
|   } | ||||
|   | ||||
| @@ -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), | ||||
|                 }, | ||||
|               ]); | ||||
|             }) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user