Tempo: Don't show error when running query without traceId (#43676)

* Check for traceId and return EMPTY observable if no value

* Make sure we change status on complete

* Add tests

* Fix test
pull/43725/head
Andrej Ocenas 3 years ago committed by GitHub
parent bac9253fdc
commit 88d17c4998
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      public/app/features/explore/state/query.test.ts
  2. 13
      public/app/features/explore/state/query.ts
  3. 11
      public/app/plugins/datasource/tempo/datasource.test.ts
  4. 44
      public/app/plugins/datasource/tempo/datasource.ts

@ -13,7 +13,7 @@ import {
storeLogsVolumeDataProviderAction,
} from './query';
import { ExploreId, ExploreItemState, StoreState, ThunkDispatch } from 'app/types';
import { interval, Observable, of } from 'rxjs';
import { EMPTY, interval, Observable, of } from 'rxjs';
import {
ArrayVector,
DataFrame,
@ -91,10 +91,8 @@ function setupQueryResponse(state: StoreState) {
describe('runQueries', () => {
it('should pass dataFrames to state even if there is error in response', async () => {
setTimeSrv({
init() {},
} as any);
const { dispatch, getState }: { dispatch: ThunkDispatch; getState: () => StoreState } = configureStore({
setTimeSrv({ init() {} } as any);
const { dispatch, getState } = configureStore({
...(defaultInitialState as any),
});
setupQueryResponse(getState());
@ -102,6 +100,17 @@ describe('runQueries', () => {
expect(getState().explore[ExploreId.left].showMetrics).toBeTruthy();
expect(getState().explore[ExploreId.left].graphResult).toBeDefined();
});
it('should set state to done if query completes without emitting', async () => {
setTimeSrv({ init() {} } as any);
const { dispatch, getState } = configureStore({
...(defaultInitialState as any),
});
(getState().explore[ExploreId.left].datasourceInstance?.query as Mock).mockReturnValueOnce(EMPTY);
await dispatch(runQueries(ExploreId.left));
await new Promise((resolve) => setTimeout(() => resolve(''), 500));
expect(getState().explore[ExploreId.left].queryResponse.state).toBe(LoadingState.Done);
});
});
describe('running queries', () => {

@ -449,8 +449,8 @@ export const runQueries = (
)
)
)
.subscribe(
(data) => {
.subscribe({
next(data) {
dispatch(queryStreamUpdatedAction({ exploreId, response: data }));
// Keep scanning for results if this was the last scanning transaction
@ -465,12 +465,15 @@ export const runQueries = (
}
}
},
(error) => {
error(error) {
dispatch(notifyApp(createErrorNotification('Query processing error', error)));
dispatch(changeLoadingStateAction({ exploreId, loadingState: LoadingState.Error }));
console.error(error);
}
);
},
complete() {
dispatch(changeLoadingStateAction({ exploreId, loadingState: LoadingState.Done }));
},
});
if (live) {
dispatch(

@ -16,6 +16,15 @@ import { DEFAULT_LIMIT, TempoJsonData, TempoDatasource, TempoQuery } from './dat
import mockJson from './mockJsonResponse.json';
describe('Tempo data source', () => {
it('returns empty response when traceId is empty', async () => {
const ds = new TempoDatasource(defaultSettings);
const response = await lastValueFrom(
ds.query({ targets: [{ refId: 'refid1', queryType: 'traceId', query: '' } as Partial<TempoQuery>] } as any),
{ defaultValue: 'empty' }
);
expect(response).toBe('empty');
});
it('parses json fields from backend', async () => {
setupBackendSrv(
new MutableDataFrame({
@ -34,7 +43,7 @@ describe('Tempo data source', () => {
})
);
const ds = new TempoDatasource(defaultSettings);
const response = await lastValueFrom(ds.query({ targets: [{ refId: 'refid1' }] } as any));
const response = await lastValueFrom(ds.query({ targets: [{ refId: 'refid1', query: '12345' }] } as any));
expect(
(response.data[0] as DataFrame).fields.map((f) => ({

@ -1,4 +1,4 @@
import { from, merge, Observable, of, throwError } from 'rxjs';
import { EMPTY, from, merge, Observable, of, throwError } from 'rxjs';
import { catchError, map, mergeMap, toArray } from 'rxjs/operators';
import {
DataQuery,
@ -41,7 +41,7 @@ export interface TempoJsonData extends DataSourceJsonData {
nodeGraph?: NodeGraphOptions;
}
export type TempoQuery = {
export interface TempoQuery extends DataQuery {
query: string;
// Query to find list of traces, e.g., via Loki
linkedQuery?: LokiQuery;
@ -53,7 +53,7 @@ export type TempoQuery = {
maxDuration?: string;
limit?: number;
serviceMapQuery?: string;
} & DataQuery;
}
export const DEFAULT_LIMIT = 20;
@ -152,22 +152,38 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson
}
if (targets.traceId?.length > 0) {
const traceRequest: DataQueryRequest<TempoQuery> = { ...options, targets: targets.traceId };
subQueries.push(
super.query(traceRequest).pipe(
map((response) => {
if (response.error) {
return response;
}
return transformTrace(response, this.nodeGraph?.enabled);
})
)
);
subQueries.push(this.handleTraceIdQuery(options, targets.traceId));
}
return merge(...subQueries);
}
/**
* Handles the simplest of the queries where we have just a trace id and return trace data for it.
* @param options
* @param targets
* @private
*/
private handleTraceIdQuery(
options: DataQueryRequest<TempoQuery>,
targets: TempoQuery[]
): Observable<DataQueryResponse> {
const validTargets = targets.filter((t) => t.query);
if (!validTargets.length) {
return EMPTY;
}
const traceRequest: DataQueryRequest<TempoQuery> = { ...options, targets: validTargets };
return super.query(traceRequest).pipe(
map((response) => {
if (response.error) {
return response;
}
return transformTrace(response, this.nodeGraph?.enabled);
})
);
}
async metadataRequest(url: string, params = {}) {
return await this._request(url, params, { method: 'GET', hideFromInspector: true }).toPromise();
}

Loading…
Cancel
Save