diff --git a/packages/jaeger-ui/src/api/jaeger.js b/packages/jaeger-ui/src/api/jaeger.js index 04ab7f4923..4ce0ee7706 100644 --- a/packages/jaeger-ui/src/api/jaeger.js +++ b/packages/jaeger-ui/src/api/jaeger.js @@ -103,6 +103,9 @@ const JaegerAPI = { fetchServiceOperations(serviceName) { return getJSON(`${this.apiRoot}services/${encodeURIComponent(serviceName)}/operations`); }, + transformOTLP(traces) { + return getJSON(`${this.apiRoot}transform`, { method: 'POST', body: JSON.stringify(traces) }); + }, fetchServiceServerOps(service) { return getJSON(`${this.apiRoot}operations`, { query: { diff --git a/packages/jaeger-ui/src/api/jaeger.test.js b/packages/jaeger-ui/src/api/jaeger.test.js index 7bad3bcf6b..23834bf67d 100644 --- a/packages/jaeger-ui/src/api/jaeger.test.js +++ b/packages/jaeger-ui/src/api/jaeger.test.js @@ -113,6 +113,15 @@ describe('fetchServiceServerOps', () => { }); }); +describe('transformOTLP', () => { + it('GETs the transformed trace of Jaeger kind when provided with OTLP', () => { + const trace = JSON.parse('{"test" : true}'); + const body = { ...defaultOptions, body: JSON.stringify(trace), method: 'POST' }; + JaegerAPI.transformOTLP(trace); + expect(fetchMock).toHaveBeenLastCalledWith(`${DEFAULT_API_ROOT}transform`, body); + }); +}); + describe('fetchTrace', () => { const generatedTraces = traceGenerator.traces({ numberOfTraces: 5 }); diff --git a/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-in-error.json b/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-in-error.json new file mode 100644 index 0000000000..e83daa4d82 --- /dev/null +++ b/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-in-error.json @@ -0,0 +1,43 @@ +{ + "resourceSpans": [ + { + "resource": { + "attributes": [] + }, + "scopeSpans": [ + { + "scope": { + "name": "telemetrygen" + }, + "spans": [ + { + "traceId": "83a9efd15c1c98a977e0711cc93ee28b", + "spanId": "e127af99e3b3e074", + "parentSpanId": "909541b92cf05311", + "name": "okey-dokey-0", + "kind": 2, + "startTimeUnixNano": "1706678909209712000", + "endTimeUnixNano": "1706678909209835000", + "attributes": [ + { + "key": "net.peer.ip", + "value": { + "stringValue": "1.2.3.4" + } + }, + { + "key": "peer.service", + "value": { + "stringValue": "telemetrygen-client" + } + } + ], + "status": {} + } + ] + } + ], + "schemaUrl": "https://opentelemetry.io/schemas/1.4.0" + } + ] +} diff --git a/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-in.json b/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-in.json new file mode 100644 index 0000000000..1a62a0a1e8 --- /dev/null +++ b/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-in.json @@ -0,0 +1,74 @@ +{ + "resourceSpans": [ + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "telemetrygen" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "telemetrygen" + }, + "spans": [ + { + "traceId": "83a9efd15c1c98a977e0711cc93ee28b", + "spanId": "e127af99e3b3e074", + "parentSpanId": "909541b92cf05311", + "name": "okey-dokey-0", + "kind": 2, + "startTimeUnixNano": "1706678909209712000", + "endTimeUnixNano": "1706678909209835000", + "attributes": [ + { + "key": "net.peer.ip", + "value": { + "stringValue": "1.2.3.4" + } + }, + { + "key": "peer.service", + "value": { + "stringValue": "telemetrygen-client" + } + } + ], + "status": {} + }, + { + "traceId": "83a9efd15c1c98a977e0711cc93ee28b", + "spanId": "e127af99e3b3e074", + "parentSpanId": "909541b92cf05311", + "name": "okey-dokey-0", + "kind": 2, + "startTimeUnixNano": "1706678909209712000", + "endTimeUnixNano": "1706678909209835000", + "attributes": [ + { + "key": "net.peer.ip", + "value": { + "stringValue": "1.2.3.4" + } + }, + { + "key": "peer.service", + "value": { + "stringValue": "telemetrygen-client" + } + } + ], + "status": {} + } + ] + } + ], + "schemaUrl": "https://opentelemetry.io/schemas/1.4.0" + } + ] +} diff --git a/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-out.json b/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-out.json new file mode 100644 index 0000000000..313659814b --- /dev/null +++ b/packages/jaeger-ui/src/utils/fixtures/otlp2jaeger-out.json @@ -0,0 +1,98 @@ +{ + "data": [ + { + "traceID": "83a9efd15c1c98a977e0711cc93ee28b", + "spans": [ + { + "traceID": "83a9efd15c1c98a977e0711cc93ee28b", + "spanID": "e127af99e3b3e074", + "operationName": "okey-dokey-0", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "83a9efd15c1c98a977e0711cc93ee28b", + "spanID": "909541b92cf05311" + } + ], + "startTime": 1706678909209712, + "duration": 123, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "telemetrygen" + }, + { + "key": "net.peer.ip", + "type": "string", + "value": "1.2.3.4" + }, + { + "key": "peer.service", + "type": "string", + "value": "telemetrygen-client" + }, + { + "key": "span.kind", + "type": "string", + "value": "server" + } + ], + "logs": [], + "processID": "p1", + "warnings": null + }, + { + "traceID": "83a9efd15c1c98a977e0711cc93ee28b", + "spanID": "e127af99e3b3e074", + "operationName": "okey-dokey-0", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "83a9efd15c1c98a977e0711cc93ee28b", + "spanID": "909541b92cf05311" + } + ], + "startTime": 1706678909209712, + "duration": 123, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "telemetrygen" + }, + { + "key": "net.peer.ip", + "type": "string", + "value": "1.2.3.4" + }, + { + "key": "peer.service", + "type": "string", + "value": "telemetrygen-client" + }, + { + "key": "span.kind", + "type": "string", + "value": "server" + } + ], + "logs": [], + "processID": "p1", + "warnings": null + } + ], + "processes": { + "p1": { + "serviceName": "telemetrygen", + "tags": [] + } + }, + "warnings": null + } + ], + "total": 0, + "limit": 0, + "offset": 0, + "errors": null +} diff --git a/packages/jaeger-ui/src/utils/readJsonFile.test.js b/packages/jaeger-ui/src/utils/readJsonFile.test.js index bf69156bf0..865cd33241 100644 --- a/packages/jaeger-ui/src/utils/readJsonFile.test.js +++ b/packages/jaeger-ui/src/utils/readJsonFile.test.js @@ -12,7 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +import fs from 'fs'; +import lodash from 'lodash'; import readJsonFile from './readJsonFile'; +import JaegerAPI from '../api/jaeger'; + +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); + } + // This defines case where API call errors out even after detecting a `resourceSpan` in the request + return Promise.reject(); +}); describe('fileReader.readJsonFile', () => { it('rejects when given an invalid file', () => { @@ -39,6 +52,21 @@ describe('fileReader.readJsonFile', () => { return expect(p).resolves.toMatchObject(obj); }); + 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 file = new File([JSON.stringify(inObj)], 'foo.json'); + const p = readJsonFile({ file }); + return expect(p).resolves.toMatchObject(outObj); + }); + + it('rejects an OTLP trace', () => { + const inObj = JSON.parse(fs.readFileSync('src/utils/fixtures/otlp2jaeger-in-error.json', 'utf-8')); + const file = new File([JSON.stringify(inObj)], 'foo.json'); + const p = readJsonFile({ file }); + return expect(p).rejects.toMatchObject(expect.any(Error)); + }); + it('rejects on malform JSON', () => { const file = new File(['not-json'], 'foo.json'); const p = readJsonFile({ file }); diff --git a/packages/jaeger-ui/src/utils/readJsonFile.tsx b/packages/jaeger-ui/src/utils/readJsonFile.tsx index 21b93bc562..be404c56be 100644 --- a/packages/jaeger-ui/src/utils/readJsonFile.tsx +++ b/packages/jaeger-ui/src/utils/readJsonFile.tsx @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import JaegerAPI from '../api/jaeger'; + export default function readJsonFile(fileList: { file: File }): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -21,7 +23,18 @@ export default function readJsonFile(fileList: { file: File }): Promise return; } try { - resolve(JSON.parse(reader.result)); + 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); + } } catch (error: unknown) { reject(new Error(`Error parsing JSON: ${(error as Error).message}`)); }