mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
d9d795ef2c
commit
e3dde3510e
@ -32,7 +32,7 @@ This is a configuration for the [trace to logs feature]({{< relref "../explore/t
|
|||||||
|
|
||||||
- **Data source -** Target data source.
|
- **Data source -** Target data source.
|
||||||
- **Tags -** The tags that will be used in the Loki query. Default is `'cluster', 'hostname', 'namespace', 'pod'`.
|
- **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.
|
- **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.
|
||||||
|
|
||||||

|

|
||||||
@ -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`.
|
- 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.
|
- 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
|
## 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.
|
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.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { QueryEditorProps } from '@grafana/data';
|
import { QueryEditorProps } from '@grafana/data';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
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 React from 'react';
|
||||||
import { JaegerDatasource } from '../datasource';
|
import { JaegerDatasource } from '../datasource';
|
||||||
import { JaegerQuery, JaegerQueryType } from '../types';
|
import { JaegerQuery, JaegerQueryType } from '../types';
|
||||||
@ -9,46 +9,70 @@ import { SearchForm } from './SearchForm';
|
|||||||
|
|
||||||
type Props = QueryEditorProps<JaegerDatasource, JaegerQuery>;
|
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 (
|
return (
|
||||||
<div className={css({ width: '50%' })}>
|
<>
|
||||||
<InlineFieldRow>
|
<div className={css({ width: query.queryType === 'upload' ? '100%' : '50%' })}>
|
||||||
<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} />
|
|
||||||
) : (
|
|
||||||
<InlineFieldRow>
|
<InlineFieldRow>
|
||||||
<InlineField label="Trace ID" labelWidth={21} grow>
|
<InlineField label="Query type">
|
||||||
<Input
|
<RadioButtonGroup<JaegerQueryType>
|
||||||
aria-label={selectors.components.DataSource.Jaeger.traceIDInput}
|
options={[
|
||||||
placeholder="Eg. 4050b8060d659e52"
|
{ value: 'search', label: 'Search' },
|
||||||
value={query.query || ''}
|
{ value: undefined, label: 'TraceID' },
|
||||||
|
{ value: 'upload', label: 'JSON file' },
|
||||||
|
]}
|
||||||
|
value={query.queryType}
|
||||||
onChange={(v) =>
|
onChange={(v) =>
|
||||||
onChange({
|
onChange({
|
||||||
...query,
|
...query,
|
||||||
query: v.currentTarget.value,
|
queryType: v,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
size="md"
|
||||||
/>
|
/>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
</InlineFieldRow>
|
</InlineFieldRow>
|
||||||
)}
|
{renderEditorBody()}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,12 @@ import { of, throwError } from 'rxjs';
|
|||||||
import { createFetchResponse } from 'test/helpers/createFetchResponse';
|
import { createFetchResponse } from 'test/helpers/createFetchResponse';
|
||||||
import { ALL_OPERATIONS_KEY } from './components/SearchForm';
|
import { ALL_OPERATIONS_KEY } from './components/SearchForm';
|
||||||
import { JaegerDatasource } from './datasource';
|
import { JaegerDatasource } from './datasource';
|
||||||
|
import mockJson from './mockJsonResponse.json';
|
||||||
import {
|
import {
|
||||||
testResponse,
|
testResponse,
|
||||||
testResponseDataFrameFields,
|
testResponseDataFrameFields,
|
||||||
testResponseNodesFields,
|
|
||||||
testResponseEdgesFields,
|
testResponseEdgesFields,
|
||||||
|
testResponseNodesFields,
|
||||||
} from './testResponse';
|
} from './testResponse';
|
||||||
import { JaegerQuery } from './types';
|
import { JaegerQuery } from './types';
|
||||||
|
|
||||||
@ -72,6 +73,21 @@ describe('JaegerDatasource', () => {
|
|||||||
expect(field.values.length).toBe(0);
|
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 () => {
|
it('should return search results when the query type is search', async () => {
|
||||||
const mock = setupFetchMock({ data: [testResponse] });
|
const mock = setupFetchMock({ data: [testResponse] });
|
||||||
const ds = new JaegerDatasource(defaultSettings, timeSrvStub);
|
const ds = new JaegerDatasource(defaultSettings, timeSrvStub);
|
||||||
|
@ -21,6 +21,7 @@ import { convertTagsLogfmt } from './util';
|
|||||||
import { ALL_OPERATIONS_KEY } from './components/SearchForm';
|
import { ALL_OPERATIONS_KEY } from './components/SearchForm';
|
||||||
|
|
||||||
export class JaegerDatasource extends DataSourceApi<JaegerQuery> {
|
export class JaegerDatasource extends DataSourceApi<JaegerQuery> {
|
||||||
|
uploadedJson: string | ArrayBuffer | null = null;
|
||||||
constructor(private instanceSettings: DataSourceInstanceSettings, private readonly timeSrv: TimeSrv = getTimeSrv()) {
|
constructor(private instanceSettings: DataSourceInstanceSettings, private readonly timeSrv: TimeSrv = getTimeSrv()) {
|
||||||
super(instanceSettings);
|
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']);
|
let jaegerQuery = pick(target, ['operation', 'service', 'tags', 'minDuration', 'maxDuration', 'limit']);
|
||||||
// remove empty properties
|
// remove empty properties
|
||||||
jaegerQuery = pickBy(jaegerQuery, identity);
|
jaegerQuery = pickBy(jaegerQuery, identity);
|
||||||
|
105
public/app/plugins/datasource/jaeger/mockJsonResponse.json
Normal file
105
public/app/plugins/datasource/jaeger/mockJsonResponse.json
Normal 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
|
||||||
|
}
|
@ -63,4 +63,4 @@ export type JaegerQuery = {
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
} & DataQuery;
|
} & DataQuery;
|
||||||
|
|
||||||
export type JaegerQueryType = 'search';
|
export type JaegerQueryType = 'search' | 'upload';
|
||||||
|
Loading…
Reference in New Issue
Block a user