diff --git a/pkg/apimachinery/errutil/errors.go b/pkg/apimachinery/errutil/errors.go index e8bc0e4b0f2..d91749496b9 100644 --- a/pkg/apimachinery/errutil/errors.go +++ b/pkg/apimachinery/errutil/errors.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" errorsK8s "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -356,6 +357,9 @@ type Error struct { LogLevel LogLevel // Source identifies from where the error originates. Source Source + // WithContactSupportErrorMessage indicates whether we should append a message + // to the public message advising the user to contact support + WithContactSupportErrorMessage bool } // MarshalJSON returns an error, we do not want raw [Error]s being @@ -458,6 +462,13 @@ func (e Error) Public() PublicError { } } + if e.WithContactSupportErrorMessage { + if !strings.HasSuffix(message, ".") { + message += "." + } + message += " Please contact support if the issue persists." + } + return PublicError{ StatusCode: e.Reason.Status().HTTPStatus(), MessageID: e.MessageID, @@ -470,3 +481,9 @@ func (e Error) Public() PublicError { func (p PublicError) Error() string { return fmt.Sprintf("[%s] %s", p.MessageID, p.Message) } + +// WithSupportContact creates a new Error with WithContactSupportErrorMessage set to true +func (e Error) WithContactSupportMessage() Error { + e.WithContactSupportErrorMessage = true + return e +} diff --git a/pkg/apimachinery/errutil/errors_test.go b/pkg/apimachinery/errutil/errors_test.go index 5e49827a32b..bd54a8730d5 100644 --- a/pkg/apimachinery/errutil/errors_test.go +++ b/pkg/apimachinery/errutil/errors_test.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "reflect" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -72,3 +73,54 @@ func TestBase_Is(t *testing.T) { }) } } + +func TestError_WithContactSupportMessage(t *testing.T) { + tests := []struct { + name string + error Error + expectedSuffix string + }{ + { + name: "should append contact support message when WithContactSupportMessage is called", + error: Error{ + Reason: StatusInternal, + MessageID: "test.error", + LogMessage: "test error message", + PublicMessage: "Something went wrong", + }.WithContactSupportMessage(), + expectedSuffix: "Please contact support if the issue persists.", + }, + { + name: "should not append contact support message when WithContactSupportMessage is not called", + error: Error{ + Reason: StatusInternal, + MessageID: "test.error", + LogMessage: "test error message", + PublicMessage: "Something went wrong", + }, + expectedSuffix: "", + }, + { + name: "should handle message with existing period", + error: Error{ + Reason: StatusInternal, + MessageID: "test.error", + LogMessage: "test error message", + PublicMessage: "Something went wrong.", + }.WithContactSupportMessage(), + expectedSuffix: "Please contact support if the issue persists.", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + public := tt.error.Public() + message := public.Message + if tt.expectedSuffix != "" { + assert.True(t, strings.HasSuffix(message, tt.expectedSuffix)) + } else { + assert.NotContains(t, message, "Please contact support if the issue persists.") + } + }) + } +} diff --git a/pkg/plugins/backendplugin/grpcplugin/client_v2.go b/pkg/plugins/backendplugin/grpcplugin/client_v2.go index 78d7b0d6dcc..089d8f42dd0 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client_v2.go +++ b/pkg/plugins/backendplugin/grpcplugin/client_v2.go @@ -14,6 +14,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "github.com/grafana/grafana/pkg/extensions/datasource/dsrunner/pluginmetrics" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" "github.com/grafana/grafana/pkg/plugins/log" @@ -173,7 +174,13 @@ func (c *ClientV2) QueryData(ctx context.Context, req *backend.QueryDataRequest) } if status.Code(err) == codes.Unavailable { - return nil, plugins.ErrPluginGrpcConnectionUnavailableBase.Errorf("%v", err) + connectionErr := plugins.ErrPluginGrpcConnectionUnavailableBase.Errorf("%v", err) + // Check if the plugin is running in a cloud environment + _, hasStackId := pluginmetrics.StackIDFromContext(ctx) + if hasStackId { + return nil,connectionErr.WithContactSupportMessage() + } + return nil, connectionErr } if status.Code(err) == codes.ResourceExhausted {