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>
This commit is contained in:
Zoltán Bedi 2021-07-29 18:07:07 +02:00 committed by GitHub
parent d9d795ef2c
commit e3dde3510e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 245 additions and 35 deletions

View File

@ -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.

View File

@ -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<JaegerDatasource, JaegerQuery>;
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 <SearchForm datasource={datasource} query={query} onChange={onChange} />;
case 'upload':
return (
<div className={css({ padding: theme.spacing(2) })}>
<FileDropzone
options={{ multiple: false }}
onLoad={(result) => {
datasource.uploadedJson = result;
onRunQuery();
}}
/>
</div>
);
default:
return (
<InlineFieldRow>
<InlineField label="Trace ID" labelWidth={21} grow>
<Input
aria-label={selectors.components.DataSource.Jaeger.traceIDInput}
placeholder="Eg. 4050b8060d659e52"
value={query.query || ''}
onChange={(v) =>
onChange({
...query,
query: v.currentTarget.value,
})
}
/>
</InlineField>
</InlineFieldRow>
);
}
};
return (
<div className={css({ width: '50%' })}>
<InlineFieldRow>
<InlineField label="Query type">
<RadioButtonGroup<JaegerQueryType>
options={[
{ value: 'search', label: 'Search' },
{ value: undefined, label: 'TraceID' },
]}
value={query.queryType}
onChange={(v) =>
onChange({
...query,
queryType: v,
})
}
size="md"
/>
</InlineField>
</InlineFieldRow>
{query.queryType === 'search' ? (
<SearchForm datasource={datasource} query={query} onChange={onChange} />
) : (
<>
<div className={css({ width: query.queryType === 'upload' ? '100%' : '50%' })}>
<InlineFieldRow>
<InlineField label="Trace ID" labelWidth={21} grow>
<Input
aria-label={selectors.components.DataSource.Jaeger.traceIDInput}
placeholder="Eg. 4050b8060d659e52"
value={query.query || ''}
<InlineField label="Query type">
<RadioButtonGroup<JaegerQueryType>
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"
/>
</InlineField>
</InlineFieldRow>
)}
</div>
{renderEditorBody()}
</div>
</>
);
}

View File

@ -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);

View File

@ -21,6 +21,7 @@ import { convertTagsLogfmt } from './util';
import { ALL_OPERATIONS_KEY } from './components/SearchForm';
export class JaegerDatasource extends DataSourceApi<JaegerQuery> {
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<JaegerQuery> {
);
}
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);

View File

@ -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
}

View File

@ -63,4 +63,4 @@ export type JaegerQuery = {
limit?: number;
} & DataQuery;
export type JaegerQueryType = 'search';
export type JaegerQueryType = 'search' | 'upload';