mirror of
				https://github.com/grafana/grafana.git
				synced 2025-02-25 18:55:37 -06:00 
			
		
		
		
	Add catch around JSON trace upload for more error handling (#38520)
This commit is contained in:
		| @@ -89,6 +89,18 @@ describe('JaegerDatasource', () => { | ||||
|     expect(field.values.length).toBe(2); | ||||
|   }); | ||||
|  | ||||
|   it('should fail on invalid json file upload', async () => { | ||||
|     const ds = new JaegerDatasource(defaultSettings); | ||||
|     ds.uploadedJson = JSON.stringify({ key: 'value', arr: [] }); | ||||
|     const response = await lastValueFrom( | ||||
|       ds.query({ | ||||
|         targets: [{ queryType: 'upload', refId: 'A' }], | ||||
|       } as any) | ||||
|     ); | ||||
|     expect(response.error?.message).toBeDefined(); | ||||
|     expect(response.data.length).toBe(0); | ||||
|   }); | ||||
|  | ||||
|   it('should return search results when the query type is search', async () => { | ||||
|     const mock = setupFetchMock({ data: [testResponse] }); | ||||
|     const ds = new JaegerDatasource(defaultSettings, timeSrvStub); | ||||
|   | ||||
| @@ -58,8 +58,13 @@ export class JaegerDatasource extends DataSourceApi<JaegerQuery> { | ||||
|       if (!this.uploadedJson) { | ||||
|         return of({ data: [] }); | ||||
|       } | ||||
|       const traceData = JSON.parse(this.uploadedJson as string).data[0]; | ||||
|       return of({ data: [createTraceFrame(traceData), ...createGraphFrames(traceData)] }); | ||||
|  | ||||
|       try { | ||||
|         const traceData = JSON.parse(this.uploadedJson as string).data[0]; | ||||
|         return of({ data: [createTraceFrame(traceData), ...createGraphFrames(traceData)] }); | ||||
|       } catch (error) { | ||||
|         return of({ error: { message: 'JSON is not valid Jaeger format' }, data: [] }); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     let jaegerQuery = pick(target, ['operation', 'service', 'tags', 'minDuration', 'maxDuration', 'limit']); | ||||
|   | ||||
| @@ -119,6 +119,18 @@ describe('Tempo data source', () => { | ||||
|     expect(field.values.length).toBe(6); | ||||
|   }); | ||||
|  | ||||
|   it('should fail on invalid json file upload', async () => { | ||||
|     const ds = new TempoDatasource(defaultSettings); | ||||
|     ds.uploadedJson = JSON.stringify(mockInvalidJson); | ||||
|     const response = await lastValueFrom( | ||||
|       ds.query({ | ||||
|         targets: [{ queryType: 'upload', refId: 'A' }], | ||||
|       } as any) | ||||
|     ); | ||||
|     expect(response.error?.message).toBeDefined(); | ||||
|     expect(response.data.length).toBe(0); | ||||
|   }); | ||||
|  | ||||
|   it('should build search query correctly', () => { | ||||
|     const ds = new TempoDatasource(defaultSettings); | ||||
|     const tempoQuery: TempoQuery = { | ||||
| @@ -229,3 +241,36 @@ const secondsPromMetric = new MutableDataFrame({ | ||||
|     { name: 'Value #tempo_service_graph_request_server_seconds_sum', values: [10, 40] }, | ||||
|   ], | ||||
| }); | ||||
|  | ||||
| const mockInvalidJson = { | ||||
|   batches: [ | ||||
|     { | ||||
|       resource: { | ||||
|         attributes: [], | ||||
|       }, | ||||
|       instrumentation_library_spans: [ | ||||
|         { | ||||
|           instrumentation_library: {}, | ||||
|           spans: [ | ||||
|             { | ||||
|               trace_id: 'AAAAAAAAAABguiq7RPE+rg==', | ||||
|               span_id: 'cmteMBAvwNA=', | ||||
|               parentSpanId: 'OY8PIaPbma4=', | ||||
|               name: 'HTTP GET - root', | ||||
|               kind: 'SPAN_KIND_SERVER', | ||||
|               startTimeUnixNano: '1627471657255809000', | ||||
|               endTimeUnixNano: '1627471657256268000', | ||||
|               attributes: [ | ||||
|                 { key: 'http.status_code', value: { intValue: '200' } }, | ||||
|                 { key: 'http.method', value: { stringValue: 'GET' } }, | ||||
|                 { key: 'http.url', value: { stringValue: '/' } }, | ||||
|                 { key: 'component', value: { stringValue: 'net/http' } }, | ||||
|               ], | ||||
|               status: {}, | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|       ], | ||||
|     }, | ||||
|   ], | ||||
| }; | ||||
|   | ||||
| @@ -115,7 +115,7 @@ export class TempoDatasource extends DataSourceWithBackend<TempoQuery, TempoJson | ||||
|       if (this.uploadedJson) { | ||||
|         const otelTraceData = JSON.parse(this.uploadedJson as string); | ||||
|         if (!otelTraceData.batches) { | ||||
|           subQueries.push(of({ error: { message: 'JSON is not valid opentelemetry format' }, data: [] })); | ||||
|           subQueries.push(of({ error: { message: 'JSON is not valid OpenTelemetry format' }, data: [] })); | ||||
|         } else { | ||||
|           subQueries.push(of(transformFromOTEL(otelTraceData.batches))); | ||||
|         } | ||||
|   | ||||
| @@ -250,25 +250,28 @@ export function transformFromOTLP( | ||||
|       preferredVisualisationType: 'trace', | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|   for (const data of traceData) { | ||||
|     const { serviceName, serviceTags } = resourceToProcess(data.resource); | ||||
|     for (const librarySpan of data.instrumentationLibrarySpans) { | ||||
|       for (const span of librarySpan.spans) { | ||||
|         frame.add({ | ||||
|           traceID: transformBase64IDToHexString(span.traceId), | ||||
|           spanID: transformBase64IDToHexString(span.spanId), | ||||
|           parentSpanID: transformBase64IDToHexString(span.parentSpanId || ''), | ||||
|           operationName: span.name || '', | ||||
|           serviceName, | ||||
|           serviceTags, | ||||
|           startTime: span.startTimeUnixNano! / 1000000, | ||||
|           duration: (span.endTimeUnixNano! - span.startTimeUnixNano!) / 1000000, | ||||
|           tags: getSpanTags(span, librarySpan.instrumentationLibrary), | ||||
|           logs: getLogs(span), | ||||
|         } as TraceSpanRow); | ||||
|   try { | ||||
|     for (const data of traceData) { | ||||
|       const { serviceName, serviceTags } = resourceToProcess(data.resource); | ||||
|       for (const librarySpan of data.instrumentationLibrarySpans) { | ||||
|         for (const span of librarySpan.spans) { | ||||
|           frame.add({ | ||||
|             traceID: transformBase64IDToHexString(span.traceId), | ||||
|             spanID: transformBase64IDToHexString(span.spanId), | ||||
|             parentSpanID: transformBase64IDToHexString(span.parentSpanId || ''), | ||||
|             operationName: span.name || '', | ||||
|             serviceName, | ||||
|             serviceTags, | ||||
|             startTime: span.startTimeUnixNano! / 1000000, | ||||
|             duration: (span.endTimeUnixNano! - span.startTimeUnixNano!) / 1000000, | ||||
|             tags: getSpanTags(span, librarySpan.instrumentationLibrary), | ||||
|             logs: getLogs(span), | ||||
|           } as TraceSpanRow); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } catch (error) { | ||||
|     return { error: { message: 'JSON is not valid OpenTelemetry format' }, data: [] }; | ||||
|   } | ||||
|  | ||||
|   return { data: [frame, ...createGraphFrames(frame)] }; | ||||
|   | ||||
| @@ -42,6 +42,18 @@ describe('ZipkinDatasource', () => { | ||||
|       expect(field.type).toBe(FieldType.string); | ||||
|       expect(field.values.length).toBe(3); | ||||
|     }); | ||||
|  | ||||
|     it('should fail on invalid json file upload', async () => { | ||||
|       const ds = new ZipkinDatasource(defaultSettings); | ||||
|       ds.uploadedJson = JSON.stringify({ key: 'value', arr: [] }); | ||||
|       const response = await lastValueFrom( | ||||
|         ds.query({ | ||||
|           targets: [{ queryType: 'upload', refId: 'A' }], | ||||
|         } as any) | ||||
|       ); | ||||
|       expect(response.error?.message).toBeDefined(); | ||||
|       expect(response.data.length).toBe(0); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   describe('metadataRequest', () => { | ||||
|   | ||||
| @@ -28,8 +28,13 @@ export class ZipkinDatasource extends DataSourceApi<ZipkinQuery> { | ||||
|       if (!this.uploadedJson) { | ||||
|         return of({ data: [] }); | ||||
|       } | ||||
|       const traceData = JSON.parse(this.uploadedJson as string); | ||||
|       return of(responseToDataQueryResponse({ data: traceData })); | ||||
|  | ||||
|       try { | ||||
|         const traceData = JSON.parse(this.uploadedJson as string); | ||||
|         return of(responseToDataQueryResponse({ data: traceData })); | ||||
|       } catch (error) { | ||||
|         return of({ error: { message: 'JSON is not valid Zipkin format' }, data: [] }); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (target.query) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user