mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 23:55:47 -06:00
Jaeger/Zipkin: URL-encode service names and trace ids for API calls (#26115)
* Encode services and id * Encode URL for Zipkin API calls * Add test coverage
This commit is contained in:
parent
c136f7da39
commit
73468e1481
@ -18,6 +18,55 @@ describe('JaegerQueryField', function() {
|
|||||||
expect(wrapper.find(ButtonCascader).props().options[0].label).toBe('No traces found');
|
expect(wrapper.find(ButtonCascader).props().options[0].label).toBe('No traces found');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses URL encoded service name in metadataRequest request', async function() {
|
||||||
|
const wrapper = mount(
|
||||||
|
<JaegerQueryField
|
||||||
|
history={[]}
|
||||||
|
datasource={makeDatasourceMock({
|
||||||
|
'service/test': {
|
||||||
|
op1: [
|
||||||
|
{
|
||||||
|
traceID: '12345',
|
||||||
|
spans: [
|
||||||
|
{
|
||||||
|
spanID: 's2',
|
||||||
|
operationName: 'nonRootOp',
|
||||||
|
references: [{ refType: 'CHILD_OF', traceID: '12345', spanID: 's1' }],
|
||||||
|
duration: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operationName: 'rootOp',
|
||||||
|
spanID: 's1',
|
||||||
|
references: [],
|
||||||
|
duration: 99,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
query={{ query: '1234' } as JaegerQuery}
|
||||||
|
onRunQuery={() => {}}
|
||||||
|
onChange={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simulating selection options. We need this as the function depends on the intermediate state of the component
|
||||||
|
await wrapper
|
||||||
|
.find(ButtonCascader)
|
||||||
|
.props()
|
||||||
|
.loadData([{ value: 'service/test', label: 'service/test' }]);
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
expect(wrapper.find(ButtonCascader).props().options[0].label).toEqual('service/test');
|
||||||
|
expect(wrapper.find(ButtonCascader).props().options[0].value).toEqual('service/test');
|
||||||
|
expect(wrapper.find(ButtonCascader).props().options[0].children[1]).toEqual({
|
||||||
|
isLeaf: false,
|
||||||
|
label: 'op1',
|
||||||
|
value: 'op1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('shows root span as 3rd level in cascader', async function() {
|
it('shows root span as 3rd level in cascader', async function() {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<JaegerQueryField
|
<JaegerQueryField
|
||||||
@ -73,9 +122,11 @@ function makeDatasourceMock(data: { [service: string]: { [operation: string]: an
|
|||||||
if (url.match(/\/services$/)) {
|
if (url.match(/\/services$/)) {
|
||||||
return Promise.resolve(Object.keys(data));
|
return Promise.resolve(Object.keys(data));
|
||||||
}
|
}
|
||||||
let match = url.match(/\/services\/(\w+)\/operations/);
|
let match = url.match(/\/services\/(.*)\/operations/);
|
||||||
if (match) {
|
if (match) {
|
||||||
return Promise.resolve(Object.keys(data[match[1]]));
|
const decodedService = decodeURIComponent(match[1]);
|
||||||
|
expect(decodedService).toBe(Object.keys(data)[0]);
|
||||||
|
return Promise.resolve(Object.keys(data[decodedService]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.match(/\/traces?/)) {
|
if (url.match(/\/traces?/)) {
|
||||||
|
@ -148,7 +148,7 @@ export class JaegerQueryField extends React.PureComponent<Props, State> {
|
|||||||
|
|
||||||
findOperations = async (service: string) => {
|
findOperations = async (service: string) => {
|
||||||
const { datasource } = this.props;
|
const { datasource } = this.props;
|
||||||
const url = `/api/services/${service}/operations`;
|
const url = `/api/services/${encodeURIComponent(service)}/operations`;
|
||||||
try {
|
try {
|
||||||
return await datasource.metadataRequest(url);
|
return await datasource.metadataRequest(url);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -16,6 +16,28 @@ describe('JaegerDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns trace when traceId with special characters is queried', async () => {
|
||||||
|
await withMockedBackendSrv(makeBackendSrvMock('a/b'), async () => {
|
||||||
|
const ds = new JaegerDatasource(defaultSettings);
|
||||||
|
const query = {
|
||||||
|
...defaultQuery,
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
query: 'a/b',
|
||||||
|
refId: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const response = await ds.query(query).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: 'a/b',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('returns empty response if trace id is not specified', async () => {
|
it('returns empty response if trace id is not specified', async () => {
|
||||||
const ds = new JaegerDatasource(defaultSettings);
|
const ds = new JaegerDatasource(defaultSettings);
|
||||||
const response = await ds
|
const response = await ds
|
||||||
@ -34,7 +56,9 @@ describe('JaegerDatasource', () => {
|
|||||||
function makeBackendSrvMock(traceId: string) {
|
function makeBackendSrvMock(traceId: string) {
|
||||||
return {
|
return {
|
||||||
datasourceRequest(options: BackendSrvRequest): Promise<any> {
|
datasourceRequest(options: BackendSrvRequest): Promise<any> {
|
||||||
expect(options.url.substr(options.url.length - 17, options.url.length)).toBe(`/api/traces/${traceId}`);
|
expect(options.url.substr(options.url.length - 17, options.url.length)).toBe(
|
||||||
|
`/api/traces/${encodeURIComponent(traceId)}`
|
||||||
|
);
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: {
|
data: {
|
||||||
data: [
|
data: [
|
||||||
|
@ -36,7 +36,7 @@ export class JaegerDatasource extends DataSourceApi<JaegerQuery> {
|
|||||||
const id = options.targets[0]?.query;
|
const id = options.targets[0]?.query;
|
||||||
if (id) {
|
if (id) {
|
||||||
// TODO: this api is internal, used in jaeger ui. Officially they have gRPC api that should be used.
|
// 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(
|
return this._request(`/api/traces/${encodeURIComponent(id)}`).pipe(
|
||||||
map(response => {
|
map(response => {
|
||||||
return {
|
return {
|
||||||
data: [
|
data: [
|
||||||
|
@ -11,6 +11,12 @@ describe('ZipkinDatasource', () => {
|
|||||||
const response = await ds.query({ targets: [{ query: '12345' }] } as DataQueryRequest<ZipkinQuery>).toPromise();
|
const response = await ds.query({ targets: [{ query: '12345' }] } as DataQueryRequest<ZipkinQuery>).toPromise();
|
||||||
expect(response.data[0].fields[0].values.get(0)).toEqual(jaegerTrace);
|
expect(response.data[0].fields[0].values.get(0)).toEqual(jaegerTrace);
|
||||||
});
|
});
|
||||||
|
it('runs query with traceId that includes special characters', async () => {
|
||||||
|
setupBackendSrv({ url: '/api/datasources/proxy/1/api/v2/trace/a%2Fb', response: zipkinResponse });
|
||||||
|
const ds = new ZipkinDatasource(defaultSettings);
|
||||||
|
const response = await ds.query({ targets: [{ query: 'a/b' }] } as DataQueryRequest<ZipkinQuery>).toPromise();
|
||||||
|
expect(response.data[0].fields[0].values.get(0)).toEqual(jaegerTrace);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('metadataRequest', () => {
|
describe('metadataRequest', () => {
|
||||||
|
@ -27,7 +27,9 @@ export class ZipkinDatasource extends DataSourceApi<ZipkinQuery> {
|
|||||||
query(options: DataQueryRequest<ZipkinQuery>): Observable<DataQueryResponse> {
|
query(options: DataQueryRequest<ZipkinQuery>): Observable<DataQueryResponse> {
|
||||||
const traceId = options.targets[0]?.query;
|
const traceId = options.targets[0]?.query;
|
||||||
if (traceId) {
|
if (traceId) {
|
||||||
return this.request<ZipkinSpan[]>(`${apiPrefix}/trace/${traceId}`).pipe(map(responseToDataQueryResponse));
|
return this.request<ZipkinSpan[]>(`${apiPrefix}/trace/${encodeURIComponent(traceId)}`).pipe(
|
||||||
|
map(responseToDataQueryResponse)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return of(emptyDataQueryResponse);
|
return of(emptyDataQueryResponse);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user