diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go index 361ed9279be..dee69d9fe5c 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go @@ -18,7 +18,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "github.com/grafana/grafana-plugin-sdk-go/data" - "github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -142,12 +141,12 @@ func (e *AzureLogAnalyticsDatasource) ExecuteTimeSeriesQuery(ctx context.Context for _, query := range originalQueries { logsQuery, err := e.buildQuery(ctx, query, dsInfo, fromAlert) if err != nil { - errorsource.AddErrorToResponse(query.RefID, result, err) + result.Responses[query.RefID] = backend.ErrorResponseWithErrorSource(err) continue } res, err := e.executeQuery(ctx, logsQuery, dsInfo, client, url) if err != nil { - errorsource.AddErrorToResponse(query.RefID, result, err) + result.Responses[query.RefID] = backend.ErrorResponseWithErrorSource(err) continue } result.Responses[query.RefID] = *res @@ -252,7 +251,7 @@ func (e *AzureLogAnalyticsDatasource) buildQuery(ctx context.Context, query back cfg := backend.GrafanaConfigFromContext(ctx) hasPromExemplarsToggle := cfg.FeatureToggles().IsEnabled("azureMonitorPrometheusExemplars") if !hasPromExemplarsToggle { - return nil, errorsource.DownstreamError(fmt.Errorf("query type unsupported as azureMonitorPrometheusExemplars feature toggle is not enabled"), false) + return nil, backend.DownstreamError(fmt.Errorf("query type unsupported as azureMonitorPrometheusExemplars feature toggle is not enabled")) } } azureAppInsightsQuery, err := buildAppInsightsQuery(ctx, query, dsInfo, appInsightsRegExp, e.Logger) @@ -269,7 +268,7 @@ func (e *AzureLogAnalyticsDatasource) buildQuery(ctx context.Context, query back func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *AzureLogAnalyticsQuery, dsInfo types.DatasourceInfo, client *http.Client, url string) (*backend.DataResponse, error) { // If azureLogAnalyticsSameAs is defined and set to false, return an error if sameAs, ok := dsInfo.JSONData["azureLogAnalyticsSameAs"]; ok && !sameAs.(bool) { - return nil, errorsource.DownstreamError(fmt.Errorf("credentials for Log Analytics are no longer supported. Go to the data source configuration to update Azure Monitor credentials"), false) + return nil, backend.DownstreamError(fmt.Errorf("credentials for Log Analytics are no longer supported. Go to the data source configuration to update Azure Monitor credentials")) } queryJSONModel := dataquery.AzureMonitorQuery{} @@ -280,7 +279,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *A if query.QueryType == dataquery.AzureQueryTypeAzureTraces { if query.ResultFormat == dataquery.ResultFormatTrace && query.Query == "" { - return nil, errorsource.DownstreamError(fmt.Errorf("cannot visualise trace events using the trace visualiser"), false) + return nil, backend.DownstreamError(fmt.Errorf("cannot visualise trace events using the trace visualiser")) } } @@ -301,7 +300,7 @@ func (e *AzureLogAnalyticsDatasource) executeQuery(ctx context.Context, query *A res, err := client.Do(req) if err != nil { - return nil, errorsource.DownstreamError(err, false) + return nil, backend.DownstreamError(err) } defer func() { @@ -597,11 +596,11 @@ func getCorrelationWorkspaces(ctx context.Context, baseResource string, resource res, err := azMonService.HTTPClient.Do(req) if err != nil { - return AzureCorrelationAPIResponse{}, errorsource.DownstreamError(err, false) + return AzureCorrelationAPIResponse{}, backend.DownstreamError(err) } body, err := io.ReadAll(res.Body) if err != nil { - return AzureCorrelationAPIResponse{}, errorsource.DownstreamError(err, false) + return AzureCorrelationAPIResponse{}, backend.DownstreamError(err) } defer func() { @@ -611,7 +610,7 @@ func getCorrelationWorkspaces(ctx context.Context, baseResource string, resource }() if res.StatusCode/100 != 2 { - return AzureCorrelationAPIResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, body: %s", res.Status, string(body)), false) + return AzureCorrelationAPIResponse{}, utils.CreateResponseErrorFromStatusCode(res.StatusCode, res.Status, body) } var data AzureCorrelationAPIResponse d := json.NewDecoder(bytes.NewReader(body)) @@ -675,7 +674,7 @@ func (e *AzureLogAnalyticsDatasource) unmarshalResponse(res *http.Response) (Azu }() if res.StatusCode/100 != 2 { - return AzureLogAnalyticsResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, body: %s", res.Status, string(body)), false) + return AzureLogAnalyticsResponse{}, utils.CreateResponseErrorFromStatusCode(res.StatusCode, res.Status, body) } var data AzureLogAnalyticsResponse diff --git a/pkg/tsdb/azuremonitor/loganalytics/utils.go b/pkg/tsdb/azuremonitor/loganalytics/utils.go index 820f941c16b..093d5dc41da 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/utils.go +++ b/pkg/tsdb/azuremonitor/loganalytics/utils.go @@ -7,8 +7,8 @@ import ( "strings" "time" + "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" - "github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery" ) @@ -47,18 +47,18 @@ func AddConfigLinks(frame data.Frame, dl string, title *string) data.Frame { // 4. the ds toggle is set to true func meetsBasicLogsCriteria(resources []string, fromAlert bool, basicLogsEnabled bool) (bool, error) { if fromAlert { - return false, errorsource.DownstreamError(fmt.Errorf("basic Logs queries cannot be used for alerts"), false) + return false, backend.DownstreamError(fmt.Errorf("basic Logs queries cannot be used for alerts")) } if len(resources) != 1 { - return false, errorsource.DownstreamError(fmt.Errorf("basic logs queries cannot be run against multiple resources"), false) + return false, backend.DownstreamError(fmt.Errorf("basic logs queries cannot be run against multiple resources")) } if !strings.Contains(strings.ToLower(resources[0]), "microsoft.operationalinsights/workspaces") { - return false, errorsource.DownstreamError(fmt.Errorf("basic logs queries may only be run against Log Analytics workspaces"), false) + return false, backend.DownstreamError(fmt.Errorf("basic logs queries may only be run against Log Analytics workspaces")) } if !basicLogsEnabled { - return false, errorsource.DownstreamError(fmt.Errorf("basic Logs queries are disabled for this data source"), false) + return false, backend.DownstreamError(fmt.Errorf("basic Logs queries are disabled for this data source")) } return true, nil diff --git a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go index 8c85239f9d6..88e1c72db63 100644 --- a/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go +++ b/pkg/tsdb/azuremonitor/metrics/azuremonitor-datasource.go @@ -17,7 +17,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "github.com/grafana/grafana-plugin-sdk-go/data" - "github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -25,6 +24,7 @@ import ( "github.com/grafana/grafana/pkg/tsdb/azuremonitor/loganalytics" azTime "github.com/grafana/grafana/pkg/tsdb/azuremonitor/time" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" + "github.com/grafana/grafana/pkg/tsdb/azuremonitor/utils" ) // AzureMonitorDatasource calls the Azure Monitor API - one of the four API's supported @@ -55,12 +55,12 @@ func (e *AzureMonitorDatasource) ExecuteTimeSeriesQuery(ctx context.Context, ori for _, query := range originalQueries { azureQuery, err := e.buildQuery(query, dsInfo) if err != nil { - errorsource.AddErrorToResponse(query.RefID, result, err) + result.Responses[query.RefID] = backend.ErrorResponseWithErrorSource(err) continue } res, err := e.executeQuery(ctx, azureQuery, dsInfo, client, url) if err != nil { - errorsource.AddErrorToResponse(query.RefID, result, err) + result.Responses[query.RefID] = backend.ErrorResponseWithErrorSource(err) continue } result.Responses[query.RefID] = *res @@ -284,7 +284,7 @@ func (e *AzureMonitorDatasource) retrieveSubscriptionDetails(cli *http.Client, c } if res.StatusCode/100 != 2 { - return "", errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, error: %s", res.Status, string(body)), false) + return "", utils.CreateResponseErrorFromStatusCode(res.StatusCode, res.Status, body) } var data types.SubscriptionsResponse @@ -321,7 +321,7 @@ func (e *AzureMonitorDatasource) executeQuery(ctx context.Context, query *types. res, err := cli.Do(req) if err != nil { - return nil, errorsource.DownstreamError(err, false) + return nil, backend.DownstreamError(err) } defer func() { @@ -366,7 +366,7 @@ func (e *AzureMonitorDatasource) unmarshalResponse(res *http.Response) (types.Az } if res.StatusCode/100 != 2 { - return types.AzureMonitorResponse{}, errorsource.SourceError(backend.ErrorSourceFromHTTPStatus(res.StatusCode), fmt.Errorf("request failed, status: %s, body: %s", res.Status, string(body)), false) + return types.AzureMonitorResponse{}, utils.CreateResponseErrorFromStatusCode(res.StatusCode, res.Status, body) } var data types.AzureMonitorResponse diff --git a/pkg/tsdb/azuremonitor/utils/utils.go b/pkg/tsdb/azuremonitor/utils/utils.go index bcf9026f533..fd36558886d 100644 --- a/pkg/tsdb/azuremonitor/utils/utils.go +++ b/pkg/tsdb/azuremonitor/utils/utils.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" + "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" @@ -78,3 +79,11 @@ func ApplySourceFromError(errorMessage error, err error) error { } return errorMessage } + +func CreateResponseErrorFromStatusCode(statusCode int, status string, body []byte) error { + statusErr := fmt.Errorf("request failed, status: %s, body: %s", status, string(body)) + if backend.ErrorSourceFromHTTPStatus(statusCode) == backend.ErrorSourceDownstream { + return backend.DownstreamError(statusErr) + } + return backend.PluginError(statusErr) +} diff --git a/pkg/tsdb/azuremonitor/utils/utils_test.go b/pkg/tsdb/azuremonitor/utils/utils_test.go new file mode 100644 index 00000000000..f339aaecb55 --- /dev/null +++ b/pkg/tsdb/azuremonitor/utils/utils_test.go @@ -0,0 +1,58 @@ +package utils + +import ( + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/stretchr/testify/assert" +) + +func TestCreateResponseErrorFromStatusCode(t *testing.T) { + tests := []struct { + name string + statusCode int + status string + body []byte + expectedErrMessage string + expectedType backend.ErrorSource + }{ + { + name: "Downstream error for 500 status", + statusCode: 500, + status: "500 Internal Server Error", + body: []byte("body bytes"), + expectedErrMessage: "request failed, status: 500 Internal Server Error, body: body bytes", + expectedType: backend.ErrorSourceDownstream, + }, + { + name: "Plugin error for 501 status", + statusCode: 501, + status: "501 Not Implemented", + body: []byte("body bytes"), + expectedErrMessage: "request failed, status: 501 Not Implemented, body: body bytes", + expectedType: backend.ErrorSourcePlugin, + }, + { + name: "Downstream error for 502 status", + statusCode: 502, + status: "502 Gateway Error", + body: []byte("body bytes"), + expectedErrMessage: "request failed, status: 502 Gateway Error, body: body bytes", + expectedType: backend.ErrorSourceDownstream, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := CreateResponseErrorFromStatusCode(tt.statusCode, tt.status, tt.body) + assert.Error(t, err) + // Check if error is of type ErrorWithSource + errorWithSource, ok := err.(backend.ErrorWithSource) + assert.True(t, ok, "error should implement ErrorWithSource") + + // Validate the source of the error + assert.Equal(t, tt.expectedType, errorWithSource.ErrorSource()) + assert.Contains(t, err.Error(), tt.expectedErrMessage) + }) + } +}