diff --git a/pkg/tsdb/graphite/resource_handler.go b/pkg/tsdb/graphite/resource_handler.go index dfd1a3c6568..2651c320d65 100644 --- a/pkg/tsdb/graphite/resource_handler.go +++ b/pkg/tsdb/graphite/resource_handler.go @@ -27,7 +27,7 @@ func (s *Service) newResourceMux() *http.ServeMux { mux.HandleFunc("/functions", handleResourceReq(s.handleFunctions, s)) mux.HandleFunc("/tags/autoComplete/tags", handleResourceReq(s.handleTagsAutocomplete, s)) mux.HandleFunc("/tags/autoComplete/values", handleResourceReq(s.handleTagValuesAutocomplete, s)) - + mux.HandleFunc("/version", handleResourceReq(s.handleVersion, s)) return mux } @@ -99,7 +99,6 @@ func (s *Service) handleEvents(ctx context.Context, dsInfo *datasourceInfo, even req, err := s.createRequest(ctx, dsInfo, URLParams{ SubPath: "events/get_data", - Method: http.MethodGet, QueryParams: queryParams, }) if err != nil { @@ -179,7 +178,6 @@ func (s *Service) handleMetricsExpand(ctx context.Context, dsInfo *datasourceInf req, err := s.createRequest(ctx, dsInfo, URLParams{ SubPath: "metrics/expand", - Method: http.MethodGet, QueryParams: queryParams, }) if err != nil { @@ -215,7 +213,6 @@ func (s *Service) handleTagsAutocomplete(ctx context.Context, dsInfo *datasource } req, err := s.createRequest(ctx, dsInfo, URLParams{ SubPath: "tags/autoComplete/tags", - Method: http.MethodGet, QueryParams: queryParams, }) if err != nil { @@ -247,7 +244,6 @@ func (s *Service) handleTagValuesAutocomplete(ctx context.Context, dsInfo *datas req, err := s.createRequest(ctx, dsInfo, URLParams{ SubPath: "tags/autoComplete/values", - Method: http.MethodGet, QueryParams: queryParams, }) if err != nil { @@ -267,10 +263,30 @@ func (s *Service) handleTagValuesAutocomplete(ctx context.Context, dsInfo *datas return tagValuesResponse, statusCode, nil } +func (s *Service) handleVersion(ctx context.Context, dsInfo *datasourceInfo, _ *any) ([]byte, int, error) { + req, err := s.createRequest(ctx, dsInfo, URLParams{ + SubPath: "version", + }) + if err != nil { + return nil, http.StatusInternalServerError, fmt.Errorf("failed to create version request %v", err) + } + + version, _, statusCode, err := doGraphiteRequest[string](ctx, dsInfo, s.logger, req, false) + if err != nil { + return nil, statusCode, fmt.Errorf("version request failed: %v", err) + } + + versionResponse, err := json.Marshal(version) + if err != nil { + return nil, http.StatusInternalServerError, fmt.Errorf("failed to marshal version response: %s", err) + } + + return versionResponse, statusCode, nil +} + func (s *Service) handleFunctions(ctx context.Context, dsInfo *datasourceInfo, _ *any) ([]byte, int, error) { req, err := s.createRequest(ctx, dsInfo, URLParams{ SubPath: "functions", - Method: http.MethodGet, }) if err != nil { return nil, http.StatusInternalServerError, fmt.Errorf("failed to create functions request %v", err) diff --git a/pkg/tsdb/graphite/resource_handler_test.go b/pkg/tsdb/graphite/resource_handler_test.go index c4ba2e201d2..66777f95072 100644 --- a/pkg/tsdb/graphite/resource_handler_test.go +++ b/pkg/tsdb/graphite/resource_handler_test.go @@ -620,6 +620,86 @@ func TestHandleTagValuesAutocomplete(t *testing.T) { }) } } + +func TestHandleVersion(t *testing.T) { + tests := []struct { + name string + responseBody string + statusCode int + expectError bool + errorContains string + expectedData string + }{ + { + name: "successful version request", + responseBody: `"1.1.10"`, + statusCode: 200, + expectedData: "1.1.10", + }, + { + name: "version with build info", + responseBody: `"1.1.10-pre1"`, + statusCode: 200, + expectedData: "1.1.10-pre1", + }, + { + name: "version request server error - invalid JSON causes parse error", + responseBody: `{"error": "internal error"}`, + statusCode: 500, + expectError: true, + errorContains: "version request failed", + }, + { + name: "version request not found - invalid JSON causes parse error", + responseBody: `{"error": "not found"}`, + statusCode: 404, + expectError: true, + errorContains: "version request failed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockTransport := &mockRoundTripper{ + respBody: []byte(tt.responseBody), + status: tt.statusCode, + } + + dsInfo := &datasourceInfo{ + HTTPClient: &http.Client{Transport: mockTransport}, + URL: "http://graphite.example.com", + } + + service := &Service{ + logger: log.NewNullLogger(), + } + + result, statusCode, err := service.handleVersion(context.Background(), dsInfo, nil) + + if tt.expectError { + assert.Error(t, err) + if tt.errorContains != "" { + assert.Contains(t, err.Error(), tt.errorContains) + } + } else { + assert.NoError(t, err) + assert.Equal(t, tt.statusCode, statusCode) + + var version string + err = json.Unmarshal(result, &version) + assert.NoError(t, err) + assert.Equal(t, tt.expectedData, version) + } + + if !tt.expectError { + expectedURL := "http://graphite.example.com/version" + assert.Equal(t, expectedURL, mockTransport.lastRequest.URL.String()) + assert.Equal(t, http.MethodGet, mockTransport.lastRequest.Method) + } + }) + } +} + func TestHandleFunctions(t *testing.T) { tests := []struct { name string diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index 2c09afd4b83..93fa556c428 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -871,7 +871,7 @@ export class GraphiteDatasource return lastValueFrom(this.doGraphiteRequest(httpOptions).pipe(mapToTags())); } - getVersion(optionalOptions: any) { + async getVersion(optionalOptions: any) { const options = optionalOptions || {}; const httpOptions = { @@ -880,6 +880,12 @@ export class GraphiteDatasource requestId: options.requestId, }; + if (config.featureToggles.graphiteBackendMode) { + const version = await this.getResource('version'); + const semver = new SemVer(version); + return valid(semver) ? version : ''; + } + return lastValueFrom( this.doGraphiteRequest(httpOptions).pipe( map((results: FetchResponse) => {