Zipkin: Run resource calls through backend with feature toggle enabled (#96139)

* Zipkin: Run resource calls througgh backend with feature toggle enabled

* Update

* Don't return early in createZipkinURL and add tests

* Update pkg/tsdb/zipkin/client.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client_test.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client_test.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client_test.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Update pkg/tsdb/zipkin/client_test.go

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>

* Fix lint

* Fix tests

---------

Co-authored-by: Sriram <153843+yesoreyeram@users.noreply.github.com>
This commit is contained in:
Ivana Huckova 2024-11-11 13:04:22 +01:00 committed by GitHub
parent c7b6822a5e
commit e5519161f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 588 additions and 16 deletions

2
go.mod
View File

@ -476,6 +476,8 @@ require github.com/jmespath-community/go-jmespath v1.1.1 // @grafana/identity-ac
require github.com/grafana/loki/v3 v3.2.1 // @grafana/observability-logs
require github.com/openzipkin/zipkin-go v0.4.3 // @grafana/oss-big-tent
require (
cloud.google.com/go/longrunning v0.6.0 // indirect
github.com/at-wat/mqtt-go v0.19.4 // indirect

2
go.sum
View File

@ -2903,6 +2903,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE=
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0=
github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY=

View File

@ -2,12 +2,14 @@ package zipkin
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/openzipkin/zipkin-go/model"
)
type ZipkinClient struct {
@ -48,3 +50,119 @@ func (z *ZipkinClient) Services() ([]string, error) {
}
return services, err
}
// Spans returns list of spans for the given service
// https://zipkin.io/zipkin-api/#/default/get_spans
func (z *ZipkinClient) Spans(serviceName string) ([]string, error) {
spans := []string{}
if serviceName == "" {
return spans, errors.New("invalid/empty serviceName")
}
spansUrl, err := createZipkinURL(z.url, "/api/v2/spans", map[string]string{"serviceName": serviceName})
if err != nil {
return spans, backend.DownstreamError(fmt.Errorf("failed to compose url: %w", err))
}
res, err := z.httpClient.Get(spansUrl)
defer func() {
if res != nil {
if err = res.Body.Close(); err != nil {
z.logger.Error("Failed to close response body", "error", err)
}
}
}()
if err != nil {
return spans, err
}
if err := json.NewDecoder(res.Body).Decode(&spans); err != nil {
return spans, err
}
return spans, err
}
// Traces returns list of traces for the given service and span
// https://zipkin.io/zipkin-api/#/default/get_traces
func (z *ZipkinClient) Traces(serviceName string, spanName string) ([][]model.SpanModel, error) {
traces := [][]model.SpanModel{}
if serviceName == "" {
return traces, errors.New("invalid/empty serviceName")
}
if spanName == "" {
return traces, errors.New("invalid/empty spanName")
}
tracesUrl, err := createZipkinURL(z.url, "/api/v2/traces", map[string]string{"serviceName": serviceName, "spanName": spanName})
if err != nil {
return traces, backend.DownstreamError(fmt.Errorf("failed to compose url: %w", err))
}
res, err := z.httpClient.Get(tracesUrl)
defer func() {
if res != nil {
if err = res.Body.Close(); err != nil {
z.logger.Error("Failed to close response body", "error", err)
}
}
}()
if err != nil {
return traces, err
}
if err := json.NewDecoder(res.Body).Decode(&traces); err != nil {
return traces, err
}
return traces, err
}
// Trace returns trace for the given traceId
// https://zipkin.io/zipkin-api/#/default/get_trace__traceId_
func (z *ZipkinClient) Trace(traceId string) ([]model.SpanModel, error) {
trace := []model.SpanModel{}
if traceId == "" {
return trace, errors.New("invalid/empty traceId")
}
traceUrl, err := url.JoinPath(z.url, "/api/v2/trace", url.QueryEscape(traceId))
if err != nil {
return trace, backend.DownstreamError(fmt.Errorf("failed to join url: %w", err))
}
res, err := z.httpClient.Get(traceUrl)
defer func() {
if res != nil {
if err = res.Body.Close(); err != nil {
z.logger.Error("Failed to close response body", "error", err)
}
}
}()
if err != nil {
return trace, err
}
if err := json.NewDecoder(res.Body).Decode(&trace); err != nil {
return trace, err
}
return trace, err
}
func createZipkinURL(baseURL string, path string, params map[string]string) (string, error) {
// Parse the base URL
finalUrl, err := url.Parse(baseURL)
if err != nil {
return "", err
}
// Add the path
urlPath, err := url.JoinPath(finalUrl.Path, path)
if err != nil {
return "", err
}
finalUrl.Path = urlPath
// If there are query parameters, add them
if len(params) > 0 {
queryParams := finalUrl.Query()
for k, v := range params {
queryParams.Set(k, v)
}
finalUrl.RawQuery = queryParams.Encode()
}
// Return the composed URL as a string
return finalUrl.String(), nil
}

View File

@ -0,0 +1,357 @@
package zipkin
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/openzipkin/zipkin-go/model"
"github.com/stretchr/testify/assert"
)
func TestZipkinClient_Services(t *testing.T) {
tests := []struct {
name string
mockResponse string
mockStatusCode int
expectedResult []string
expectError bool
}{
{
name: "Successful response",
mockResponse: `["service1", "service2"]`,
mockStatusCode: http.StatusOK,
expectedResult: []string{"service1", "service2"},
expectError: false,
},
{
name: "Non-200 response",
mockResponse: "",
mockStatusCode: http.StatusInternalServerError,
expectedResult: []string{},
expectError: true,
},
{
name: "Invalid JSON response",
mockResponse: `{invalid json`,
mockStatusCode: http.StatusOK,
expectedResult: []string{},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/v2/services", r.URL.Path)
w.WriteHeader(tt.mockStatusCode)
_, _ = w.Write([]byte(tt.mockResponse))
}))
defer server.Close()
client, _ := New(server.URL, server.Client(), log.New())
services, err := client.Services()
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.expectedResult, services)
})
}
}
func TestZipkinClient_Spans(t *testing.T) {
tests := []struct {
name string
serviceName string
mockResponse string
mockStatusCode int
expectedResult []string
expectError bool
}{
{
name: "Successful response",
serviceName: "service1",
mockResponse: `["span1", "span2"]`,
mockStatusCode: http.StatusOK,
expectedResult: []string{"span1", "span2"},
expectError: false,
},
{
name: "Non-200 response",
serviceName: "service1",
mockResponse: "",
mockStatusCode: http.StatusNotFound,
expectedResult: []string{},
expectError: true,
},
{
name: "Invalid JSON response",
serviceName: "service1",
mockResponse: `{invalid json`,
mockStatusCode: http.StatusOK,
expectedResult: []string{},
expectError: true,
},
{
name: "Empty serviceName",
serviceName: "",
mockResponse: "",
mockStatusCode: http.StatusOK,
expectedResult: []string{},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/v2/spans", r.URL.Path)
w.WriteHeader(tt.mockStatusCode)
_, _ = w.Write([]byte(tt.mockResponse))
}))
defer server.Close()
client, _ := New(server.URL, server.Client(), log.New())
spans, err := client.Spans(tt.serviceName)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.expectedResult, spans)
})
}
}
func TestZipkinClient_Traces(t *testing.T) {
tests := []struct {
name string
serviceName string
spanName string
mockResponse interface{}
mockStatusCode int
expectedResult [][]model.SpanModel
expectError bool
expectedError string
}{
{
name: "Successful response",
serviceName: "service1",
spanName: "span1",
mockResponse: [][]model.SpanModel{{{SpanContext: model.SpanContext{TraceID: model.TraceID{Low: 1234}, ID: 1}, Name: "operation1", Tags: map[string]string{"key1": "value1"}}}},
mockStatusCode: http.StatusOK,
expectedResult: [][]model.SpanModel{{{SpanContext: model.SpanContext{TraceID: model.TraceID{Low: 1234}, ID: 1}, Name: "operation1", Tags: map[string]string{"key1": "value1"}}}},
expectError: false,
expectedError: "",
},
{
name: "Non-200 response",
serviceName: "service1",
spanName: "span1",
mockResponse: nil,
mockStatusCode: http.StatusForbidden,
expectedResult: [][]model.SpanModel{},
expectError: true,
expectedError: "EOF",
},
{
name: "Empty serviceName",
serviceName: "",
spanName: "span1",
mockResponse: nil,
mockStatusCode: http.StatusOK,
expectedResult: [][]model.SpanModel{},
expectError: true,
expectedError: "invalid/empty serviceName",
},
{
name: "Empty spanName",
serviceName: "service1",
spanName: "",
mockResponse: nil,
mockStatusCode: http.StatusOK,
expectedResult: [][]model.SpanModel{},
expectError: true,
expectedError: "invalid/empty spanName",
},
{
name: "Valid response with empty trace list",
serviceName: "service1",
spanName: "span1",
mockResponse: [][]model.SpanModel{},
mockStatusCode: http.StatusOK,
expectedResult: [][]model.SpanModel{},
expectError: false,
expectedError: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var response []byte
if mockData, ok := tt.mockResponse.([][]model.SpanModel); ok {
response, _ = json.Marshal(mockData)
} else if str, ok := tt.mockResponse.(string); ok {
response = []byte(str)
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/v2/traces", r.URL.Path)
w.WriteHeader(tt.mockStatusCode)
_, _ = w.Write(response)
}))
defer server.Close()
client, _ := New(server.URL, server.Client(), log.New())
traces, err := client.Traces(tt.serviceName, tt.spanName)
if tt.expectError {
assert.Error(t, err)
assert.Equal(t, err.Error(), tt.expectedError)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.expectedResult, traces)
})
}
}
func TestZipkinClient_Trace(t *testing.T) {
tests := []struct {
name string
traceID string
mockResponse string
mockStatusCode int
expectedResult []model.SpanModel
expectError bool
expectedError string
}{
{
name: "Successful response",
traceID: "trace-id",
mockResponse: `[{"traceId":"00000000000004d2","id":"0000000000000001","name":"operation1","tags":{"key1":"value1"}}]`,
mockStatusCode: http.StatusOK,
expectedResult: []model.SpanModel{
{
SpanContext: model.SpanContext{
TraceID: model.TraceID{Low: 1234},
ID: model.ID(1),
},
Name: "operation1",
Tags: map[string]string{"key1": "value1"},
},
},
expectError: false,
},
{
name: "Invalid traceID",
traceID: "",
mockResponse: "",
mockStatusCode: http.StatusOK,
expectedResult: []model.SpanModel{},
expectError: true,
expectedError: "invalid/empty traceId",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var client ZipkinClient
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/v2/trace/"+tt.traceID, r.URL.Path)
w.WriteHeader(tt.mockStatusCode)
_, _ = w.Write([]byte(tt.mockResponse))
}))
defer server.Close()
client, _ = New(server.URL, server.Client(), log.New())
trace, err := client.Trace(tt.traceID)
if tt.expectError {
assert.Error(t, err)
assert.Empty(t, trace)
assert.Equal(t, tt.expectedError, err.Error())
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, trace)
}
})
}
}
func TestCreateZipkinURL(t *testing.T) {
tests := []struct {
name string
baseURL string
path string
params map[string]string
expected string
shouldErr bool
}{
{
name: "WithPathAndParams",
baseURL: "http://example.com",
path: "api/v1/trace",
params: map[string]string{"key1": "value1", "key2": "value2"},
expected: "http://example.com/api/v1/trace?key1=value1&key2=value2",
},
{
name: "OnlyParams",
baseURL: "http://example.com",
path: "",
params: map[string]string{"key1": "value1"},
expected: "http://example.com?key1=value1",
},
{
name: "NoParams",
baseURL: "http://example.com",
path: "api/v1/trace",
params: map[string]string{},
expected: "http://example.com/api/v1/trace",
},
{
name: "InvalidBaseURL",
baseURL: "http://example .com",
path: "api/v1/trace",
params: map[string]string{},
shouldErr: true,
},
{
name: "BaseURLWithPath",
baseURL: "http://example.com/base",
path: "api/v1/trace",
params: map[string]string{"key1": "value1"},
expected: "http://example.com/base/api/v1/trace?key1=value1",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result, err := createZipkinURL(tc.baseURL, tc.path, tc.params)
if tc.shouldErr {
if err == nil {
t.Fatalf("Expected error, but got nil")
}
return
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if result != tc.expected {
t.Errorf("Expected %s, got %s", tc.expected, result)
}
})
}
}

