Jaeger: Migrate "search" query type to backend (#103399)

* jaeger backend migration

* move processing to JaegerClient.Search

* fix upload query error source type

* suggestions

* lint

* fix link to traceid query

* fix tests

* fix tests

* use consistent types

* add tests for TransformSearchResponse fn

* test search function

* fix filtering using tags

* suggestion

* remove unnecessary arguments

* use logfmt parser for tags

* test

* test

* use logfmt for query tags

* update

* go fmt

* run backend for all queryTypes

* run make update-workspace

* assign owner to logfmt pkg in go.mod

* apply suggestions

* update tests

* trigger workflows?
pull/104996/head^2
Gareth Dawson 2 months ago committed by GitHub
parent a4efb73774
commit 977e923555
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      go.mod
  2. 109
      pkg/tsdb/jaeger/client.go
  3. 123
      pkg/tsdb/jaeger/client_test.go
  4. 2
      pkg/tsdb/jaeger/jaeger.go
  5. 9
      pkg/tsdb/jaeger/jaeger_test.go
  6. 232
      pkg/tsdb/jaeger/querydata.go
  7. 102
      pkg/tsdb/jaeger/querydata_test.go
  8. 100
      pkg/tsdb/jaeger/testdata/search_empty_response.golden.jsonc
  9. 123
      pkg/tsdb/jaeger/testdata/search_multiple_response.golden.jsonc
  10. 117
      pkg/tsdb/jaeger/testdata/search_single_response.golden.jsonc
  11. 9
      public/app/plugins/datasource/jaeger/datasource.ts

@ -354,7 +354,7 @@ require (
github.com/gammazero/deque v0.2.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logfmt/logfmt v0.6.0 // @grafana/oss-big-tent
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect

@ -6,7 +6,9 @@ import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/go-logfmt/logfmt"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)
@ -15,7 +17,7 @@ type JaegerClient struct {
logger log.Logger
url string
httpClient *http.Client
traceIdTimeEnabled bool
settings backend.DataSourceInstanceSettings
}
type ServicesResponse struct {
@ -26,6 +28,12 @@ type ServicesResponse struct {
Total int `json:"total"`
}
type SettingsJSONData struct {
TraceIdTimeParams struct {
Enabled bool `json:"enabled"`
} `json:"traceIdTimeParams"`
}
type DependenciesResponse struct {
Data []ServiceDependency `json:"data"`
Errors []struct {
@ -40,12 +48,12 @@ type ServiceDependency struct {
CallCount int `json:"callCount"`
}
func New(url string, hc *http.Client, logger log.Logger, traceIdTimeEnabled bool) (JaegerClient, error) {
func New(hc *http.Client, logger log.Logger, settings backend.DataSourceInstanceSettings) (JaegerClient, error) {
client := JaegerClient{
logger: logger,
url: url,
url: settings.URL,
httpClient: hc,
traceIdTimeEnabled: traceIdTimeEnabled,
settings: settings,
}
return client, nil
}
@ -106,6 +114,90 @@ func (j *JaegerClient) Operations(s string) ([]string, error) {
return operations, err
}
func (j *JaegerClient) Search(query *JaegerQuery, start, end int64) ([]TraceResponse, error) {
jaegerURL, err := url.Parse(j.url)
if err != nil {
return []TraceResponse{}, fmt.Errorf("failed to parse Jaeger URL: %w", err)
}
jaegerURL.Path = "/api/traces"
var queryTags string
if query.Tags != "" {
tagMap := make(map[string]string)
decoder := logfmt.NewDecoder(strings.NewReader(query.Tags))
for decoder.ScanRecord() {
for decoder.ScanKeyval() {
key := decoder.Key()
value := decoder.Value()
tagMap[string(key)] = string(value)
}
}
marshaledTags, err := json.Marshal(tagMap)
if err != nil {
return []TraceResponse{}, fmt.Errorf("failed to convert tags to JSON: %w", err)
}
queryTags = string(marshaledTags)
}
queryParams := map[string]string{
"service": query.Service,
"operation": query.Operation,
"tags": queryTags,
"minDuration": query.MinDuration,
"maxDuration": query.MaxDuration,
}
urlQuery := jaegerURL.Query()
if query.Limit > 0 {
urlQuery.Set("limit", fmt.Sprintf("%d", query.Limit))
}
if start > 0 {
urlQuery.Set("start", fmt.Sprintf("%d", start))
}
if end > 0 {
urlQuery.Set("end", fmt.Sprintf("%d", end))
}
for key, value := range queryParams {
if value != "" {
urlQuery.Set(key, value)
}
}
jaegerURL.RawQuery = urlQuery.Encode()
resp, err := j.httpClient.Get(jaegerURL.String())
if err != nil {
if backend.IsDownstreamHTTPError(err) {
return []TraceResponse{}, backend.DownstreamError(err)
}
return []TraceResponse{}, err
}
defer func() {
if err = resp.Body.Close(); err != nil {
j.logger.Error("Failed to close response body", "error", err)
}
}()
if resp.StatusCode != http.StatusOK {
err := backend.DownstreamError(fmt.Errorf("request failed: %s", resp.Status))
if backend.ErrorSourceFromHTTPStatus(resp.StatusCode) == backend.ErrorSourceDownstream {
return []TraceResponse{}, backend.DownstreamError(err)
}
return []TraceResponse{}, err
}
var result TracesResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return []TraceResponse{}, fmt.Errorf("failed to decode Jaeger response: %w", err)
}
return result.Data, nil
}
func (j *JaegerClient) Trace(ctx context.Context, traceID string, start, end int64) (TraceResponse, error) {
logger := j.logger.FromContext(ctx)
var response TracesResponse
@ -120,8 +212,13 @@ func (j *JaegerClient) Trace(ctx context.Context, traceID string, start, end int
return trace, backend.DownstreamError(fmt.Errorf("failed to join url: %w", err))
}
// Add time parameters if provided and traceIdTimeEnabled is true
if j.traceIdTimeEnabled {
var jsonData SettingsJSONData
if err := json.Unmarshal(j.settings.JSONData, &jsonData); err != nil {
return trace, backend.DownstreamError(fmt.Errorf("failed to parse settings JSON data: %w", err))
}
// Add time parameters if trace ID time is enabled and time range is provided
if jsonData.TraceIdTimeParams.Enabled {
if start > 0 || end > 0 {
parsedURL, err := url.Parse(traceUrl)
if err != nil {

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"testing"
@ -60,7 +61,10 @@ func TestJaegerClient_Services(t *testing.T) {
}))
defer server.Close()
client, err := New(server.URL, server.Client(), log.NewNullLogger(), false)
settings := backend.DataSourceInstanceSettings{
URL: server.URL,
}
client, err := New(server.Client(), log.NewNullLogger(), settings)
assert.NoError(t, err)
services, err := client.Services()
@ -149,7 +153,10 @@ func TestJaegerClient_Operations(t *testing.T) {
}))
defer server.Close()
client, err := New(server.URL, server.Client(), log.NewNullLogger(), false)
settings := backend.DataSourceInstanceSettings{
URL: server.URL,
}
client, err := New(server.Client(), log.NewNullLogger(), settings)
assert.NoError(t, err)
operations, err := client.Operations(tt.service)
@ -167,11 +174,100 @@ func TestJaegerClient_Operations(t *testing.T) {
}
}
func TestJaegerClient_Search(t *testing.T) {
tests := []struct {
name string
query *JaegerQuery
start int64
end int64
mockResponse string
mockStatusCode int
expectedURL string
expectError bool
expectedError error
}{
{
name: "Successful search with all parameters",
query: &JaegerQuery{
Service: "test-service",
Operation: "test-operation",
Tags: "error=true",
MinDuration: "1s",
MaxDuration: "5s",
Limit: 10,
},
start: 1735689600000000,
end: 1738368000000000,
mockResponse: `{"data":[{"traceID":"test-trace-id"}]}`,
mockStatusCode: http.StatusOK,
expectedURL: "/api/traces?end=1738368000000000&limit=10&maxDuration=5s&minDuration=1s&operation=test-operation&service=test-service&start=1735689600000000&tags=%7B%22error%22%3A%22true%22%7D",
expectError: false,
expectedError: nil,
},
{
name: "Successful search with minimal parameters",
query: &JaegerQuery{
Service: "test-service",
},
start: 1735689600000000,
end: 1738368000000000,
mockResponse: `{"data":[{"traceID":"test-trace-id"}]}`,
mockStatusCode: http.StatusOK,
expectedURL: "/api/traces?end=1738368000000000&service=test-service&start=1735689600000000",
expectError: false,
expectedError: nil,
},
{
name: "Server error",
query: &JaegerQuery{
Service: "test-service",
},
start: 1735689600000000,
end: 1738368000000000,
mockResponse: "",
mockStatusCode: http.StatusInternalServerError,
expectedURL: "/api/traces?end=1738368000000000&service=test-service&start=1735689600000000",
expectError: true,
expectedError: backend.DownstreamError(fmt.Errorf("request failed: %s", http.StatusText(http.StatusInternalServerError))),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var actualURL string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
actualURL = r.URL.String()
w.WriteHeader(tt.mockStatusCode)
_, _ = w.Write([]byte(tt.mockResponse))
}))
defer server.Close()
settings := backend.DataSourceInstanceSettings{
URL: server.URL,
}
client, err := New(server.Client(), log.NewNullLogger(), settings)
assert.NoError(t, err)
traces, err := client.Search(tt.query, tt.start, tt.end)
if tt.expectError {
assert.Error(t, err)
if tt.expectedError != nil {
assert.IsType(t, tt.expectedError, err)
}
} else {
assert.NoError(t, err)
assert.NotNil(t, traces)
assert.Equal(t, tt.expectedURL, actualURL)
}
})
}
}
func TestJaegerClient_Trace(t *testing.T) {
tests := []struct {
name string
traceId string
traceIdTimeEnabled bool
jsonData string
start int64
end int64
mockResponse string
@ -184,7 +280,7 @@ func TestJaegerClient_Trace(t *testing.T) {
{
name: "Successful response with time params enabled",
traceId: "abc123",
traceIdTimeEnabled: true,
jsonData: `{"traceIdTimeParams": {"enabled": true}}`,
start: 1000,
end: 2000,
mockResponse: `{"data":[{"traceID":"abc123"}]}`,
@ -197,7 +293,7 @@ func TestJaegerClient_Trace(t *testing.T) {
{
name: "Successful response with time params disabled",
traceId: "abc123",
traceIdTimeEnabled: false,
jsonData: `{"traceIdTimeParams": {"enabled": false}}`,
start: 1000,
end: 2000,
mockResponse: `{"data":[{"traceID":"abc123"}]}`,
@ -210,7 +306,7 @@ func TestJaegerClient_Trace(t *testing.T) {
{
name: "Non-200 response",
traceId: "abc123",
traceIdTimeEnabled: true,
jsonData: `{"traceIdTimeParams": {"enabled": true}}`,
start: 1000,
end: 2000,
mockResponse: "",
@ -223,7 +319,7 @@ func TestJaegerClient_Trace(t *testing.T) {
{
name: "Invalid JSON response",
traceId: "abc123",
traceIdTimeEnabled: true,
jsonData: `{"traceIdTimeParams": {"enabled": true}}`,
start: 1000,
end: 2000,
mockResponse: `{invalid json`,
@ -236,7 +332,7 @@ func TestJaegerClient_Trace(t *testing.T) {
{
name: "Empty trace ID",
traceId: "",
traceIdTimeEnabled: true,
jsonData: `{"traceIdTimeParams": {"enabled": true}}`,
start: 1000,
end: 2000,
mockResponse: `{"data":[]}`,
@ -258,7 +354,11 @@ func TestJaegerClient_Trace(t *testing.T) {
}))
defer server.Close()
client, err := New(server.URL, server.Client(), log.NewNullLogger(), tt.traceIdTimeEnabled)
settings := backend.DataSourceInstanceSettings{
URL: server.URL,
JSONData: []byte(tt.jsonData),
}
client, err := New(server.Client(), log.NewNullLogger(), settings)
assert.NoError(t, err)
trace, err := client.Trace(context.Background(), tt.traceId, tt.start, tt.end)
@ -367,7 +467,10 @@ func TestJaegerClient_Dependencies(t *testing.T) {
}))
defer server.Close()
client, err := New(server.URL, server.Client(), log.NewNullLogger(), false)
settings := backend.DataSourceInstanceSettings{
URL: server.URL,
}
client, err := New(server.Client(), log.NewNullLogger(), settings)
assert.NoError(t, err)
dependencies, err := client.Dependencies(context.Background(), tt.start, tt.end)

@ -59,7 +59,7 @@ func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.Inst
}
logger := logger.FromContext(ctx)
jaegerClient, err := New(settings.URL, httpClient, logger, jsonData.TraceIdTimeParams.Enabled)
jaegerClient, err := New(httpClient, logger, settings)
return &datasourceInfo{JaegerClient: jaegerClient}, err
}
}

@ -2,6 +2,7 @@ package jaeger
import (
"context"
"encoding/json"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
@ -83,7 +84,13 @@ func TestDataSourceInstanceSettings_TraceIdTimeEnabled(t *testing.T) {
require.NotNil(t, dsInfo)
// Verify the client's traceIdTimeEnabled parameter
assert.Equal(t, tt.expectedEnabled, dsInfo.JaegerClient.traceIdTimeEnabled)
var jsonData SettingsJSONData
if err := json.Unmarshal(dsInfo.JaegerClient.settings.JSONData, &jsonData); err != nil {
t.Fatalf("failed to parse settings JSON data: %v", err)
}
assert.Equal(t, tt.expectedEnabled, jsonData.TraceIdTimeParams.Enabled)
})
}
}

@ -5,12 +5,13 @@ import (
"encoding/json"
"fmt"
"sort"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
)
type jaegerQuery struct {
type JaegerQuery struct {
QueryType string `json:"queryType"`
Service string `json:"service"`
Operation string `json:"operation"`
@ -23,9 +24,10 @@ type jaegerQuery struct {
func queryData(ctx context.Context, dsInfo *datasourceInfo, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
response := backend.NewQueryDataResponse()
logger := dsInfo.JaegerClient.logger.FromContext(ctx)
for _, q := range req.Queries {
var query jaegerQuery
var query JaegerQuery
err := json.Unmarshal(q.JSON, &query)
if err != nil {
@ -34,6 +36,29 @@ func queryData(ctx context.Context, dsInfo *datasourceInfo, req *backend.QueryDa
continue
}
// Handle "Upload" query type
if query.QueryType == "upload" {
logger.Debug("upload query type is not supported in backend mode")
response.Responses[q.RefID] = backend.DataResponse{
Error: fmt.Errorf("unsupported query type %s. only available in frontend mode", query.QueryType),
ErrorSource: backend.ErrorSourceDownstream,
}
continue
}
// Handle "Search" query type
if query.QueryType == "search" {
traces, err := dsInfo.JaegerClient.Search(&query, q.TimeRange.From.UnixMicro(), q.TimeRange.To.UnixMicro())
if err != nil {
response.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(err)
continue
}
frames := transformSearchResponse(traces, dsInfo)
response.Responses[q.RefID] = backend.DataResponse{
Frames: data.Frames{frames},
}
}
// No query type means traceID query
if query.QueryType == "" {
traces, err := dsInfo.JaegerClient.Trace(ctx, query.Query, q.TimeRange.From.UnixMilli(), q.TimeRange.To.UnixMilli())
@ -68,7 +93,98 @@ func queryData(ctx context.Context, dsInfo *datasourceInfo, req *backend.QueryDa
return response, nil
}
// transformTraceResponse converts Jaeger trace data to a Data frame
func transformSearchResponse(response []TraceResponse, dsInfo *datasourceInfo) *data.Frame {
// Create a frame for the traces
frame := data.NewFrame("traces",
data.NewField("traceID", nil, []string{}).SetConfig(&data.FieldConfig{
DisplayName: "Trace ID",
Links: []data.DataLink{
{
Title: "Trace: ${__value.raw}",
URL: "",
Internal: &data.InternalDataLink{
DatasourceUID: dsInfo.JaegerClient.settings.UID,
DatasourceName: dsInfo.JaegerClient.settings.Name,
Query: map[string]interface{}{
"query": "${__value.raw}",
},
},
},
},
}),
data.NewField("traceName", nil, []string{}).SetConfig(&data.FieldConfig{
DisplayName: "Trace name",
}),
data.NewField("startTime", nil, []time.Time{}).SetConfig(&data.FieldConfig{
DisplayName: "Start time",
}),
data.NewField("duration", nil, []int64{}).SetConfig(&data.FieldConfig{
DisplayName: "Duration",
Unit: "µs",
}),
)
// Set the visualization type to table
frame.Meta = &data.FrameMeta{
PreferredVisualization: "table",
}
// Sort traces by start time in descending order (newest first)
sort.Slice(response, func(i, j int) bool {
rootSpanI := response[i].Spans[0]
rootSpanJ := response[j].Spans[0]
for _, span := range response[i].Spans {
if span.StartTime < rootSpanI.StartTime {
rootSpanI = span
}
}
for _, span := range response[j].Spans {
if span.StartTime < rootSpanJ.StartTime {
rootSpanJ = span
}
}
return rootSpanI.StartTime > rootSpanJ.StartTime
})
// Process each trace
for _, trace := range response {
if len(trace.Spans) == 0 {
continue
}
// Get the root span
rootSpan := trace.Spans[0]
for _, span := range trace.Spans {
if span.StartTime < rootSpan.StartTime {
rootSpan = span
}
}
// Get the service name for the trace
serviceName := ""
if process, ok := trace.Processes[rootSpan.ProcessID]; ok {
serviceName = process.ServiceName
}
// Get the trace name and start time
traceName := fmt.Sprintf("%s: %s", serviceName, rootSpan.OperationName)
startTime := time.Unix(0, rootSpan.StartTime*1000)
// Append the row to the frame
frame.AppendRow(
trace.TraceID,
traceName,
startTime,
rootSpan.Duration,
)
}
return frame
}
func transformTraceResponse(trace TraceResponse, refID string) *data.Frame {
frame := data.NewFrame(refID,
data.NewField("traceID", nil, []string{}),
@ -179,61 +295,6 @@ func transformTraceResponse(trace TraceResponse, refID string) *data.Frame {
return frame
}
type TraceKeyValuePair struct {
Key string `json:"key"`
Type string `json:"type"`
Value interface{} `json:"value"`
}
type TraceProcess struct {
ServiceName string `json:"serviceName"`
Tags []TraceKeyValuePair `json:"tags"`
}
type TraceSpanReference struct {
RefType string `json:"refType"`
SpanID string `json:"spanID"`
TraceID string `json:"traceID"`
}
type TraceLog struct {
// Millisecond epoch time
Timestamp int64 `json:"timestamp"`
Fields []TraceKeyValuePair `json:"fields"`
Name string `json:"name"`
}
type Span struct {
TraceID string `json:"traceID"`
SpanID string `json:"spanID"`
ProcessID string `json:"processID"`
OperationName string `json:"operationName"`
// Times are in microseconds
StartTime int64 `json:"startTime"`
Duration int64 `json:"duration"`
Logs []TraceLog `json:"logs"`
References []TraceSpanReference `json:"references"`
Tags []TraceKeyValuePair `json:"tags"`
Warnings []string `json:"warnings"`
Flags int `json:"flags"`
StackTraces []string `json:"stackTraces"`
}
type TraceResponse struct {
Processes map[string]TraceProcess `json:"processes"`
TraceID string `json:"traceID"`
Warnings []string `json:"warnings"`
Spans []Span `json:"spans"`
}
type TracesResponse struct {
Data []TraceResponse `json:"data"`
Errors interface{} `json:"errors"` // TODO: Handle errors, but we were not using them in the frontend either
Limit int `json:"limit"`
Offset int `json:"offset"`
Total int `json:"total"`
}
func transformDependenciesResponse(dependencies DependenciesResponse, refID string) []*data.Frame {
// Create nodes frame
nodesFrame := data.NewFrame(refID+"_nodes",
@ -300,3 +361,58 @@ func transformDependenciesResponse(dependencies DependenciesResponse, refID stri
return []*data.Frame{nodesFrame, edgesFrame}
}
type TraceKeyValuePair struct {
Key string `json:"key"`
Type string `json:"type"`
Value interface{} `json:"value"`
}
type TraceProcess struct {
ServiceName string `json:"serviceName"`
Tags []TraceKeyValuePair `json:"tags"`
}
type TraceSpanReference struct {
RefType string `json:"refType"`
SpanID string `json:"spanID"`
TraceID string `json:"traceID"`
}
type TraceLog struct {
// Millisecond epoch time
Timestamp int64 `json:"timestamp"`
Fields []TraceKeyValuePair `json:"fields"`
Name string `json:"name"`
}
type Span struct {
TraceID string `json:"traceID"`
SpanID string `json:"spanID"`
ProcessID string `json:"processID"`
OperationName string `json:"operationName"`
// Times are in microseconds
StartTime int64 `json:"startTime"`
Duration int64 `json:"duration"`
Logs []TraceLog `json:"logs"`
References []TraceSpanReference `json:"references"`
Tags []TraceKeyValuePair `json:"tags"`
Warnings []string `json:"warnings"`
Flags int `json:"flags"`
StackTraces []string `json:"stackTraces"`
}
type TraceResponse struct {
Processes map[string]TraceProcess `json:"processes"`
TraceID string `json:"traceID"`
Warnings []string `json:"warnings"`
Spans []Span `json:"spans"`
}
type TracesResponse struct {
Data []TraceResponse `json:"data"`
Errors interface{} `json:"errors"` // TODO: Handle errors, but we were not using them in the frontend either
Limit int `json:"limit"`
Offset int `json:"offset"`
Total int `json:"total"`
}

@ -3,9 +3,111 @@ package jaeger
import (
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/experimental"
)
func TestTransformSearchResponse(t *testing.T) {
t.Run("empty_response", func(t *testing.T) {
dsInfo := &datasourceInfo{
JaegerClient: JaegerClient{
settings: backend.DataSourceInstanceSettings{
UID: "test-uid",
Name: "test-name",
},
},
}
frame := transformSearchResponse([]TraceResponse{}, dsInfo)
experimental.CheckGoldenJSONFrame(t, "./testdata", "search_empty_response.golden", frame, false)
})
t.Run("single_trace", func(t *testing.T) {
dsInfo := &datasourceInfo{
JaegerClient: JaegerClient{
settings: backend.DataSourceInstanceSettings{
UID: "test-uid",
Name: "test-name",
},
},
}
response := []TraceResponse{
{
TraceID: "test-trace-id",
Spans: []Span{
{
TraceID: "test-trace-id",
ProcessID: "p1",
OperationName: "test-operation",
StartTime: 1605873894680409,
Duration: 1000,
},
},
Processes: map[string]TraceProcess{
"p1": {
ServiceName: "test-service",
},
},
},
}
frame := transformSearchResponse(response, dsInfo)
experimental.CheckGoldenJSONFrame(t, "./testdata", "search_single_response.golden", frame, false)
})
t.Run("multiple_traces", func(t *testing.T) {
dsInfo := &datasourceInfo{
JaegerClient: JaegerClient{
settings: backend.DataSourceInstanceSettings{
UID: "test-uid",
Name: "test-name",
},
},
}
response := []TraceResponse{
{
TraceID: "trace-1",
Spans: []Span{
{
TraceID: "trace-1",
ProcessID: "p1",
OperationName: "op1",
StartTime: 1605873894680409,
Duration: 1000,
},
},
Processes: map[string]TraceProcess{
"p1": {
ServiceName: "service-1",
},
},
},
{
TraceID: "trace-2",
Spans: []Span{
{
TraceID: "trace-2",
ProcessID: "p2",
OperationName: "op2",
StartTime: 1605873894680409,
Duration: 2000,
},
},
Processes: map[string]TraceProcess{
"p2": {
ServiceName: "service-2",
},
},
},
}
frame := transformSearchResponse(response, dsInfo)
experimental.CheckGoldenJSONFrame(t, "./testdata", "search_multiple_response.golden", frame, false)
})
}
func TestTransformTraceResponse(t *testing.T) {
t.Run("simple_trace", func(t *testing.T) {
trace := TraceResponse{

@ -0,0 +1,100 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "typeVersion": [
// 0,
// 0
// ],
// "preferredVisualisationType": "table"
// }
// Name: traces
// Dimensions: 4 Fields by 0 Rows
// +----------------+-----------------+-------------------+----------------+
// | Name: traceID | Name: traceName | Name: startTime | Name: duration |
// | Labels: | Labels: | Labels: | Labels: |
// | Type: []string | Type: []string | Type: []time.Time | Type: []int64 |
// +----------------+-----------------+-------------------+----------------+
// +----------------+-----------------+-------------------+----------------+
//
//
// 🌟 This was machine generated. Do not edit. 🌟
{
"status": 200,
"frames": [
{
"schema": {
"name": "traces",
"meta": {
"typeVersion": [
0,
0
],
"preferredVisualisationType": "table"
},
"fields": [
{
"name": "traceID",
"type": "string",
"typeInfo": {
"frame": "string"
},
"config": {
"displayName": "Trace ID",
"links": [
{
"title": "Trace: ${__value.raw}",
"internal": {
"query": {
"query": "${__value.raw}"
},
"datasourceUid": "test-uid",
"datasourceName": "test-name"
}
}
]
}
},
{
"name": "traceName",
"type": "string",
"typeInfo": {
"frame": "string"
},
"config": {
"displayName": "Trace name"
}
},
{
"name": "startTime",
"type": "time",
"typeInfo": {
"frame": "time.Time"
},
"config": {
"displayName": "Start time"
}
},
{
"name": "duration",
"type": "number",
"typeInfo": {
"frame": "int64"
},
"config": {
"displayName": "Duration",
"unit": "µs"
}
}
]
},
"data": {
"values": [
[],
[],
[],
[]
]
}
}
]
}

@ -0,0 +1,123 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "typeVersion": [
// 0,
// 0
// ],
// "preferredVisualisationType": "table"
// }
// Name: traces
// Dimensions: 4 Fields by 2 Rows
// +----------------+-----------------+--------------------------------------+----------------+
// | Name: traceID | Name: traceName | Name: startTime | Name: duration |
// | Labels: | Labels: | Labels: | Labels: |
// | Type: []string | Type: []string | Type: []time.Time | Type: []int64 |
// +----------------+-----------------+--------------------------------------+----------------+
// | trace-1 | service-1: op1 | 2020-11-20 12:04:54.680409 +0000 GMT | 1000 |
// | trace-2 | service-2: op2 | 2020-11-20 12:04:54.680409 +0000 GMT | 2000 |
// +----------------+-----------------+--------------------------------------+----------------+
//
//
// 🌟 This was machine generated. Do not edit. 🌟
{
"status": 200,
"frames": [
{
"schema": {
"name": "traces",
"meta": {
"typeVersion": [
0,
0
],
"preferredVisualisationType": "table"
},
"fields": [
{
"name": "traceID",
"type": "string",
"typeInfo": {
"frame": "string"
},
"config": {
"displayName": "Trace ID",
"links": [
{
"title": "Trace: ${__value.raw}",
"internal": {
"query": {
"query": "${__value.raw}"
},
"datasourceUid": "test-uid",
"datasourceName": "test-name"
}
}
]
}
},
{
"name": "traceName",
"type": "string",
"typeInfo": {
"frame": "string"
},
"config": {
"displayName": "Trace name"
}
},
{
"name": "startTime",
"type": "time",
"typeInfo": {
"frame": "time.Time"
},
"config": {
"displayName": "Start time"
}
},
{
"name": "duration",
"type": "number",
"typeInfo": {
"frame": "int64"
},
"config": {
"displayName": "Duration",
"unit": "µs"
}
}
]
},
"data": {
"values": [
[
"trace-1",
"trace-2"
],
[
"service-1: op1",
"service-2: op2"
],
[
1605873894680,
1605873894680
],
[
1000,
2000
]
],
"nanos": [
null,
null,
[
409000,
409000
],
null
]
}
}
]
}

@ -0,0 +1,117 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "typeVersion": [
// 0,
// 0
// ],
// "preferredVisualisationType": "table"
// }
// Name: traces
// Dimensions: 4 Fields by 1 Rows
// +----------------+------------------------------+--------------------------------------+----------------+
// | Name: traceID | Name: traceName | Name: startTime | Name: duration |
// | Labels: | Labels: | Labels: | Labels: |
// | Type: []string | Type: []string | Type: []time.Time | Type: []int64 |
// +----------------+------------------------------+--------------------------------------+----------------+
// | test-trace-id | test-service: test-operation | 2020-11-20 12:04:54.680409 +0000 GMT | 1000 |
// +----------------+------------------------------+--------------------------------------+----------------+
//
//
// 🌟 This was machine generated. Do not edit. 🌟
{
"status": 200,
"frames": [
{
"schema": {
"name": "traces",
"meta": {
"typeVersion": [
0,
0
],
"preferredVisualisationType": "table"
},
"fields": [
{
"name": "traceID",
"type": "string",
"typeInfo": {
"frame": "string"
},
"config": {
"displayName": "Trace ID",
"links": [
{
"title": "Trace: ${__value.raw}",
"internal": {
"query": {
"query": "${__value.raw}"
},
"datasourceUid": "test-uid",
"datasourceName": "test-name"
}
}
]
}
},
{
"name": "traceName",
"type": "string",
"typeInfo": {
"frame": "string"
},
"config": {
"displayName": "Trace name"
}
},
{
"name": "startTime",
"type": "time",
"typeInfo": {
"frame": "time.Time"
},
"config": {
"displayName": "Start time"
}
},
{
"name": "duration",
"type": "number",
"typeInfo": {
"frame": "int64"
},
"config": {
"displayName": "Duration",
"unit": "µs"
}
}
]
},
"data": {
"values": [
[
"test-trace-id"
],
[
"test-service: test-operation"
],
[
1605873894680
],
[
1000
]
],
"nanos": [
null,
null,
[
409000
],
null
]
}
}
]
}

@ -69,6 +69,9 @@ export class JaegerDatasource extends DataSourceWithBackend<JaegerQuery, JaegerJ
return !!query.service;
}
/**
* Migrated to backend with feature toggle `jaegerBackendMigration`
*/
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.
@ -77,11 +80,7 @@ export class JaegerDatasource extends DataSourceWithBackend<JaegerQuery, JaegerJ
return of({ data: [emptyTraceDataFrame] });
}
if (
config.featureToggles.jaegerBackendMigration &&
// No query type means that the query is a trace ID query
(!target.queryType || target.queryType === 'dependencyGraph')
) {
if (config.featureToggles.jaegerBackendMigration && target.queryType !== 'upload') {
return super.query({ ...options, targets: [target] }).pipe(
map((response) => {
// If the node graph is enabled and the query is a trace ID query, add the node graph frames to the response

Loading…
Cancel
Save