Zipkin: add ability to upload trace json (#37483)

* Zipkin: upload json

* Change back mock json
This commit is contained in:
Zoltán Bedi
2021-08-05 10:55:15 +02:00
committed by GitHub
parent 0563bc68fc
commit 44c7e65fac
7 changed files with 186 additions and 44 deletions

View File

@@ -1,9 +1,10 @@
import React from 'react';
import { QueryField, useLoadOptions, useServices } from './QueryField';
import { ZipkinDatasource, ZipkinQuery } from './datasource';
import { shallow } from 'enzyme';
import { ButtonCascader, CascaderOption } from '@grafana/ui';
import { renderHook, act } from '@testing-library/react-hooks';
import { act, renderHook } from '@testing-library/react-hooks';
import { shallow } from 'enzyme';
import React from 'react';
import { ZipkinDatasource } from './datasource';
import { QueryField, useLoadOptions, useServices } from './QueryField';
import { ZipkinQuery } from './types';
describe('QueryField', () => {
it('renders properly', () => {

View File

@@ -1,21 +1,31 @@
import { css } from '@emotion/css';
import { ExploreQueryFieldProps } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { ButtonCascader, CascaderOption } from '@grafana/ui';
import {
ButtonCascader,
CascaderOption,
FileDropzone,
InlineField,
InlineFieldRow,
RadioButtonGroup,
useTheme2,
} from '@grafana/ui';
import { notifyApp } from 'app/core/actions';
import { createErrorNotification } from 'app/core/copy/appNotification';
import { dispatch } from 'app/store/store';
import { fromPairs } from 'lodash';
import React, { useCallback, useMemo, useState } from 'react';
import { useAsyncFn, useMount, useMountedState } from 'react-use';
import { AsyncState } from 'react-use/lib/useAsyncFn';
import { apiPrefix } from './constants';
import { ZipkinDatasource, ZipkinQuery } from './datasource';
import { ZipkinSpan } from './types';
import { dispatch } from 'app/store/store';
import { notifyApp } from 'app/core/actions';
import { createErrorNotification } from 'app/core/copy/appNotification';
import { ZipkinDatasource } from './datasource';
import { ZipkinQuery, ZipkinQueryType, ZipkinSpan } from './types';
type Props = ExploreQueryFieldProps<ZipkinDatasource, ZipkinQuery>;
export const QueryField = ({ query, onChange, onRunQuery, datasource }: Props) => {
const serviceOptions = useServices(datasource);
const theme = useTheme2();
const { onLoadOptions, allOptions } = useLoadOptions(datasource);
const onSelectTrace = useCallback(
@@ -33,29 +43,59 @@ export const QueryField = ({ query, onChange, onRunQuery, datasource }: Props) =
return (
<>
<div className="gf-form-inline gf-form-inline--nowrap">
<div className="gf-form flex-shrink-0">
<ButtonCascader options={cascaderOptions} onChange={onSelectTrace} loadData={onLoadOptions}>
Traces
</ButtonCascader>
<InlineFieldRow>
<InlineField label="Query type">
<RadioButtonGroup<ZipkinQueryType>
options={[
{ value: 'traceID', label: 'TraceID' },
{ value: 'upload', label: 'JSON file' },
]}
value={query.queryType || 'traceID'}
onChange={(v) =>
onChange({
...query,
queryType: v,
})
}
size="md"
/>
</InlineField>
</InlineFieldRow>
{query.queryType === 'upload' ? (
<div className={css({ padding: theme.spacing(2) })}>
<FileDropzone
options={{ multiple: false }}
onLoad={(result) => {
datasource.uploadedJson = result;
onRunQuery();
}}
/>
</div>
<div className="gf-form gf-form--grow flex-shrink-1">
<div className="slate-query-field__wrapper">
<div className="slate-query-field" aria-label={selectors.components.QueryField.container}>
<input
style={{ width: '100%' }}
value={query.query || ''}
onChange={(e) =>
onChange({
...query,
query: e.currentTarget.value,
})
}
/>
) : (
<div className="gf-form-inline gf-form-inline--nowrap">
<div className="gf-form flex-shrink-0">
<ButtonCascader options={cascaderOptions} onChange={onSelectTrace} loadData={onLoadOptions}>
Traces
</ButtonCascader>
</div>
<div className="gf-form gf-form--grow flex-shrink-1">
<div className="slate-query-field__wrapper">
<div className="slate-query-field" aria-label={selectors.components.QueryField.container}>
<input
style={{ width: '100%' }}
value={query.query || ''}
onChange={(e) =>
onChange({
...query,
query: e.currentTarget.value,
})
}
/>
</div>
</div>
</div>
</div>
</div>
)}
</>
);
};

View File

@@ -1,8 +1,9 @@
import { DataSourceInstanceSettings } from '@grafana/data';
import { DataSourceInstanceSettings, FieldType } from '@grafana/data';
import { backendSrv } from 'app/core/services/backend_srv';
import { of } from 'rxjs';
import { createFetchResponse } from 'test/helpers/createFetchResponse';
import { ZipkinDatasource } from './datasource';
import mockJson from './mockJsonResponse.json';
import { traceFrameFields, zipkinResponse } from './utils/testData';
jest.mock('@grafana/runtime', () => ({
@@ -26,6 +27,20 @@ describe('ZipkinDatasource', () => {
expect(val[0].data[0].fields).toMatchObject(traceFrameFields);
});
});
it('should handle json file upload', async () => {
const ds = new ZipkinDatasource(defaultSettings);
ds.uploadedJson = JSON.stringify(mockJson);
const response = await ds
.query({
targets: [{ queryType: 'upload', refId: 'A' }],
} as any)
.toPromise();
const field = response.data[0].fields[0];
expect(field.name).toBe('traceID');
expect(field.type).toBe(FieldType.string);
expect(field.values.length).toBe(3);
});
});
describe('metadataRequest', () => {

View File

@@ -1,5 +1,4 @@
import {
DataQuery,
DataQueryRequest,
DataQueryResponse,
DataSourceApi,
@@ -12,28 +11,32 @@ import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { serializeParams } from '../../../core/utils/fetch';
import { apiPrefix } from './constants';
import { ZipkinSpan } from './types';
import { transformResponse } from './utils/transforms';
import { ZipkinQuery, ZipkinSpan } from './types';
import { createGraphFrames } from './utils/graphTransform';
export interface ZipkinQuery extends DataQuery {
query: string;
}
import { transformResponse } from './utils/transforms';
export class ZipkinDatasource extends DataSourceApi<ZipkinQuery> {
uploadedJson: string | ArrayBuffer | null = null;
constructor(private instanceSettings: DataSourceInstanceSettings) {
super(instanceSettings);
}
query(options: DataQueryRequest<ZipkinQuery>): Observable<DataQueryResponse> {
const traceId = options.targets[0]?.query;
if (traceId) {
return this.request<ZipkinSpan[]>(`${apiPrefix}/trace/${encodeURIComponent(traceId)}`).pipe(
const target = options.targets[0];
if (target.queryType === 'upload') {
if (!this.uploadedJson) {
return of({ data: [] });
}
const traceData = JSON.parse(this.uploadedJson as string);
return of(responseToDataQueryResponse({ data: traceData }));
}
if (target.query) {
return this.request<ZipkinSpan[]>(`${apiPrefix}/trace/${encodeURIComponent(target.query)}`).pipe(
map(responseToDataQueryResponse)
);
} else {
return of(emptyDataQueryResponse);
}
return of(emptyDataQueryResponse);
}
async metadataRequest(url: string, params?: Record<string, any>): Promise<any> {

View File

@@ -0,0 +1,45 @@
[
{
"traceId": "efe9cb8857f68c8f",
"parentId": "efe9cb8857f68c8f",
"id": "8608dc6ce5cafe8e",
"kind": "SERVER",
"name": "get /api",
"timestamp": 1627975249601797,
"duration": 23457,
"localEndpoint": { "serviceName": "backend", "ipv4": "127.0.0.1", "port": 9000 },
"tags": {
"http.method": "GET",
"http.path": "/api",
"jaxrs.resource.class": "Resource",
"jaxrs.resource.method": "printDate"
},
"shared": true
},
{
"traceId": "efe9cb8857f68c8f",
"parentId": "efe9cb8857f68c8f",
"id": "8608dc6ce5cafe8e",
"kind": "CLIENT",
"name": "get",
"timestamp": 1627975249491667,
"duration": 148265,
"localEndpoint": { "serviceName": "frontend", "ipv4": "127.0.0.1", "port": 8081 },
"tags": { "http.method": "GET", "http.path": "/api" }
},
{
"traceId": "efe9cb8857f68c8f",
"id": "efe9cb8857f68c8f",
"kind": "SERVER",
"name": "get",
"timestamp": 1627975249258448,
"duration": 413469,
"localEndpoint": { "serviceName": "frontend", "ipv4": "127.0.0.1", "port": 8081 },
"tags": {
"http.method": "GET",
"http.path": "/",
"jaxrs.resource.class": "Resource",
"jaxrs.resource.method": "callBackend"
}
}
]

View File

@@ -1,3 +1,5 @@
import { DataQuery } from '@grafana/data';
export type ZipkinSpan = {
traceId: string;
parentId?: string;
@@ -23,3 +25,9 @@ export type ZipkinAnnotation = {
timestamp: number;
value: string;
};
export type ZipkinQueryType = 'traceID' | 'upload';
export interface ZipkinQuery extends DataQuery {
query: string;
queryType?: ZipkinQueryType;
}