View File

@ -0,0 +1,88 @@
package zipkin
import (
"encoding/json"
"errors"
"net/http"
"strings"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)
func (s *Service) registerResourceRoutes() *http.ServeMux {
router := http.NewServeMux()
router.HandleFunc("GET /services", s.withDatasourceHandlerFunc(getServicesHandler))
router.HandleFunc("GET /spans", s.withDatasourceHandlerFunc(getSpansHandler))
router.HandleFunc("GET /traces", s.withDatasourceHandlerFunc(getTracesHandler))
router.HandleFunc("GET /trace/{traceId}", s.withDatasourceHandlerFunc(getTraceHandler))
return router
}
func (s *Service) withDatasourceHandlerFunc(getHandler func(d *datasourceInfo) http.HandlerFunc) func(rw http.ResponseWriter, r *http.Request) {
return func(rw http.ResponseWriter, r *http.Request) {
client, err := s.getDSInfo(r.Context(), backend.PluginConfigFromContext(r.Context()))
if err != nil {
writeResponse(nil, errors.New("error getting data source information from context"), rw, client.ZipkinClient.logger)
return
}
h := getHandler(client)
h.ServeHTTP(rw, r)
}
}
func getServicesHandler(ds *datasourceInfo) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
services, err := ds.ZipkinClient.Services()
writeResponse(services, err, rw, ds.ZipkinClient.logger)
}
}
func getSpansHandler(ds *datasourceInfo) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
serviceName := strings.TrimSpace(r.URL.Query().Get("serviceName"))
spans, err := ds.ZipkinClient.Spans(serviceName)
writeResponse(spans, err, rw, ds.ZipkinClient.logger)
}
}
func getTracesHandler(ds *datasourceInfo) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
serviceName := strings.TrimSpace(r.URL.Query().Get("serviceName"))
spanName := strings.TrimSpace(r.URL.Query().Get("spanName"))
traces, err := ds.ZipkinClient.Traces(serviceName, spanName)
writeResponse(traces, err, rw, ds.ZipkinClient.logger)
}
}
func getTraceHandler(ds *datasourceInfo) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
traceId := strings.TrimSpace(r.PathValue("traceId"))
trace, err := ds.ZipkinClient.Trace(traceId)
writeResponse(trace, err, rw, ds.ZipkinClient.logger)
}
}
func writeResponse(res interface{}, err error, rw http.ResponseWriter, logger log.Logger) {
if err != nil {
// This is used for resource calls, we don't need to add actual error message, but we should log it
logger.Warn("An error occurred while doing a resource call", "error", err)
http.Error(rw, "An error occurred within the plugin", http.StatusInternalServerError)
return
}
// Response should not be string, but just in case, handle it
if str, ok := res.(string); ok {
rw.Header().Set("Content-Type", "text/plain")
_, _ = rw.Write([]byte(str))
return
}
b, err := json.Marshal(res)
if err != nil {
// This is used for resource calls, we don't need to add actual error message, but we should log it
logger.Warn("An error occurred while processing response from resource call", "error", err)
http.Error(rw, "An error occurred within the plugin", http.StatusInternalServerError)
return
}
rw.Header().Set("Content-Type", "application/json")
_, _ = rw.Write(b)
}

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
"github.com/grafana/grafana/pkg/infra/httpclient"
)
@ -81,3 +82,8 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
Message: "Data source is working",
}, nil
}
func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
handler := httpadapter.New(s.registerResourceRoutes())
return handler.CallResource(ctx, req, sender)
}

