diff --git a/packages/jaeger-ui/src/utils/fixtures/oltp2jaeger-multi-out.json b/packages/jaeger-ui/src/utils/fixtures/oltp2jaeger-multi-out.json new file mode 100644 index 0000000000..6e37231af0 --- /dev/null +++ b/packages/jaeger-ui/src/utils/fixtures/oltp2jaeger-multi-out.json @@ -0,0 +1,524 @@ +{ + "data": [ + { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spans": [ + { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "26f5ef9dbb885479", + "operationName": "ohboy.do", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "49b8e9efa1e8a3b1", + "span": { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "49b8e9efa1e8a3b1", + "operationName": "info", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "span": { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "operationName": "main", + "references": [], + "startTime": 1711138246940000, + "duration": 176321, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 0, + "depth": 0, + "hasChildren": true, + "childSpanIds": ["49b8e9efa1e8a3b1"] + } + } + ], + "startTime": 1711138246943000, + "duration": 173150, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 3000, + "depth": 1, + "hasChildren": true, + "childSpanIds": ["e76628f4e6dde174", "26f5ef9dbb885479"] + } + } + ], + "startTime": 1711138246978000, + "duration": 46460, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "ohboy" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 38000, + "depth": 2, + "hasChildren": false, + "childSpanIds": [] + }, + { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "e76628f4e6dde174", + "operationName": "ohboy.do", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "49b8e9efa1e8a3b1", + "span": { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "49b8e9efa1e8a3b1", + "operationName": "info", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "span": { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "operationName": "main", + "references": [], + "startTime": 1711138246940000, + "duration": 176321, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 0, + "depth": 0, + "hasChildren": true, + "childSpanIds": ["49b8e9efa1e8a3b1"] + } + } + ], + "startTime": 1711138246943000, + "duration": 173150, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 3000, + "depth": 1, + "hasChildren": true, + "childSpanIds": ["e76628f4e6dde174", "26f5ef9dbb885479"] + } + } + ], + "startTime": 1711138247025000, + "duration": 71355, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "ohboy" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 85000, + "depth": 2, + "hasChildren": false, + "childSpanIds": [] + }, + { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "49b8e9efa1e8a3b1", + "operationName": "info", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "span": { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "operationName": "main", + "references": [], + "startTime": 1711138246940000, + "duration": 176321, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 0, + "depth": 0, + "hasChildren": true, + "childSpanIds": ["49b8e9efa1e8a3b1"] + } + } + ], + "startTime": 1711138246943000, + "duration": 173150, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 3000, + "depth": 1, + "hasChildren": true, + "childSpanIds": ["e76628f4e6dde174", "26f5ef9dbb885479"] + }, + { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "operationName": "main", + "references": [], + "startTime": 1711138246940000, + "duration": 176321, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 0, + "depth": 0, + "hasChildren": true, + "childSpanIds": ["49b8e9efa1e8a3b1"] + } + ], + "processes": { + "p1": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + } + }, + "warnings": null + } + ], + "total": 0, + "limit": 0, + "offset": 0, + "errors": null +} diff --git a/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-multi-in-combined.json b/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-multi-in-combined.json new file mode 100644 index 0000000000..eb2bc1ae7c --- /dev/null +++ b/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-multi-in-combined.json @@ -0,0 +1,202 @@ +{ + "resourceSpans": [ + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "example-trace" + } + }, + { + "key": "telemetry.sdk.language", + "value": { + "stringValue": "nodejs" + } + }, + { + "key": "telemetry.sdk.name", + "value": { + "stringValue": "opentelemetry" + } + }, + { + "key": "telemetry.sdk.version", + "value": { + "stringValue": "1.22.0" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "ohboy" + }, + "spans": [ + { + "traceId": "c620759e5d60fafb8ee0922b30e06bc6", + "spanId": "26f5ef9dbb885479", + "parentSpanId": "49b8e9efa1e8a3b1", + "name": "ohboy.do", + "kind": 1, + "startTimeUnixNano": "1711138246978000000", + "endTimeUnixNano": "1711138247024460625", + "status": {} + } + ] + } + ] + }, + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "example-trace" + } + }, + { + "key": "telemetry.sdk.language", + "value": { + "stringValue": "nodejs" + } + }, + { + "key": "telemetry.sdk.name", + "value": { + "stringValue": "opentelemetry" + } + }, + { + "key": "telemetry.sdk.version", + "value": { + "stringValue": "1.22.0" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "ohboy" + }, + "spans": [ + { + "traceId": "c620759e5d60fafb8ee0922b30e06bc6", + "spanId": "e76628f4e6dde174", + "parentSpanId": "49b8e9efa1e8a3b1", + "name": "ohboy.do", + "kind": 1, + "startTimeUnixNano": "1711138247025000000", + "endTimeUnixNano": "1711138247096355250", + "status": {} + } + ] + } + ] + }, + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "example-trace" + } + }, + { + "key": "telemetry.sdk.language", + "value": { + "stringValue": "nodejs" + } + }, + { + "key": "telemetry.sdk.name", + "value": { + "stringValue": "opentelemetry" + } + }, + { + "key": "telemetry.sdk.version", + "value": { + "stringValue": "1.22.0" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "widgets", + "version": "1.2.3" + }, + "spans": [ + { + "traceId": "c620759e5d60fafb8ee0922b30e06bc6", + "spanId": "49b8e9efa1e8a3b1", + "parentSpanId": "eff3153be6b1fb93", + "name": "info", + "kind": 1, + "startTimeUnixNano": "1711138246943000000", + "endTimeUnixNano": "1711138247116150875", + "status": {} + } + ] + } + ] + }, + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "example-trace" + } + }, + { + "key": "telemetry.sdk.language", + "value": { + "stringValue": "nodejs" + } + }, + { + "key": "telemetry.sdk.name", + "value": { + "stringValue": "opentelemetry" + } + }, + { + "key": "telemetry.sdk.version", + "value": { + "stringValue": "1.22.0" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "widgets", + "version": "1.2.3" + }, + "spans": [ + { + "traceId": "c620759e5d60fafb8ee0922b30e06bc6", + "spanId": "eff3153be6b1fb93", + "parentSpanId": "", + "name": "main", + "kind": 1, + "startTimeUnixNano": "1711138246940000000", + "endTimeUnixNano": "1711138247116321958", + "status": {} + } + ] + } + ] + } + ] +} diff --git a/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-multi-in.json.txt b/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-multi-in.json.txt new file mode 100644 index 0000000000..5c92bedab2 --- /dev/null +++ b/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-multi-in.json.txt @@ -0,0 +1,4 @@ +{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"example-trace"}},{"key":"telemetry.sdk.language","value":{"stringValue":"nodejs"}},{"key":"telemetry.sdk.name","value":{"stringValue":"opentelemetry"}},{"key":"telemetry.sdk.version","value":{"stringValue":"1.22.0"}}]},"scopeSpans":[{"scope":{"name":"ohboy"},"spans":[{"traceId":"c620759e5d60fafb8ee0922b30e06bc6","spanId":"26f5ef9dbb885479","parentSpanId":"49b8e9efa1e8a3b1","name":"ohboy.do","kind":1,"startTimeUnixNano":"1711138246978000000","endTimeUnixNano":"1711138247024460625","status":{}}]}]}]} +{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"example-trace"}},{"key":"telemetry.sdk.language","value":{"stringValue":"nodejs"}},{"key":"telemetry.sdk.name","value":{"stringValue":"opentelemetry"}},{"key":"telemetry.sdk.version","value":{"stringValue":"1.22.0"}}]},"scopeSpans":[{"scope":{"name":"ohboy"},"spans":[{"traceId":"c620759e5d60fafb8ee0922b30e06bc6","spanId":"e76628f4e6dde174","parentSpanId":"49b8e9efa1e8a3b1","name":"ohboy.do","kind":1,"startTimeUnixNano":"1711138247025000000","endTimeUnixNano":"1711138247096355250","status":{}}]}]}]} +{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"example-trace"}},{"key":"telemetry.sdk.language","value":{"stringValue":"nodejs"}},{"key":"telemetry.sdk.name","value":{"stringValue":"opentelemetry"}},{"key":"telemetry.sdk.version","value":{"stringValue":"1.22.0"}}]},"scopeSpans":[{"scope":{"name":"widgets","version":"1.2.3"},"spans":[{"traceId":"c620759e5d60fafb8ee0922b30e06bc6","spanId":"49b8e9efa1e8a3b1","parentSpanId":"eff3153be6b1fb93","name":"info","kind":1,"startTimeUnixNano":"1711138246943000000","endTimeUnixNano":"1711138247116150875","status":{}}]}]}]} +{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"example-trace"}},{"key":"telemetry.sdk.language","value":{"stringValue":"nodejs"}},{"key":"telemetry.sdk.name","value":{"stringValue":"opentelemetry"}},{"key":"telemetry.sdk.version","value":{"stringValue":"1.22.0"}}]},"scopeSpans":[{"scope":{"name":"widgets","version":"1.2.3"},"spans":[{"traceId":"c620759e5d60fafb8ee0922b30e06bc6","spanId":"eff3153be6b1fb93","parentSpanId":"","name":"main","kind":1,"startTimeUnixNano":"1711138246940000000","endTimeUnixNano":"1711138247116321958","status":{}}]}]}]} diff --git a/packages/jaeger-ui/src/utils/readJsonFile.test.js b/packages/jaeger-ui/src/utils/readJsonFile.test.js index 865cd33241..7c910a8493 100644 --- a/packages/jaeger-ui/src/utils/readJsonFile.test.js +++ b/packages/jaeger-ui/src/utils/readJsonFile.test.js @@ -17,12 +17,29 @@ import lodash from 'lodash'; import readJsonFile from './readJsonFile'; import JaegerAPI from '../api/jaeger'; +let OTLPTrace; +let jaegerTrace; +let OTLPTraceMulti; +let jaegerTraceMulti; + +beforeAll(() => { + OTLPTrace = JSON.parse(fs.readFileSync('src/utils/fixtures/otlp2jaeger-in.json', 'utf-8')); + jaegerTrace = JSON.parse(fs.readFileSync('src/utils/fixtures/otlp2jaeger-out.json', 'utf-8')); + OTLPTraceMulti = JSON.parse( + fs.readFileSync('src/utils/fixtures/otlp2jaeger-multi-in-combined.json', 'utf-8') + ); + jaegerTraceMulti = JSON.parse(fs.readFileSync('src/utils/fixtures/oltp2jaeger-multi-out.json', 'utf-8')); +}); + jest.spyOn(JaegerAPI, 'transformOTLP').mockImplementation(APICallRequest => { - const OTLPTrace = JSON.parse(fs.readFileSync('src/utils/fixtures/otlp2jaeger-in.json', 'utf-8')); - const jaegerTrace = JSON.parse(fs.readFileSync('src/utils/fixtures/otlp2jaeger-out.json', 'utf-8')); if (lodash.isEqual(APICallRequest, OTLPTrace)) { return Promise.resolve(jaegerTrace); } + + if (lodash.isEqual(APICallRequest, OTLPTraceMulti)) { + return Promise.resolve(jaegerTraceMulti); + } + // This defines case where API call errors out even after detecting a `resourceSpan` in the request return Promise.reject(); }); @@ -53,8 +70,8 @@ describe('fileReader.readJsonFile', () => { }); it('loads JSON data (OTLP), successfully', () => { - const inObj = JSON.parse(fs.readFileSync('src/utils/fixtures/otlp2jaeger-in.json', 'utf-8')); - const outObj = JSON.parse(fs.readFileSync('src/utils/fixtures/otlp2jaeger-out.json', 'utf-8')); + const inObj = OTLPTrace; + const outObj = jaegerTrace; const file = new File([JSON.stringify(inObj)], 'foo.json'); const p = readJsonFile({ file }); return expect(p).resolves.toMatchObject(outObj); @@ -67,9 +84,17 @@ describe('fileReader.readJsonFile', () => { return expect(p).rejects.toMatchObject(expect.any(Error)); }); - it('rejects on malform JSON', () => { + it('rejects malformed JSON', () => { const file = new File(['not-json'], 'foo.json'); const p = readJsonFile({ file }); return expect(p).rejects.toMatchObject(expect.any(Error)); }); + + it('loads JSON-per-line data', () => { + const expectedOutput = jaegerTraceMulti; + const fileContent = fs.readFileSync('src/utils/fixtures/otlp2jaeger-multi-in.json.txt', 'utf-8'); + const file = new File([fileContent], 'multi.json', { type: 'application/json' }); + const p = readJsonFile({ file }); + return expect(p).resolves.toMatchObject(expectedOutput); + }); }); diff --git a/packages/jaeger-ui/src/utils/readJsonFile.tsx b/packages/jaeger-ui/src/utils/readJsonFile.tsx index be404c56be..d680c0df33 100644 --- a/packages/jaeger-ui/src/utils/readJsonFile.tsx +++ b/packages/jaeger-ui/src/utils/readJsonFile.tsx @@ -14,6 +14,22 @@ import JaegerAPI from '../api/jaeger'; +function tryParseMultiLineInput(input: string): any[] { + const jsonStrings = input.split('\n').filter((line: string) => line.trim() !== ''); + const parsedObjects: any[] = []; + + jsonStrings.forEach((jsonString: string, index: number) => { + try { + const traceObj = JSON.parse(jsonString.trim()); + parsedObjects.push(traceObj); + } catch (error) { + throw new Error(`Error parsing JSON at line ${index + 1}: ${(error as Error).message}`); + } + }); + + return parsedObjects; +} + export default function readJsonFile(fileList: { file: File }): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -22,21 +38,36 @@ export default function readJsonFile(fileList: { file: File }): Promise reject(new Error('Invalid result type')); return; } + let traceObj; try { - const traceObj = JSON.parse(reader.result); - if ('resourceSpans' in traceObj) { - JaegerAPI.transformOTLP(traceObj) - .then((result: string) => { - resolve(result); - }) - .catch(() => { - reject(new Error(`Error converting traces to OTLP`)); - }); - } else { - resolve(traceObj); + traceObj = JSON.parse(reader.result); + } catch (error) { + try { + traceObj = tryParseMultiLineInput(reader.result); + } catch (error) { + reject(error); + return; } - } catch (error: unknown) { - reject(new Error(`Error parsing JSON: ${(error as Error).message}`)); + } + if (Array.isArray(traceObj) && traceObj.every(obj => 'resourceSpans' in obj)) { + const mergedResourceSpans = traceObj.reduce((acc, obj) => { + acc.push(...obj.resourceSpans); + return acc; + }, []); + + traceObj = { resourceSpans: mergedResourceSpans }; + } + + if ('resourceSpans' in traceObj) { + JaegerAPI.transformOTLP(traceObj) + .then((result: string) => { + resolve(result); + }) + .catch(() => { + reject(new Error('Error converting traces to OTLP')); + }); + } else { + resolve(traceObj); } }; reader.onerror = () => { @@ -48,7 +79,7 @@ export default function readJsonFile(fileList: { file: File }): Promise }; try { reader.readAsText(fileList.file); - } catch (error: unknown) { + } catch (error) { reject(new Error(`Error reading the JSON file: ${(error as Error).message}`)); } });