diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 42f875c8881..4964d71f064 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -7,6 +7,7 @@ ### CLI ### Bundles +* `bundle run` now prints the modern job run URL (`/jobs//runs/`) so that non-admin users permitted to view the run are taken to the run instead of the workspace homepage. ### Dependency updates diff --git a/acceptance/bundle/deploy/spark-jar-task/output.txt b/acceptance/bundle/deploy/spark-jar-task/output.txt index 3b556fbb60f..be85c64ad0a 100644 --- a/acceptance/bundle/deploy/spark-jar-task/output.txt +++ b/acceptance/bundle/deploy/spark-jar-task/output.txt @@ -8,7 +8,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run jar_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[NUMID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Spark Jar Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Spark Jar Job [UNIQUE_NAME]" TERMINATED SUCCESS diff --git a/acceptance/bundle/integration_whl/base/output.txt b/acceptance/bundle/integration_whl/base/output.txt index 13343edcb7b..3a335ebbe51 100644 --- a/acceptance/bundle/integration_whl/base/output.txt +++ b/acceptance/bundle/integration_whl/base/output.txt @@ -39,7 +39,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" TERMINATED SUCCESS @@ -57,7 +57,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" TERMINATED SUCCESS diff --git a/acceptance/bundle/integration_whl/custom_params/output.txt b/acceptance/bundle/integration_whl/custom_params/output.txt index b5b5af78091..763f169e73f 100644 --- a/acceptance/bundle/integration_whl/custom_params/output.txt +++ b/acceptance/bundle/integration_whl/custom_params/output.txt @@ -39,7 +39,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job --python-params param1,param2 -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" TERMINATED SUCCESS diff --git a/acceptance/bundle/integration_whl/interactive_cluster/output.txt b/acceptance/bundle/integration_whl/interactive_cluster/output.txt index 57beb13d026..63ff83e67e4 100644 --- a/acceptance/bundle/integration_whl/interactive_cluster/output.txt +++ b/acceptance/bundle/integration_whl/interactive_cluster/output.txt @@ -39,7 +39,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" TERMINATED SUCCESS @@ -57,7 +57,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" TERMINATED SUCCESS diff --git a/acceptance/bundle/integration_whl/interactive_cluster_dynamic_version/output.txt b/acceptance/bundle/integration_whl/interactive_cluster_dynamic_version/output.txt index 64e5907450d..ea37514510b 100644 --- a/acceptance/bundle/integration_whl/interactive_cluster_dynamic_version/output.txt +++ b/acceptance/bundle/integration_whl/interactive_cluster_dynamic_version/output.txt @@ -8,7 +8,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" TERMINATED SUCCESS @@ -26,7 +26,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" TERMINATED SUCCESS diff --git a/acceptance/bundle/integration_whl/interactive_single_user/output.txt b/acceptance/bundle/integration_whl/interactive_single_user/output.txt index f45e1b9ce42..35ce77c6df0 100644 --- a/acceptance/bundle/integration_whl/interactive_single_user/output.txt +++ b/acceptance/bundle/integration_whl/interactive_single_user/output.txt @@ -39,7 +39,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" TERMINATED SUCCESS @@ -54,7 +54,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" TERMINATED SUCCESS diff --git a/acceptance/bundle/integration_whl/serverless/output.txt b/acceptance/bundle/integration_whl/serverless/output.txt index f09d77ad397..b23c80e491f 100644 --- a/acceptance/bundle/integration_whl/serverless/output.txt +++ b/acceptance/bundle/integration_whl/serverless/output.txt @@ -10,7 +10,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "My Wheel Job" RUNNING [TIMESTAMP] "My Wheel Job" TERMINATED SUCCESS @@ -28,7 +28,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "My Wheel Job" RUNNING [TIMESTAMP] "My Wheel Job" TERMINATED SUCCESS diff --git a/acceptance/bundle/integration_whl/serverless_custom_params/output.txt b/acceptance/bundle/integration_whl/serverless_custom_params/output.txt index d964055d869..3bb59861afb 100644 --- a/acceptance/bundle/integration_whl/serverless_custom_params/output.txt +++ b/acceptance/bundle/integration_whl/serverless_custom_params/output.txt @@ -8,7 +8,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job With Environments [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job With Environments [UNIQUE_NAME]" TERMINATED SUCCESS @@ -17,7 +17,7 @@ Got arguments: ['my_test_code', 'one', 'two'] >>> [CLI] bundle run some_other_job --python-params=param1,param2 -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job With Environments [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job With Environments [UNIQUE_NAME]" TERMINATED SUCCESS diff --git a/acceptance/bundle/integration_whl/serverless_dynamic_version/output.txt b/acceptance/bundle/integration_whl/serverless_dynamic_version/output.txt index 0c44462f8a8..30de76035c7 100644 --- a/acceptance/bundle/integration_whl/serverless_dynamic_version/output.txt +++ b/acceptance/bundle/integration_whl/serverless_dynamic_version/output.txt @@ -10,7 +10,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "My Wheel Job" RUNNING [TIMESTAMP] "My Wheel Job" TERMINATED SUCCESS @@ -28,7 +28,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "My Wheel Job" RUNNING [TIMESTAMP] "My Wheel Job" TERMINATED SUCCESS diff --git a/acceptance/bundle/integration_whl/wrapper/output.txt b/acceptance/bundle/integration_whl/wrapper/output.txt index bae674f2da4..09c0e810248 100644 --- a/acceptance/bundle/integration_whl/wrapper/output.txt +++ b/acceptance/bundle/integration_whl/wrapper/output.txt @@ -39,7 +39,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[SOME_OTHER_JOB_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[SOME_OTHER_JOB_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" TERMINATED SUCCESS diff --git a/acceptance/bundle/integration_whl/wrapper_custom_params/output.txt b/acceptance/bundle/integration_whl/wrapper_custom_params/output.txt index 96a698ba0d3..6d18ceab9cb 100644 --- a/acceptance/bundle/integration_whl/wrapper_custom_params/output.txt +++ b/acceptance/bundle/integration_whl/wrapper_custom_params/output.txt @@ -39,7 +39,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run some_other_job --python-params param1,param2 -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[NUMID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" RUNNING [TIMESTAMP] "[default] Test Wheel Job [UNIQUE_NAME]" TERMINATED SUCCESS diff --git a/acceptance/bundle/resources/clusters/run/spark_python_task/output.txt b/acceptance/bundle/resources/clusters/run/spark_python_task/output.txt index ed0bdb249dc..1d40fae8de9 100644 --- a/acceptance/bundle/resources/clusters/run/spark_python_task/output.txt +++ b/acceptance/bundle/resources/clusters/run/spark_python_task/output.txt @@ -10,7 +10,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run foo -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[FOO_ID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[FOO_ID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "test-job-with-cluster-[UNIQUE_NAME]" RUNNING [TIMESTAMP] "test-job-with-cluster-[UNIQUE_NAME]" TERMINATED SUCCESS diff --git a/acceptance/bundle/run/basic/output.txt b/acceptance/bundle/run/basic/output.txt index 5c3d3c4d12a..6d0534d9c2b 100644 --- a/acceptance/bundle/run/basic/output.txt +++ b/acceptance/bundle/run/basic/output.txt @@ -13,7 +13,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run foo -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[NUMID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "foo" RUNNING [TIMESTAMP] "foo" TERMINATED SUCCESS @@ -26,7 +26,7 @@ Exit code: 1 === resource key with parameters >>> [CLI] bundle run foo -- arg1 arg2 -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[NUMID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "foo" RUNNING [TIMESTAMP] "foo" TERMINATED SUCCESS diff --git a/acceptance/bundle/run/jobs/partial_run/output.txt b/acceptance/bundle/run/jobs/partial_run/output.txt index c9cc043cfe7..56ab5daa21f 100644 --- a/acceptance/bundle/run/jobs/partial_run/output.txt +++ b/acceptance/bundle/run/jobs/partial_run/output.txt @@ -6,7 +6,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run my_job --only task_1 -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[NUMID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "my_job" RUNNING [TIMESTAMP] "my_job" TERMINATED SUCCESS @@ -33,7 +33,7 @@ Hello from notebook2! } >>> [CLI] bundle run my_job --only task_1,task_2 -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[NUMID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "my_job" RUNNING [TIMESTAMP] "my_job" TERMINATED SUCCESS @@ -61,7 +61,7 @@ Hello from notebook2! } >>> [CLI] bundle run my_job -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[NUMID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "my_job" RUNNING [TIMESTAMP] "my_job" TERMINATED SUCCESS @@ -91,7 +91,7 @@ Error: task "non_existent_task" not found in job "my_job" Error: task "non_existent_task" not found in job "my_job" >>> [CLI] bundle run my_job --only task1.table1,task2>,task3>table3 -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[NUMID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "my_job" RUNNING [TIMESTAMP] "my_job" TERMINATED SUCCESS diff --git a/acceptance/bundle/run/state-wiped/output.txt b/acceptance/bundle/run/state-wiped/output.txt index 2214445a11b..ff2f702bf28 100644 --- a/acceptance/bundle/run/state-wiped/output.txt +++ b/acceptance/bundle/run/state-wiped/output.txt @@ -10,7 +10,7 @@ Updating deployment state... Deployment complete! >>> [CLI] bundle run foo -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[NUMID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "foo" RUNNING [TIMESTAMP] "foo" TERMINATED SUCCESS @@ -19,7 +19,7 @@ Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] >>> rm -fr .databricks >>> [CLI] bundle run foo -Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] +Run URL: [DATABRICKS_URL]/jobs/[NUMID]/runs/[NUMID]?o=[NUMID] [TIMESTAMP] "foo" RUNNING [TIMESTAMP] "foo" TERMINATED SUCCESS diff --git a/bundle/run/job.go b/bundle/run/job.go index f1012015fc7..de5457c26e9 100644 --- a/bundle/run/job.go +++ b/bundle/run/job.go @@ -5,7 +5,9 @@ import ( "encoding/json" "errors" "fmt" + "net/url" "strconv" + "strings" "time" "github.com/databricks/cli/bundle" @@ -14,6 +16,7 @@ import ( "github.com/databricks/cli/bundle/run/progress" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" @@ -96,8 +99,9 @@ func (m *jobRunMonitor) onProgress(info *jobs.Run) { // First time we see this run. if m.prevState == nil { - log.Infof(m.ctx, "Run available at %s", info.RunPageUrl) - cmdio.Log(m.ctx, progress.NewJobRunUrlEvent(info.RunPageUrl)) + runURL := runPageURL(m.ctx, info.RunPageUrl) + log.Infof(m.ctx, "Run available at %s", runURL) + cmdio.Log(m.ctx, progress.NewJobRunUrlEvent(runURL)) } // No state change: do not log. @@ -122,6 +126,47 @@ func (m *jobRunMonitor) onProgress(info *jobs.Run) { log.Info(m.ctx, event.String()) } +// runPageURL converts the legacy run URL returned by the Jobs API +// +// https:///?o=#job//run/ +// +// into the modern path form +// +// https:///jobs//runs/?o= +// +// so that non-admin users permitted to view the run are not redirected to the +// workspace homepage. See https://github.com/databricks/cli/issues/5142. The +// workspace selector query param (o) is preserved as-is. The conversion is +// cosmetic, so the original URL is returned on the rare chance the format is +// unexpected. +func runPageURL(ctx context.Context, raw string) string { + u, err := url.Parse(raw) + if err != nil { + log.Debugf(ctx, "could not parse run URL %q: %v", raw, err) + return raw + } + + jobID, runID, ok := parseLegacyRunFragment(u.Fragment) + if !ok { + log.Debugf(ctx, "unexpected run URL fragment %q", u.Fragment) + return raw + } + + u.Fragment = "" + u.Path = "/" + workspaceurls.JobRunPath(jobID, runID) + return u.String() +} + +// parseLegacyRunFragment extracts the job and run IDs from a legacy run URL +// fragment of the form "job//run/". +func parseLegacyRunFragment(fragment string) (jobID, runID string, ok bool) { + parts := strings.Split(fragment, "/") + if len(parts) != 4 || parts[0] != "job" || parts[2] != "run" || parts[1] == "" || parts[3] == "" { + return "", "", false + } + return parts[1], parts[3], true +} + func (r *jobRunner) Run(ctx context.Context, opts *Options) (output.RunOutput, error) { jobID, err := strconv.ParseInt(r.job.ID, 10, 64) if err != nil { @@ -160,7 +205,7 @@ func (r *jobRunner) Run(ctx context.Context, opts *Options) (output.RunOutput, e if err != nil { return nil, err } - cmdio.Log(ctx, progress.NewJobRunUrlEvent(details.RunPageUrl)) + cmdio.Log(ctx, progress.NewJobRunUrlEvent(runPageURL(ctx, details.RunPageUrl))) return nil, nil } diff --git a/bundle/run/job_test.go b/bundle/run/job_test.go index 4307f2fae74..73fb68757c2 100644 --- a/bundle/run/job_test.go +++ b/bundle/run/job_test.go @@ -11,6 +11,7 @@ import ( "github.com/databricks/cli/libs/cmdio" "github.com/databricks/databricks-sdk-go/experimental/mocks" "github.com/databricks/databricks-sdk-go/service/jobs" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -291,3 +292,50 @@ func TestJobRunnerRestartForContinuousUnpausedJobs(t *testing.T) { _, err := runner.Restart(ctx, &Options{}) require.NoError(t, err) } + +func TestRunPageURL(t *testing.T) { + ctx := t.Context() + tests := []struct { + name string + raw string + expected string + }{ + { + "legacy fragment form preserves workspace selector", + "https://myworkspace.databricks.test/?o=900800700600#job/123/run/456", + "https://myworkspace.databricks.test/jobs/123/runs/456?o=900800700600", + }, + { + "no workspace selector", + "https://myworkspace.databricks.test/#job/123/run/456", + "https://myworkspace.databricks.test/jobs/123/runs/456", + }, + { + "http host with port", + "http://127.0.0.1:8080/?o=900800700600#job/1/run/2", + "http://127.0.0.1:8080/jobs/1/runs/2?o=900800700600", + }, + // Unexpected formats are returned unchanged because the conversion is cosmetic. + { + "already modern path is left as-is", + "https://myworkspace.databricks.test/jobs/123/runs/456?o=900800700600", + "https://myworkspace.databricks.test/jobs/123/runs/456?o=900800700600", + }, + { + "incomplete fragment is left as-is", + "https://myworkspace.databricks.test/?o=900800700600#job/123", + "https://myworkspace.databricks.test/?o=900800700600#job/123", + }, + { + "empty job id is left as-is", + "https://myworkspace.databricks.test/?o=900800700600#job//run/456", + "https://myworkspace.databricks.test/?o=900800700600#job//run/456", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, runPageURL(ctx, tt.raw)) + }) + } +} diff --git a/libs/workspaceurls/urls.go b/libs/workspaceurls/urls.go index 46930c92239..f599139e340 100644 --- a/libs/workspaceurls/urls.go +++ b/libs/workspaceurls/urls.go @@ -67,6 +67,20 @@ func ResourceTypes() []string { return names } +// JobRunPath returns the modern workspace path for a job run, of the form +// +// jobs//runs/ +// +// Callers join this onto a workspace base URL. It exists because the Jobs API +// returns a legacy fragment URL (#job//run/) that relies on +// client-side redirection that only resolves for workspace admins; non-admin +// users with CAN_VIEW are sent to the workspace homepage instead. The modern +// path form resolves for any user permitted to view the run. +// See https://github.com/databricks/cli/issues/5142. +func JobRunPath(jobID, runID string) string { + return fmt.Sprintf("jobs/%s/runs/%s", jobID, runID) +} + // ResourceURL constructs a workspace URL for a named resource type and ID. func ResourceURL(baseURL url.URL, resourceType, id string) string { resourceType = resolveAlias(resourceType) diff --git a/libs/workspaceurls/urls_test.go b/libs/workspaceurls/urls_test.go index d0a86a0f247..683b9c470e7 100644 --- a/libs/workspaceurls/urls_test.go +++ b/libs/workspaceurls/urls_test.go @@ -31,6 +31,10 @@ func TestNormalizeDotSeparatedID(t *testing.T) { } } +func TestJobRunPath(t *testing.T) { + assert.Equal(t, "jobs/123/runs/456", JobRunPath("123", "456")) +} + func TestResourceTypes(t *testing.T) { types := ResourceTypes() assert.NotEmpty(t, types)