View File

@ -29,7 +29,7 @@ describe('useServices', () => {
it('returns services from datasource', async () => {
const ds = {
async metadataRequest(url) {
if (url === '/api/v2/services') {
if (url === 'services') {
return Promise.resolve(['service1', 'service2']);
}
return undefined;
@ -50,11 +50,11 @@ describe('useLoadOptions', () => {
it('loads spans and traces', async () => {
const ds = {
async metadataRequest(url, params) {
if (url === '/api/v2/spans' && params?.serviceName === 'service1') {
if (url === 'spans' && params?.serviceName === 'service1') {
return Promise.resolve(['span1', 'span2']);
}
if (url === '/api/v2/traces' && params?.serviceName === 'service1' && params?.spanName === 'span1') {
if (url === 'traces' && params?.serviceName === 'service1' && params?.spanName === 'span1') {
return Promise.resolve([[{ name: 'trace1', duration: 10_000, traceId: 'traceId1' }]]);
}
return undefined;

View File

@ -21,7 +21,6 @@ import {
Button,
} from '@grafana/ui';
import { apiPrefix } from './constants';
import { ZipkinDatasource } from './datasource';
import { ZipkinQuery, ZipkinQueryType, ZipkinSpan } from './types';
@ -147,11 +146,9 @@ export function useServices(
datasource: ZipkinDatasource,
setErrorText: (text: string) => void
): AsyncState<CascaderOption[]> {
const url = `${apiPrefix}/services`;
const [servicesOptions, fetch] = useAsyncFn(async (): Promise<CascaderOption[]> => {
try {
const services: string[] | null = await datasource.metadataRequest(url);
const services: string[] | null = await datasource.metadataRequest('services');
if (services) {
return services.sort().map((service) => ({
label: service,
@ -191,12 +188,11 @@ export function useLoadOptions(datasource: ZipkinDatasource, setErrorText: (text
const [, fetchSpans] = useAsyncFn(
async function findSpans(service: string): Promise<void> {
const url = `${apiPrefix}/spans`;
try {
// The response of this should have been full ZipkinSpan objects based on API docs but is just list
// of span names.
// TODO: check if this is some issue of version used or something else
const response: string[] = await datasource.metadataRequest(url, { serviceName: service });
const response: string[] = await datasource.metadataRequest('spans', { serviceName: service });
if (isMounted()) {
setAllOptions((state) => {
const spanOptions = fromPairs(response.map((span: string) => [span, undefined]));
@ -218,7 +214,6 @@ export function useLoadOptions(datasource: ZipkinDatasource, setErrorText: (text
const [, fetchTraces] = useAsyncFn(
async function findTraces(serviceName: string, spanName: string): Promise<void> {
const url = `${apiPrefix}/traces`;
const search = {
serviceName,
spanName,
@ -226,7 +221,7 @@ export function useLoadOptions(datasource: ZipkinDatasource, setErrorText: (text
};
try {
// This should return just root traces as there isn't any nesting
const traces: ZipkinSpan[][] = await datasource.metadataRequest(url, search);
const traces: ZipkinSpan[][] = await datasource.metadataRequest('traces', search);
if (isMounted()) {
const newTraces = traces.length
? fromPairs(

View File

@ -1 +0,0 @@
export const apiPrefix = '/api/v2';

View File

@ -76,7 +76,7 @@ describe('ZipkinDatasource', () => {
it('runs query', async () => {
setupBackendSrv(['service 1', 'service 2'] as unknown as ZipkinSpan[]);
const ds = new ZipkinDatasource(defaultSettings);
const response = await ds.metadataRequest('/api/v2/services');
const response = await ds.metadataRequest('services');
expect(response).toEqual(['service 1', 'service 2']);
});
});

View File

@ -22,11 +22,12 @@ import {
TemplateSrv,
} from '@grafana/runtime';
import { apiPrefix } from './constants';
import { ZipkinQuery, ZipkinSpan } from './types';
import { createGraphFrames } from './utils/graphTransform';
import { transformResponse } from './utils/transforms';
const apiPrefix = '/api/v2';
export interface ZipkinJsonData extends DataSourceJsonData {
nodeGraph?: NodeGraphOptions;
}
@ -68,7 +69,11 @@ export class ZipkinDatasource extends DataSourceWithBackend<ZipkinQuery, ZipkinJ
}
async metadataRequest(url: string, params?: Record<string, unknown>) {
const res = await lastValueFrom(this.request(url, params, { hideFromInspector: true }));
if (config.featureToggles.zipkinBackendMigration) {
return await this.getResource(url, params);
}
const urlWithPrefix = `${apiPrefix}/${url}`;
const res = await lastValueFrom(this.request(urlWithPrefix, params, { hideFromInspector: true }));
return res.data;
}
@ -77,7 +82,7 @@ export class ZipkinDatasource extends DataSourceWithBackend<ZipkinQuery, ZipkinJ
return await super.testDatasource();
}
await this.metadataRequest(`${apiPrefix}/services`);
await this.metadataRequest('services');
return { status: 'success', message: 'Data source is working' };
}