Explore: Add trace UI to show traces from tracing datasources (#23047)

* Add integration with Jeager
Add Jaeger datasource and modify derived fields in loki to allow for opening a trace in Jager in separate split.
Modifies build so that this branch docker images are pushed to docker hub
Add a traceui dir with docker-compose and provision files for demoing.:wq

* Enable docker logger plugin to send logs to loki

* Add placeholder zipkin datasource

* Fixed rebase issues, added enhanceDataFrame to non-legacy code path

* Trace selector for jaeger query field

* Fix logs default mode for Loki

* Fix loading jaeger query field services on split

* Updated grafana image in traceui/compose file

* Fix prettier error

* Hide behind feature flag, clean up unused code.

* Fix tests

* Fix tests

* Cleanup code and review feedback

* Remove traceui directory

* Remove circle build changes

* Fix feature toggles object

* Fix merge issues

* Add trace ui in Explore

* WIP

* WIP

* WIP

* Make jaeger datasource return trace data instead of link

* Allow js in jest tests

* Return data from Jaeger datasource

* Take yarn.lock from master

* Fix missing component

* Update yarn lock

* Fix some ts and lint errors

* Fix merge

* Fix type errors

* Make tests pass again

* Add tests

* Fix es5 compatibility

Co-authored-by: David Kaltschmidt <david.kaltschmidt@gmail.com>
This commit is contained in:
Andrej Ocenas
2020-04-02 13:34:16 +02:00
committed by GitHub
parent a40c258544
commit a4d4dd325f
149 changed files with 16275 additions and 193 deletions

View File

@@ -0,0 +1,88 @@
import { JaegerDatasource, JaegerQuery } from './datasource';
import { DataQueryRequest, DataSourceInstanceSettings, FieldType, PluginType } from '@grafana/data';
import { BackendSrv, BackendSrvRequest, getBackendSrv, setBackendSrv } from '@grafana/runtime';
describe('JaegerDatasource', () => {
it('returns trace when queried', async () => {
await withMockedBackendSrv(makeBackendSrvMock('12345'), async () => {
const ds = new JaegerDatasource(defaultSettings);
const response = await ds.query(defaultQuery).toPromise();
const field = response.data[0].fields[0];
expect(field.name).toBe('trace');
expect(field.type).toBe(FieldType.trace);
expect(field.values.get(0)).toEqual({
traceId: '12345',
});
});
});
it('returns empty response if trace id is not specified', async () => {
const ds = new JaegerDatasource(defaultSettings);
const response = await ds
.query({
...defaultQuery,
targets: [],
})
.toPromise();
const field = response.data[0].fields[0];
expect(field.name).toBe('trace');
expect(field.type).toBe(FieldType.trace);
expect(field.values.length).toBe(0);
});
});
function makeBackendSrvMock(traceId: string) {
return {
datasourceRequest(options: BackendSrvRequest): Promise<any> {
expect(options.url.substr(options.url.length - 17, options.url.length)).toBe(`/api/traces/${traceId}`);
return Promise.resolve({
data: {
data: [
{
traceId,
},
],
},
});
},
} as any;
}
async function withMockedBackendSrv(srv: BackendSrv, fn: () => Promise<void>) {
const oldSrv = getBackendSrv();
setBackendSrv(srv);
await fn();
setBackendSrv(oldSrv);
}
const defaultSettings: DataSourceInstanceSettings = {
id: 0,
type: 'tracing',
name: 'jaeger',
meta: {
id: 'jaeger',
name: 'jaeger',
type: PluginType.datasource,
info: {} as any,
module: '',
baseUrl: '',
},
jsonData: {},
};
const defaultQuery: DataQueryRequest<JaegerQuery> = {
requestId: '1',
dashboardId: 0,
interval: '0',
panelId: 0,
scopedVars: {},
timezone: '',
app: 'explore',
startTime: 0,
targets: [
{
query: '12345',
refId: '1',
},
],
};

View File

@@ -7,14 +7,15 @@ import {
DataQueryRequest,
DataQueryResponse,
DataQuery,
FieldType,
} from '@grafana/data';
import { getBackendSrv } from '@grafana/runtime';
import { Observable, from, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { DatasourceRequestOptions } from 'app/core/services/backend_srv';
import { serializeParams } from '../../../core/utils/fetch';
import { Observable, from, of } from 'rxjs';
import { serializeParams } from 'app/core/utils/fetch';
export type JaegerQuery = {
query: string;
@@ -25,7 +26,64 @@ export class JaegerDatasource extends DataSourceApi<JaegerQuery> {
super(instanceSettings);
}
_request(apiUrl: string, data?: any, options?: DatasourceRequestOptions): Observable<Record<string, any>> {
async metadataRequest(url: string, params?: Record<string, any>) {
const res = await this._request(url, params, { silent: true }).toPromise();
return res.data.data;
}
query(options: DataQueryRequest<JaegerQuery>): Observable<DataQueryResponse> {
// At this moment we expect only one target. In case we somehow change the UI to be able to show multiple
// traces at one we need to change this.
const id = options.targets[0]?.query;
if (id) {
// TODO: this api is internal, used in jaeger ui. Officially they have gRPC api that should be used.
return this._request(`/api/traces/${id}`).pipe(
map(response => {
return {
data: [
new MutableDataFrame({
fields: [
{
name: 'trace',
type: FieldType.trace,
values: response?.data?.data || [],
},
],
}),
],
};
})
);
} else {
return of({
data: [
new MutableDataFrame({
fields: [
{
name: 'trace',
type: FieldType.trace,
values: [],
},
],
}),
],
});
}
}
async testDatasource(): Promise<any> {
return true;
}
getTimeRange(): { start: number; end: number } {
const range = getTimeSrv().timeRange();
return {
start: getTime(range.from, false),
end: getTime(range.to, true),
};
}
private _request(apiUrl: string, data?: any, options?: DatasourceRequestOptions): Observable<Record<string, any>> {
// Hack for proxying metadata requests
const baseUrl = `/api/datasources/proxy/${this.instanceSettings.id}`;
const params = data ? serializeParams(data) : '';
@@ -37,49 +95,11 @@ export class JaegerDatasource extends DataSourceApi<JaegerQuery> {
return from(getBackendSrv().datasourceRequest(req));
}
async metadataRequest(url: string, params?: Record<string, any>) {
const res = await this._request(url, params, { silent: true }).toPromise();
return res.data.data;
}
query(options: DataQueryRequest<JaegerQuery>): Observable<DataQueryResponse> {
//http://localhost:16686/search?end=1573338717880000&limit=20&lookback=6h&maxDuration&minDuration&service=app&start=1573317117880000
const url =
options.targets.length && options.targets[0].query
? `${this.instanceSettings.url}/trace/${options.targets[0].query}?uiEmbed=v0`
: '';
return of({
data: [
new MutableDataFrame({
fields: [
{
name: 'url',
values: [url],
},
],
}),
],
});
}
async testDatasource(): Promise<any> {
return true;
}
getTime(date: string | DateTime, roundUp: boolean) {
if (typeof date === 'string') {
date = dateMath.parse(date, roundUp);
}
return date.valueOf() * 1000;
}
getTimeRange(): { start: number; end: number } {
const range = getTimeSrv().timeRange();
return {
start: this.getTime(range.from, false),
end: this.getTime(range.to, true),
};
}
}
function getTime(date: string | DateTime, roundUp: boolean) {
if (typeof date === 'string') {
date = dateMath.parse(date, roundUp);
}
return date.valueOf() * 1000;
}