diff --git a/pkg/tsdb/tempo/tempo.go b/pkg/tsdb/tempo/tempo.go
index 6dee8319a97..7f60389dba9 100644
--- a/pkg/tsdb/tempo/tempo.go
+++ b/pkg/tsdb/tempo/tempo.go
@@ -73,7 +73,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
return nil, err
}
- request, err := s.createRequest(ctx, dsInfo, model.TraceID)
+ request, err := s.createRequest(ctx, dsInfo, model.TraceID, req.Queries[0].TimeRange.From.Unix(), req.Queries[0].TimeRange.To.Unix())
if err != nil {
return result, err
}
@@ -117,8 +117,15 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
return result, nil
}
-func (s *Service) createRequest(ctx context.Context, dsInfo *datasourceInfo, traceID string) (*http.Request, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", dsInfo.URL+"/api/traces/"+traceID, nil)
+func (s *Service) createRequest(ctx context.Context, dsInfo *datasourceInfo, traceID string, start int64, end int64) (*http.Request, error) {
+ var tempoQuery string
+ if start == 0 || end == 0 {
+ tempoQuery = fmt.Sprintf("%s/api/traces/%s", dsInfo.URL, traceID)
+ } else {
+ tempoQuery = fmt.Sprintf("%s/api/traces/%s?start=%d&end=%d", dsInfo.URL, traceID, start, end)
+ }
+
+ req, err := http.NewRequestWithContext(ctx, "GET", tempoQuery, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/tsdb/tempo/tempo_test.go b/pkg/tsdb/tempo/tempo_test.go
index 741a7bd3e40..a037d8b4777 100644
--- a/pkg/tsdb/tempo/tempo_test.go
+++ b/pkg/tsdb/tempo/tempo_test.go
@@ -10,10 +10,18 @@ import (
)
func TestTempo(t *testing.T) {
- t.Run("createRequest - success", func(t *testing.T) {
+ t.Run("createRequest without time range - success", func(t *testing.T) {
service := &Service{tlog: log.New("tempo-test")}
- req, err := service.createRequest(context.Background(), &datasourceInfo{}, "traceID")
+ req, err := service.createRequest(context.Background(), &datasourceInfo{}, "traceID", 0, 0)
require.NoError(t, err)
assert.Equal(t, 1, len(req.Header))
})
+
+ t.Run("createRequest with time range - success", func(t *testing.T) {
+ service := &Service{tlog: log.New("tempo-test")}
+ req, err := service.createRequest(context.Background(), &datasourceInfo{}, "traceID", 1, 2)
+ require.NoError(t, err)
+ assert.Equal(t, 1, len(req.Header))
+ assert.Equal(t, "/api/traces/traceID?start=1&end=2", req.URL.String())
+ })
}
diff --git a/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx
index 10b85be9e49..32f633a5414 100644
--- a/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx
+++ b/public/app/plugins/datasource/tempo/configuration/ConfigEditor.tsx
@@ -9,6 +9,7 @@ import { TraceToLogsSettings } from 'app/core/components/TraceToLogs/TraceToLogs
import { TraceToMetricsSettings } from 'app/core/components/TraceToMetrics/TraceToMetricsSettings';
import { LokiSearchSettings } from './LokiSearchSettings';
+import { QuerySettings } from './QuerySettings';
import { SearchSettings } from './SearchSettings';
import { ServiceGraphSettings } from './ServiceGraphSettings';
@@ -50,6 +51,10 @@ export const ConfigEditor = ({ options, onOptionsChange }: Props) => {
+
+
+
+
diff --git a/public/app/plugins/datasource/tempo/configuration/QuerySettings.tsx b/public/app/plugins/datasource/tempo/configuration/QuerySettings.tsx
new file mode 100644
index 00000000000..a8d82af8479
--- /dev/null
+++ b/public/app/plugins/datasource/tempo/configuration/QuerySettings.tsx
@@ -0,0 +1,72 @@
+import { css } from '@emotion/css';
+import React from 'react';
+
+import { DataSourcePluginOptionsEditorProps, updateDatasourcePluginJsonDataOption } from '@grafana/data';
+import { InlineField, InlineFieldRow, Input, useStyles } from '@grafana/ui';
+
+import { TempoJsonData } from '../types';
+
+interface Props extends DataSourcePluginOptionsEditorProps {}
+
+export function QuerySettings({ options, onOptionsChange }: Props) {
+ const styles = useStyles(getStyles);
+
+ return (
+
+
TraceID Query
+
+
+
+ updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'traceQuery', {
+ ...options.jsonData.traceQuery,
+ spanStartTimeShift: v.currentTarget.value,
+ })
+ }
+ value={options.jsonData.traceQuery?.spanStartTimeShift || ''}
+ />
+
+
+
+
+
+ updateDatasourcePluginJsonDataOption({ onOptionsChange, options }, 'traceQuery', {
+ ...options.jsonData.traceQuery,
+ spanEndTimeShift: v.currentTarget.value,
+ })
+ }
+ value={options.jsonData.traceQuery?.spanEndTimeShift || ''}
+ />
+
+
+
+ );
+}
+
+const getStyles = () => ({
+ container: css`
+ label: container;
+ width: 100%;
+ `,
+ row: css`
+ label: row;
+ align-items: baseline;
+ `,
+});
diff --git a/public/app/plugins/datasource/tempo/datasource.test.ts b/public/app/plugins/datasource/tempo/datasource.test.ts
index 99c46aeb309..0a3c84a2ed4 100644
--- a/public/app/plugins/datasource/tempo/datasource.test.ts
+++ b/public/app/plugins/datasource/tempo/datasource.test.ts
@@ -6,6 +6,7 @@ import {
DataFrame,
dataFrameToJSON,
DataSourceInstanceSettings,
+ dateTime,
FieldType,
getDefaultTimeRange,
LoadingState,
@@ -365,6 +366,35 @@ describe('Tempo data source', () => {
expect(response).toBe('456');
});
});
+
+ it('should include time shift when querying for traceID', () => {
+ const ds = new TempoDatasource({
+ ...defaultSettings,
+ jsonData: { traceQuery: { spanStartTimeShift: '2m', spanEndTimeShift: '4m' } },
+ });
+
+ const request = ds.traceIdQueryRequest(
+ {
+ requestId: 'test',
+ interval: '',
+ intervalMs: 5,
+ scopedVars: {},
+ targets: [],
+ timezone: '',
+ app: '',
+ startTime: 0,
+ range: {
+ from: dateTime(new Date(2022, 8, 13, 16, 0, 0, 0)),
+ to: dateTime(new Date(2022, 8, 13, 16, 15, 0, 0)),
+ raw: { from: '15m', to: 'now' },
+ },
+ },
+ [{ refId: 'refid1', queryType: 'traceId', query: '' } as TempoQuery]
+ );
+
+ expect(request.range.from.unix()).toBe(dateTime(new Date(2022, 8, 13, 15, 58, 0, 0)).unix());
+ expect(request.range.to.unix()).toBe(dateTime(new Date(2022, 8, 13, 16, 19, 0, 0)).unix());
+ });
});
describe('Tempo apm table', () => {
diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts
index 6e6dd74c25a..2dc6bb964cd 100644
--- a/public/app/plugins/datasource/tempo/datasource.ts
+++ b/public/app/plugins/datasource/tempo/datasource.ts
@@ -11,6 +11,7 @@ import {
FieldType,
isValidGoDuration,
LoadingState,
+ rangeUtil,
ScopedVars,
} from '@grafana/data';
import {
@@ -67,6 +68,10 @@ export class TempoDatasource extends DataSourceWithBackend,
- targets: TempoQuery[]
- ): Observable {
+ handleTraceIdQuery(options: DataQueryRequest, targets: TempoQuery[]): Observable {
const validTargets = targets.filter((t) => t.query).map((t) => ({ ...t, query: t.query.trim() }));
if (!validTargets.length) {
return EMPTY;
}
- const traceRequest: DataQueryRequest = { ...options, targets: validTargets };
+ const traceRequest = this.traceIdQueryRequest(options, validTargets);
+
return super.query(traceRequest).pipe(
map((response) => {
if (response.error) {
@@ -320,6 +324,21 @@ export class TempoDatasource extends DataSourceWithBackend, targets: TempoQuery[]): DataQueryRequest {
+ return {
+ ...options,
+ range: options.range && {
+ ...options.range,
+ from: options.range.from.subtract(
+ rangeUtil.intervalToMs(this.traceQuery?.spanStartTimeShift || '30m'),
+ 'milliseconds'
+ ),
+ to: options.range.to.add(rangeUtil.intervalToMs(this.traceQuery?.spanEndTimeShift || '30m'), 'milliseconds'),
+ },
+ targets,
+ };
+ }
+
async metadataRequest(url: string, params = {}) {
return await lastValueFrom(this._request(url, params, { method: 'GET', hideFromInspector: true }));
}
diff --git a/public/app/plugins/datasource/tempo/types.ts b/public/app/plugins/datasource/tempo/types.ts
index 6e78f3df544..4d4c34a472f 100644
--- a/public/app/plugins/datasource/tempo/types.ts
+++ b/public/app/plugins/datasource/tempo/types.ts
@@ -29,6 +29,10 @@ export interface TempoJsonData extends DataSourceJsonData {
spanBar?: {
tag: string;
};
+ traceQuery?: {
+ spanStartTimeShift?: string;
+ spanEndTimeShift?: string;
+ };
}
// search = Loki search, nativeSearch = Tempo search for backwards compatibility