From e3dde3510e07a98baab04606f32b7f41602d938e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Bedi?= Date: Thu, 29 Jul 2021 18:07:07 +0200 Subject: [PATCH] Jaeger: upload json file (#37205) * Dropzone component * Add file list * Add progress, error and cancelation to filelistitem * Update Dropzone component to support progress Cancelation Retry * Update file name changes * Rename to FileDropzone * FileListItem tests A11y updates for icon buttons Use value formatter from grafana/data * Add tests for FileDropzone Review comments * export FileDropzoneDefaultChildren * Jaeger: Upload trace json * Change primary text when multiple false * styling * Review comments addressed * Add test for upload trace json * Add docs for Jaeger upload * Fix strict type check error * Update docs/sources/datasources/jaeger.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Minor changes Render all the options from one function. Add graph frames to the result. * Address review comments * Update docs/sources/datasources/jaeger.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * docs: move image Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> --- docs/sources/datasources/jaeger.md | 58 +++++++++- .../jaeger/components/QueryEditor.tsx | 88 +++++++++------ .../datasource/jaeger/datasource.test.ts | 18 ++- .../plugins/datasource/jaeger/datasource.ts | 9 ++ .../datasource/jaeger/mockJsonResponse.json | 105 ++++++++++++++++++ public/app/plugins/datasource/jaeger/types.ts | 2 +- 6 files changed, 245 insertions(+), 35 deletions(-) create mode 100644 public/app/plugins/datasource/jaeger/mockJsonResponse.json diff --git a/docs/sources/datasources/jaeger.md b/docs/sources/datasources/jaeger.md index 98920398b4a..6b5c96e67b8 100644 --- a/docs/sources/datasources/jaeger.md +++ b/docs/sources/datasources/jaeger.md @@ -32,7 +32,7 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t - **Data source -** Target data source. - **Tags -** The tags that will be used in the Loki query. Default is `'cluster', 'hostname', 'namespace', 'pod'`. -- **Span start time shift -** Shift in the start time for the Loki query based on the span start time. In order to extend to the past, you need to use a negative value. Time units can be used here, for example, 5s, 1m, 3h. The default is 0. +- **Span start time shift -** Shift in the start time for the Loki query based on the span start time. In order to extend to the past, you need to use a negative value. Use time interval units like 5s, 1m, 3h. The default is 0. - **Span end time shift -** Shift in the end time for the Loki query based on the span end time. Time units can be used here, for example, 5s, 1m, 3h. The default is 0. ![Trace to logs settings](/static/img/docs/explore/trace-to-logs-settings-8.png 'Screenshot of the trace to logs settings') @@ -56,6 +56,62 @@ To perform a search, set the query type selector to Search, then use the followi - Max Duration - Filter all traces with a duration lower than the set value. Possible values are `1.2s, 100ms, 500us`. - Limit - Limits the number of traces returned. +## Upload JSON trace file + +You can upload a JSON file that contains a single trace to visualize it. If the file has multiple traces then the first trace is used for visualization. + +{{< figure src="/static/img/docs/explore/jaeger-upload-json.png" class="docs-image--no-shadow" caption="Screenshot of the Jaeger data source in explore with upload selected" >}} + +Here is an example JSON: + +```json +{ + "data": [ + { + "traceID": "2ee9739529395e31", + "spans": [ + { + "traceID": "2ee9739529395e31", + "spanID": "2ee9739529395e31", + "flags": 1, + "operationName": "CAS", + "references": [], + "startTime": 1616095319593196, + "duration": 1004, + "tags": [ + { + "key": "sampler.type", + "type": "string", + "value": "const" + } + ], + "logs": [], + "processID": "p1", + "warnings": null + } + ], + "processes": { + "p1": { + "serviceName": "loki-all", + "tags": [ + { + "key": "jaeger.version", + "type": "string", + "value": "Go-2.25.0" + } + ] + } + }, + "warnings": null + } + ], + "total": 0, + "limit": 0, + "offset": 0, + "errors": null +} +``` + ## Linking Trace ID from logs You can link to Jaeger trace from logs in Loki by configuring a derived field with internal link. See the [Derived fields]({{< relref "loki.md#derived-fields" >}}) section in the [Loki data source]({{< relref "loki.md" >}}) documentation for details. diff --git a/public/app/plugins/datasource/jaeger/components/QueryEditor.tsx b/public/app/plugins/datasource/jaeger/components/QueryEditor.tsx index 04d9cf0d229..7f15aba20a8 100644 --- a/public/app/plugins/datasource/jaeger/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/jaeger/components/QueryEditor.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import { QueryEditorProps } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { InlineField, InlineFieldRow, Input, RadioButtonGroup } from '@grafana/ui'; +import { FileDropzone, InlineField, InlineFieldRow, Input, RadioButtonGroup, useTheme2 } from '@grafana/ui'; import React from 'react'; import { JaegerDatasource } from '../datasource'; import { JaegerQuery, JaegerQueryType } from '../types'; @@ -9,46 +9,70 @@ import { SearchForm } from './SearchForm'; type Props = QueryEditorProps; -export function QueryEditor({ datasource, query, onChange }: Props) { +export function QueryEditor({ datasource, query, onChange, onRunQuery }: Props) { + const theme = useTheme2(); + + const renderEditorBody = () => { + switch (query.queryType) { + case 'search': + return ; + case 'upload': + return ( +
+ { + datasource.uploadedJson = result; + onRunQuery(); + }} + /> +
+ ); + default: + return ( + + + + onChange({ + ...query, + query: v.currentTarget.value, + }) + } + /> + + + ); + } + }; + return ( -
- - - - options={[ - { value: 'search', label: 'Search' }, - { value: undefined, label: 'TraceID' }, - ]} - value={query.queryType} - onChange={(v) => - onChange({ - ...query, - queryType: v, - }) - } - size="md" - /> - - - {query.queryType === 'search' ? ( - - ) : ( + <> +
- - + + options={[ + { value: 'search', label: 'Search' }, + { value: undefined, label: 'TraceID' }, + { value: 'upload', label: 'JSON file' }, + ]} + value={query.queryType} onChange={(v) => onChange({ ...query, - query: v.currentTarget.value, + queryType: v, }) } + size="md" /> - )} -
+ {renderEditorBody()} +
+ ); } diff --git a/public/app/plugins/datasource/jaeger/datasource.test.ts b/public/app/plugins/datasource/jaeger/datasource.test.ts index 3335082d790..cb846e0e8b1 100644 --- a/public/app/plugins/datasource/jaeger/datasource.test.ts +++ b/public/app/plugins/datasource/jaeger/datasource.test.ts @@ -4,11 +4,12 @@ import { of, throwError } from 'rxjs'; import { createFetchResponse } from 'test/helpers/createFetchResponse'; import { ALL_OPERATIONS_KEY } from './components/SearchForm'; import { JaegerDatasource } from './datasource'; +import mockJson from './mockJsonResponse.json'; import { testResponse, testResponseDataFrameFields, - testResponseNodesFields, testResponseEdgesFields, + testResponseNodesFields, } from './testResponse'; import { JaegerQuery } from './types'; @@ -72,6 +73,21 @@ describe('JaegerDatasource', () => { expect(field.values.length).toBe(0); }); + it('should handle json file upload', async () => { + const ds = new JaegerDatasource(defaultSettings); + ds.uploadedJson = JSON.stringify(mockJson); + const response = await ds + .query({ + ...defaultQuery, + targets: [{ queryType: 'upload', refId: 'A' }], + }) + .toPromise(); + const field = response.data[0].fields[0]; + expect(field.name).toBe('traceID'); + expect(field.type).toBe(FieldType.string); + expect(field.values.length).toBe(2); + }); + it('should return search results when the query type is search', async () => { const mock = setupFetchMock({ data: [testResponse] }); const ds = new JaegerDatasource(defaultSettings, timeSrvStub); diff --git a/public/app/plugins/datasource/jaeger/datasource.ts b/public/app/plugins/datasource/jaeger/datasource.ts index 3f8d6bc35a9..8ffef846b7f 100644 --- a/public/app/plugins/datasource/jaeger/datasource.ts +++ b/public/app/plugins/datasource/jaeger/datasource.ts @@ -21,6 +21,7 @@ import { convertTagsLogfmt } from './util'; import { ALL_OPERATIONS_KEY } from './components/SearchForm'; export class JaegerDatasource extends DataSourceApi { + uploadedJson: string | ArrayBuffer | null = null; constructor(private instanceSettings: DataSourceInstanceSettings, private readonly timeSrv: TimeSrv = getTimeSrv()) { super(instanceSettings); } @@ -52,6 +53,14 @@ export class JaegerDatasource extends DataSourceApi { ); } + if (target.queryType === 'upload') { + if (!this.uploadedJson) { + return of({ data: [] }); + } + const traceData = JSON.parse(this.uploadedJson as string).data[0]; + return of({ data: [createTraceFrame(traceData), ...createGraphFrames(traceData)] }); + } + let jaegerQuery = pick(target, ['operation', 'service', 'tags', 'minDuration', 'maxDuration', 'limit']); // remove empty properties jaegerQuery = pickBy(jaegerQuery, identity); diff --git a/public/app/plugins/datasource/jaeger/mockJsonResponse.json b/public/app/plugins/datasource/jaeger/mockJsonResponse.json new file mode 100644 index 00000000000..66ce36c3fdc --- /dev/null +++ b/public/app/plugins/datasource/jaeger/mockJsonResponse.json @@ -0,0 +1,105 @@ +{ + "data": [ + { + "traceID": "2ee9739529395e31", + "spans": [ + { + "traceID": "2ee9739529395e31", + "spanID": "2ee9739529395e31", + "flags": 1, + "operationName": "CAS", + "references": [], + "startTime": 1616095319593196, + "duration": 1004, + "tags": [ + { + "key": "sampler.type", + "type": "string", + "value": "const" + }, + { + "key": "sampler.param", + "type": "bool", + "value": true + }, + { + "key": "span.kind", + "type": "string", + "value": "client" + }, + { + "key": "internal.span.format", + "type": "string", + "value": "proto" + } + ], + "logs": [], + "processID": "p1", + "warnings": null + }, + { + "traceID": "2ee9739529395e31", + "spanID": "620c647812563b5a", + "flags": 1, + "operationName": "CAS loop", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "2ee9739529395e31", + "spanID": "2ee9739529395e31" + } + ], + "startTime": 1616095319593344, + "duration": 738, + "tags": [ + { + "key": "span.kind", + "type": "string", + "value": "client" + }, + { + "key": "internal.span.format", + "type": "string", + "value": "proto" + } + ], + "logs": [], + "processID": "p1", + "warnings": null + } + ], + "processes": { + "p1": { + "serviceName": "loki-all", + "tags": [ + { + "key": "jaeger.version", + "type": "string", + "value": "Go-2.25.0" + }, + { + "key": "hostname", + "type": "string", + "value": "07bb841e14c3" + }, + { + "key": "ip", + "type": "string", + "value": "172.26.0.2" + }, + { + "key": "client-uuid", + "type": "string", + "value": "44344b1d17f4ce30" + } + ] + } + }, + "warnings": null + } + ], + "total": 0, + "limit": 0, + "offset": 0, + "errors": null +} diff --git a/public/app/plugins/datasource/jaeger/types.ts b/public/app/plugins/datasource/jaeger/types.ts index 4018a09d206..7d1b203532c 100644 --- a/public/app/plugins/datasource/jaeger/types.ts +++ b/public/app/plugins/datasource/jaeger/types.ts @@ -63,4 +63,4 @@ export type JaegerQuery = { limit?: number; } & DataQuery; -export type JaegerQueryType = 'search'; +export type JaegerQueryType = 'search' | 'upload';