From 9f9e960ab846a8d6aa6c3040082982d4e7280b84 Mon Sep 17 00:00:00 2001 From: Ryan Albert <42415738+ryan-timothy-albert@users.noreply.github.com> Date: Wed, 19 Feb 2025 15:45:58 -0800 Subject: [PATCH] feat: test report links output from interactive test cmd (#1271) --- internal/run/testing.go | 2 +- internal/testcmd/runner.go | 30 ++++++++++++++++++++++++++++-- internal/testcmd/testing.go | 26 ++++++++++++++++++++------ 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/internal/run/testing.go b/internal/run/testing.go index cbca1349..059d5588 100644 --- a/internal/run/testing.go +++ b/internal/run/testing.go @@ -61,7 +61,7 @@ func (w Workflow) runTesting(ctx context.Context, workflowTargetName string, tar return err } - if err := testcmd.ExecuteTargetTesting(testingCtx, generator, target, workflowTargetName, outputDir); err != nil { + if _, err := testcmd.ExecuteTargetTesting(testingCtx, generator, target, workflowTargetName, outputDir); err != nil { return fmt.Errorf("error running workflow target %s (%s) testing: %w", workflowTargetName, target.Target, err) } diff --git a/internal/testcmd/runner.go b/internal/testcmd/runner.go index 8b6975eb..8f81653a 100644 --- a/internal/testcmd/runner.go +++ b/internal/testcmd/runner.go @@ -8,11 +8,13 @@ import ( "path/filepath" "strings" + "github.com/charmbracelet/lipgloss" "github.com/speakeasy-api/openapi-generation/v2/pkg/generate" "github.com/speakeasy-api/sdk-gen-config/workflow" "github.com/speakeasy-api/speakeasy-core/auth" "github.com/speakeasy-api/speakeasy/internal/charm/styles" "github.com/speakeasy-api/speakeasy/internal/env" + "github.com/speakeasy-api/speakeasy/internal/links" "github.com/speakeasy-api/speakeasy/internal/log" "github.com/speakeasy-api/speakeasy/internal/utils" "github.com/speakeasy-api/speakeasy/internal/workflowTracking" @@ -42,6 +44,8 @@ type Runner struct { // Enhanced CLI visualization tracker for the workflow. workflowTracker *workflowTracking.WorkflowStep + + testReportURLs []string } // NewRunner creates a new Runner with the given options. @@ -113,6 +117,22 @@ func (r *Runner) RunWithVisualization(ctx context.Context) error { logger.PrintlnUnstyled(styles.MakeSection("Workflow testing run logs", output, styles.Colors.Grey)) } + if len(r.testReportURLs) > 0 { + msg := "view your test report here" + if len(r.testReportURLs) > 1 { + msg = "view your test reports here" + } + shortenedURLs := make([]string, 0, len(r.testReportURLs)) + for _, url := range r.testReportURLs { + shortenedURLs = append(shortenedURLs, links.Shorten(ctx, url)) + } + if runErr != nil { + logger.Println("\n\n" + styles.RenderErrorMessage("Tests Failed - "+msg, lipgloss.Center, shortenedURLs...)) + } else { + logger.Println("\n\n" + styles.RenderSuccessMessage("Tests Succeeded - "+msg, shortenedURLs...)) + } + } + return errors.Join(err, runErr) } @@ -231,8 +251,14 @@ func (r *Runner) runSingleWorkflowTargetTesting(ctx context.Context, workflowTar return err } - if err := ExecuteTargetTesting(testingCtx, generator, workflowTarget, workflowTargetName, outputDir); err != nil { - return fmt.Errorf("error running workflow target %s (%s) testing: %w", workflowTargetName, workflowTarget.Target, err) + testReportURL, err := ExecuteTargetTesting(testingCtx, generator, workflowTarget, workflowTargetName, outputDir) + + if testReportURL != "" { + r.testReportURLs = append(r.testReportURLs, testReportURL) + } + + if err != nil { + return fmt.Errorf("error running tests for target %s (%s): %w", workflowTargetName, workflowTarget.Target, err) } targetTracker.SucceedWorkflow() diff --git a/internal/testcmd/testing.go b/internal/testcmd/testing.go index b307137e..2751898b 100644 --- a/internal/testcmd/testing.go +++ b/internal/testcmd/testing.go @@ -2,6 +2,7 @@ package testcmd import ( "context" + "fmt" "os" "slices" "strings" @@ -11,10 +12,12 @@ import ( "github.com/speakeasy-api/sdk-gen-config/workflow" "github.com/speakeasy-api/sdk-gen-config/workspace" "github.com/speakeasy-api/speakeasy-client-sdk-go/v3/pkg/models/shared" + "github.com/speakeasy-api/speakeasy-core/auth" "github.com/speakeasy-api/speakeasy-core/events" ) -func ExecuteTargetTesting(ctx context.Context, generator *generate.Generator, workflowTarget workflow.Target, targetName, outDir string) error { +func ExecuteTargetTesting(ctx context.Context, generator *generate.Generator, workflowTarget workflow.Target, targetName, outDir string) (string, error) { + testReportURL := "" err := events.Telemetry(ctx, shared.InteractionTypeTest, func(ctx context.Context, event *shared.CliEvent) error { event.GenerateTargetName = &targetName if prReference := os.Getenv("GH_PULL_REQUEST"); prReference != "" { @@ -24,35 +27,46 @@ func ExecuteTargetTesting(ctx context.Context, generator *generate.Generator, wo err := generator.RunTargetTesting(ctx, workflowTarget.Target, outDir) - populateRawTestReport(outDir, event) - populateGenLockDetails(outDir, event) + foundTestReport := populateRawTestReport(outDir, event) + genLockID := populateGenLockDetails(outDir, event) + orgSlug := auth.GetOrgSlugFromContext(ctx) + workspaceSlug := auth.GetWorkspaceSlugFromContext(ctx) + if foundTestReport && genLockID != "" { + testReportURL = fmt.Sprintf("https://app.speakeasy.com/org/%s/%s/targets/%s/tests/%s", orgSlug, workspaceSlug, genLockID, event.ExecutionID) + } return err }) - return err + return testReportURL, err } func CheckTestingAccountType(accountType shared.AccountType) bool { return slices.Contains([]shared.AccountType{shared.AccountTypeEnterprise, shared.AccountTypeBusiness}, accountType) } -func populateRawTestReport(outDir string, event *shared.CliEvent) { +func populateRawTestReport(outDir string, event *shared.CliEvent) bool { + foundTestReport := false if res, _ := workspace.FindWorkspace(outDir, workspace.FindWorkspaceOptions{ FindFile: "reports/tests.xml", Recursive: true, }); res != nil && len(res.Data) > 0 { testReportContent := string(res.Data) event.TestReportRaw = &testReportContent + foundTestReport = true } + return foundTestReport } -func populateGenLockDetails(outDir string, event *shared.CliEvent) { +func populateGenLockDetails(outDir string, event *shared.CliEvent) string { if cfg, err := config.Load(outDir); err == nil && cfg.LockFile != nil { // The generator marks a testing run's version as internal to avoid a bump // So we pull current version of the SDK from the lock file currentVersion := cfg.LockFile.Management.ReleaseVersion event.GenerateVersion = ¤tVersion + return cfg.LockFile.ID } + + return "" } func reformatPullRequestURL(url string) string {