From b31a4a306b96389a5d40d1ecb995875fc6d8e47f Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Tue, 3 Oct 2023 17:59:45 +0300 Subject: [PATCH] feat: add dashbaord uris for executions --- api/v1/testkube.yaml | 4 ++ .../commands/common/render/common.go | 37 ++++++++++++++++++- .../commands/common/render/obj.go | 8 +++- .../commands/tests/executions.go | 2 +- .../commands/tests/renderer/execution_obj.go | 5 ++- .../commands/tests/renderer/test_obj.go | 3 +- cmd/kubectl-testkube/commands/tests/run.go | 2 +- .../testsources/renderer/testsource_obj.go | 3 +- .../commands/testsuites/common.go | 13 ++++++- .../testsuites/renderer/execution_obj.go | 10 ++++- .../testsuites/renderer/testsuite_obj.go | 3 +- .../commands/testsuites/run.go | 2 +- .../commands/testsuites/watch.go | 2 +- internal/app/api/v1/handlers.go | 1 + internal/app/api/v1/server.go | 2 + pkg/api/v1/testkube/model_server_info.go | 2 + 16 files changed, 85 insertions(+), 14 deletions(-) diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml index b76fd828f10..68305997565 100644 --- a/api/v1/testkube.yaml +++ b/api/v1/testkube.yaml @@ -4478,6 +4478,10 @@ components: type: string description: helm chart version example: "1.4.14" + dashboardUri: + type: string + description: dashboard uri + example: "http://localhost:8080" Repository: description: repository representation for tests in git repositories diff --git a/cmd/kubectl-testkube/commands/common/render/common.go b/cmd/kubectl-testkube/commands/common/render/common.go index 9f73160adee..ad957f27881 100644 --- a/cmd/kubectl-testkube/commands/common/render/common.go +++ b/cmd/kubectl-testkube/commands/common/render/common.go @@ -2,11 +2,13 @@ package render import ( "encoding/json" + "fmt" "io" "os" "gopkg.in/yaml.v2" + "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/ui" "github.com/kubeshop/testkube/pkg/utils" @@ -21,7 +23,7 @@ const ( OutputPretty OutputType = "pretty" ) -type CliObjRenderer func(ui *ui.UI, obj interface{}) error +type CliObjRenderer func(client client.Client, ui *ui.UI, obj interface{}) error func RenderJSON(obj interface{}, w io.Writer) error { return json.NewEncoder(w).Encode(obj) @@ -63,7 +65,7 @@ func RenderPrettyList(obj ui.TableData, w io.Writer) error { return nil } -func RenderExecutionResult(execution *testkube.Execution, logsOnly bool) { +func RenderExecutionResult(client client.Client, execution *testkube.Execution, logsOnly bool) { result := execution.ExecutionResult if result == nil { @@ -84,6 +86,11 @@ func RenderExecutionResult(execution *testkube.Execution, logsOnly bool) { if !logsOnly { duration := execution.EndTime.Sub(execution.StartTime) ui.Success("Test execution completed with success in " + duration.String()) + + info, err := client.GetServerInfo() + ui.ExitOnError("getting server info", err) + + PrintExecutionURIs(execution, info.DashboardUri) } case result.IsAborted(): @@ -99,6 +106,11 @@ func RenderExecutionResult(execution *testkube.Execution, logsOnly bool) { ui.UseStderr() ui.Warn("Test execution failed:\n") ui.Errf(result.ErrorMessage) + + info, err := client.GetServerInfo() + ui.ExitOnError("getting server info", err) + + PrintExecutionURIs(execution, info.DashboardUri) } ui.Info(result.Output) @@ -118,3 +130,24 @@ func RenderExecutionResult(execution *testkube.Execution, logsOnly bool) { } } + +func PrintExecutionURIs(execution *testkube.Execution, dashboardURI string) { + ui.NL() + ui.Link("Test URI:", fmt.Sprintf("%s/tests/%s", dashboardURI, execution.TestName)) + ui.Link("Test Execution URI:", fmt.Sprintf("%s/tests/%s/executions/%s", dashboardURI, + execution.TestName, execution.Id)) + ui.NL() +} + +func PrintTestSuiteExecutionURIs(execution *testkube.TestSuiteExecution, dashboardURI string) { + ui.NL() + testSuiteName := "" + if execution.TestSuite != nil { + testSuiteName = execution.TestSuite.Name + } + + ui.Link("Test Suite URI:", fmt.Sprintf("%s/test-suites/%s", dashboardURI, testSuiteName)) + ui.Link("Test Suite Execution URI:", fmt.Sprintf("%s/test-suites/%s/executions/%s", dashboardURI, + testSuiteName, execution.Id)) + ui.NL() +} diff --git a/cmd/kubectl-testkube/commands/common/render/obj.go b/cmd/kubectl-testkube/commands/common/render/obj.go index cbb5cf56e94..f1a23fc7d0f 100644 --- a/cmd/kubectl-testkube/commands/common/render/obj.go +++ b/cmd/kubectl-testkube/commands/common/render/obj.go @@ -6,6 +6,7 @@ import ( "github.com/spf13/cobra" + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" "github.com/kubeshop/testkube/pkg/ui" ) @@ -15,7 +16,12 @@ func Obj(cmd *cobra.Command, obj interface{}, w io.Writer, renderer ...CliObjRen switch outputType { case OutputPretty: if len(renderer) > 0 { // if custom renderer is set render using custom pretty renderer - return renderer[0](ui.NewUI(ui.Verbose, w), obj) + client, _, err := common.GetClient(cmd) + if err != nil { + return err + } + + return renderer[0](client, ui.NewUI(ui.Verbose, w), obj) } return RenderYaml(obj, w) // fallback to yaml case OutputYAML: diff --git a/cmd/kubectl-testkube/commands/tests/executions.go b/cmd/kubectl-testkube/commands/tests/executions.go index aec8fb9f80c..3e0b9719a51 100644 --- a/cmd/kubectl-testkube/commands/tests/executions.go +++ b/cmd/kubectl-testkube/commands/tests/executions.go @@ -35,7 +35,7 @@ func NewGetExecutionCmd() *cobra.Command { ui.ExitOnError("getting test execution: "+executionID, err) if logsOnly { - render.RenderExecutionResult(&execution, logsOnly) + render.RenderExecutionResult(client, &execution, logsOnly) } else { err = render.Obj(cmd, execution, os.Stdout, renderer.ExecutionRenderer) ui.ExitOnError("rendering execution", err) diff --git a/cmd/kubectl-testkube/commands/tests/renderer/execution_obj.go b/cmd/kubectl-testkube/commands/tests/renderer/execution_obj.go index be3320eae4a..5b48a046372 100644 --- a/cmd/kubectl-testkube/commands/tests/renderer/execution_obj.go +++ b/cmd/kubectl-testkube/commands/tests/renderer/execution_obj.go @@ -5,11 +5,12 @@ import ( "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/render" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/renderer" + "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/ui" ) -func ExecutionRenderer(ui *ui.UI, obj interface{}) error { +func ExecutionRenderer(client client.Client, ui *ui.UI, obj interface{}) error { execution, ok := obj.(testkube.Execution) if !ok { return fmt.Errorf("can't render execution, expecrted obj to be testkube.Execution but got '%T'", obj) @@ -56,7 +57,7 @@ func ExecutionRenderer(ui *ui.UI, obj interface{}) error { ui.Warn(" Auth type: ", execution.Content.Repository.AuthType) } - render.RenderExecutionResult(&execution, false) + render.RenderExecutionResult(client, &execution, false) ui.NL() diff --git a/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go b/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go index 76882ce7242..cf4af35011f 100644 --- a/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go +++ b/cmd/kubectl-testkube/commands/tests/renderer/test_obj.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/renderer" + "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/ui" ) @@ -14,7 +15,7 @@ type mountParams struct { path string } -func TestRenderer(ui *ui.UI, obj interface{}) error { +func TestRenderer(client client.Client, ui *ui.UI, obj interface{}) error { test, ok := obj.(testkube.Test) if !ok { return fmt.Errorf("can't use '%T' as testkube.Test in RenderObj for test", obj) diff --git a/cmd/kubectl-testkube/commands/tests/run.go b/cmd/kubectl-testkube/commands/tests/run.go index 1b03d41e2bc..d298c2be638 100644 --- a/cmd/kubectl-testkube/commands/tests/run.go +++ b/cmd/kubectl-testkube/commands/tests/run.go @@ -256,7 +256,7 @@ func NewRunTestCmd() *cobra.Command { ui.ExitOnError("getting recent execution data id:"+execution.Id, err) } - render.RenderExecutionResult(&execution, false) + render.RenderExecutionResult(client, &execution, false) if execution.Id != "" { if downloadArtifactsEnabled { diff --git a/cmd/kubectl-testkube/commands/testsources/renderer/testsource_obj.go b/cmd/kubectl-testkube/commands/testsources/renderer/testsource_obj.go index a3062143f37..b4dfcb905af 100644 --- a/cmd/kubectl-testkube/commands/testsources/renderer/testsource_obj.go +++ b/cmd/kubectl-testkube/commands/testsources/renderer/testsource_obj.go @@ -3,11 +3,12 @@ package renderer import ( "fmt" + "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/ui" ) -func TestSourceRenderer(ui *ui.UI, obj interface{}) error { +func TestSourceRenderer(client client.Client, ui *ui.UI, obj interface{}) error { testSource, ok := obj.(testkube.TestSource) if !ok { return fmt.Errorf("can't use '%T' as testkube.TestSource in RenderObj for test source", obj) diff --git a/cmd/kubectl-testkube/commands/testsuites/common.go b/cmd/kubectl-testkube/commands/testsuites/common.go index ca7d0b31445..2203f8439c4 100644 --- a/cmd/kubectl-testkube/commands/testsuites/common.go +++ b/cmd/kubectl-testkube/commands/testsuites/common.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/render" apiclientv1 "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/ui" @@ -37,7 +38,7 @@ func printExecution(execution testkube.TestSuiteExecution, startTime time.Time) ui.NL() } -func uiPrintExecutionStatus(execution testkube.TestSuiteExecution) { +func uiPrintExecutionStatus(client apiclientv1.Client, execution testkube.TestSuiteExecution) { if execution.Status == nil { return } @@ -52,9 +53,19 @@ func uiPrintExecutionStatus(execution testkube.TestSuiteExecution) { case execution.IsPassed(): ui.Success("Test Suite execution completed with sucess in " + execution.Duration) + info, err := client.GetServerInfo() + ui.ExitOnError("getting server info", err) + + render.PrintTestSuiteExecutionURIs(&execution, info.DashboardUri) + case execution.IsFailed(): ui.UseStderr() ui.Errf("Test Suite execution failed") + + info, err := client.GetServerInfo() + ui.ExitOnError("getting server info", err) + + render.PrintTestSuiteExecutionURIs(&execution, info.DashboardUri) os.Exit(1) } diff --git a/cmd/kubectl-testkube/commands/testsuites/renderer/execution_obj.go b/cmd/kubectl-testkube/commands/testsuites/renderer/execution_obj.go index 965b0b732eb..24e910010d6 100644 --- a/cmd/kubectl-testkube/commands/testsuites/renderer/execution_obj.go +++ b/cmd/kubectl-testkube/commands/testsuites/renderer/execution_obj.go @@ -4,11 +4,13 @@ import ( "fmt" "os" + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/render" + "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/ui" ) -func TestSuiteExecutionRenderer(ui *ui.UI, obj interface{}) error { +func TestSuiteExecutionRenderer(client client.Client, ui *ui.UI, obj interface{}) error { execution, ok := obj.(testkube.TestSuiteExecution) if !ok { return fmt.Errorf("can't render execution, expecrted obj to be testkube.Execution but got '%T'", obj) @@ -26,6 +28,12 @@ func TestSuiteExecutionRenderer(ui *ui.UI, obj interface{}) error { ui.Warn("Type: ", execution.RunningContext.Type_) ui.Warn("Context:", execution.RunningContext.Context) } + + info, err := client.GetServerInfo() + ui.ExitOnError("getting server info", err) + + render.PrintTestSuiteExecutionURIs(&execution, info.DashboardUri) + ui.Table(execution, os.Stdout) ui.NL() diff --git a/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go b/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go index c0540326f3d..f9c62ff7d69 100644 --- a/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go +++ b/cmd/kubectl-testkube/commands/testsuites/renderer/testsuite_obj.go @@ -5,11 +5,12 @@ import ( "strings" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/renderer" + "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/ui" ) -func TestSuiteRenderer(ui *ui.UI, obj interface{}) error { +func TestSuiteRenderer(client client.Client, ui *ui.UI, obj interface{}) error { ts, ok := obj.(testkube.TestSuite) if !ok { return fmt.Errorf("can't use '%T' as testkube.TestSuite in RenderObj for test suite", obj) diff --git a/cmd/kubectl-testkube/commands/testsuites/run.go b/cmd/kubectl-testkube/commands/testsuites/run.go index 33700e2595c..7324b79824e 100644 --- a/cmd/kubectl-testkube/commands/testsuites/run.go +++ b/cmd/kubectl-testkube/commands/testsuites/run.go @@ -148,7 +148,7 @@ func NewRunTestSuiteCmd() *cobra.Command { printExecution(execution, startTime) ui.ExitOnError("getting recent execution data id:"+execution.Id, err) - uiPrintExecutionStatus(execution) + uiPrintExecutionStatus(client, execution) uiShellTestSuiteGetCommandBlock(execution.Id) if execution.Id != "" { diff --git a/cmd/kubectl-testkube/commands/testsuites/watch.go b/cmd/kubectl-testkube/commands/testsuites/watch.go index e93d73ce058..0f1a141164e 100644 --- a/cmd/kubectl-testkube/commands/testsuites/watch.go +++ b/cmd/kubectl-testkube/commands/testsuites/watch.go @@ -36,7 +36,7 @@ func NewWatchTestSuiteExecutionCmd() *cobra.Command { printExecution(execution, startTime) ui.ExitOnError("getting recent execution data id:"+execution.Id, err) - uiPrintExecutionStatus(execution) + uiPrintExecutionStatus(client, execution) uiShellTestSuiteGetCommandBlock(execution.Id) }, } diff --git a/internal/app/api/v1/handlers.go b/internal/app/api/v1/handlers.go index e2bf99af394..3db757e5d1c 100644 --- a/internal/app/api/v1/handlers.go +++ b/internal/app/api/v1/handlers.go @@ -70,6 +70,7 @@ func (s *TestkubeAPI) InfoHandler() fiber.Handler { EnvId: os.Getenv(cloudEnvIdEnvName), OrgId: os.Getenv(cloudOrgIdEnvName), HelmchartVersion: s.helmchartVersion, + DashboardUri: s.dashboardURI, }) } } diff --git a/internal/app/api/v1/server.go b/internal/app/api/v1/server.go index 7770c8fa781..a2844ec173c 100644 --- a/internal/app/api/v1/server.go +++ b/internal/app/api/v1/server.go @@ -127,6 +127,7 @@ func NewTestkubeAPI( graphqlPort: graphqlPort, artifactsStorage: artifactsStorage, TemplatesClient: templatesClient, + dashboardURI: dashboardURI, helmchartVersion: helmchartVersion, mode: mode, eventsBus: eventsBus, @@ -182,6 +183,7 @@ type TestkubeAPI struct { graphqlPort string artifactsStorage storage.ArtifactsStorage TemplatesClient *templatesclientv1.TemplatesClient + dashboardURI string helmchartVersion string mode string eventsBus bus.Bus diff --git a/pkg/api/v1/testkube/model_server_info.go b/pkg/api/v1/testkube/model_server_info.go index a29ce4fb795..38e0ff0051f 100644 --- a/pkg/api/v1/testkube/model_server_info.go +++ b/pkg/api/v1/testkube/model_server_info.go @@ -25,4 +25,6 @@ type ServerInfo struct { EnvId string `json:"envId,omitempty"` // helm chart version HelmchartVersion string `json:"helmchartVersion,omitempty"` + // dashboard uri + DashboardUri string `json:"dashboardUri,omitempty"` }