Skip to content

Commit 5ddda92

Browse files
committed
Add links to 'nextflow launch' as well as 'nextflow run'
1 parent 71fd0d7 commit 5ddda92

File tree

2 files changed

+63
-10
lines changed

2 files changed

+63
-10
lines changed

modules/nextflow/src/main/groovy/nextflow/util/ColorUtil.groovy

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,19 @@ class ColorUtil {
138138
return fmt.toString()
139139
}
140140

141+
/**
142+
* Wraps text in an OSC 8 terminal hyperlink escape sequence.
143+
*
144+
* @param text The visible text to display
145+
* @param url The URL to link to
146+
* @return The text wrapped in hyperlink escape sequences, or just text if url is null/empty or ANSI is disabled
147+
*/
148+
static String hyperlink(String text, String url) {
149+
if (!url || !isAnsiEnabled())
150+
return text ?: ''
151+
return "\033]8;;${url}\007${text}\033]8;;\007"
152+
}
153+
141154
/**
142155
* Parse color name to Jansi Color enum
143156
*/

plugins/nf-tower/src/main/io/seqera/tower/plugin/launch/LaunchCommandImpl.groovy

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class LaunchCommandImpl extends BaseCommandImpl implements CmdLaunch.LaunchComma
8686
String revision
8787
String repository
8888
String trackingUrl
89+
String commitUrl
8990
}
9091

9192
// ===== Main Entry Point =====
@@ -104,9 +105,9 @@ class LaunchCommandImpl extends BaseCommandImpl implements CmdLaunch.LaunchComma
104105
final result = submitWorkflowLaunch(options, context, resolvedPipelineUrl)
105106

106107
// Display launch information
107-
printLaunchInfo(result.repository, result.runName, result.commitId, result.revision,
108-
context.workDir, context.computeEnvName, context.userName,
109-
context.orgName, context.workspaceName, options)
108+
printLaunchInfo(result.repository, result.runName, result.commitId, result.revision, result.commitUrl,
109+
context.workDir, context.computeEnvId, context.computeEnvName, context.userName,
110+
context.orgName, context.workspaceName, context.apiEndpoint, options)
110111
printSuccessMessage(result.workflowId, result.trackingUrl, options)
111112

112113
// Poll for workflow logs
@@ -269,13 +270,17 @@ class LaunchCommandImpl extends BaseCommandImpl implements CmdLaunch.LaunchComma
269270
}
270271
}
271272

273+
// Get commit browse URL from SCM provider
274+
final commitUrl = getCommitBrowseUrl(pipelineUrl, commitId)
275+
272276
return new WorkflowLaunchResult(
273277
workflowId: response.workflowId as String,
274278
runName: runName,
275279
commitId: commitId,
276280
revision: revision,
277281
repository: pipelineUrl,
278-
trackingUrl: trackingUrl
282+
trackingUrl: trackingUrl,
283+
commitUrl: commitUrl
279284
)
280285
}
281286

@@ -334,7 +339,8 @@ class LaunchCommandImpl extends BaseCommandImpl implements CmdLaunch.LaunchComma
334339

335340
// Show Nextflow version and launch context
336341
print(ColorUtil.colorize(" ~ ", "dim", true))
337-
println("version " + BuildInfo.version)
342+
final versionUrl = "https://github.com/nextflow-io/nextflow/releases/tag/v${BuildInfo.version}"
343+
println(ColorUtil.hyperlink("version " + BuildInfo.version, versionUrl))
338344
println("Launching workflow in Seqera Platform")
339345
println ""
340346
} else {
@@ -344,10 +350,26 @@ class LaunchCommandImpl extends BaseCommandImpl implements CmdLaunch.LaunchComma
344350
}
345351
}
346352

347-
protected void printLaunchInfo(String repo, String runName, String commitId, String revision, String workDir, String computeEnvName, String userName, String orgName, String workspaceName, CmdLaunch.LaunchOptions options) {
353+
protected void printLaunchInfo(String repo, String runName, String commitId, String revision, String commitUrl, String workDir, String computeEnvId, String computeEnvName, String userName, String orgName, String workspaceName, String apiEndpoint, CmdLaunch.LaunchOptions options) {
348354
def showRevision = commitId && commitId != 'unknown'
349355
def showRevisionBrackets = revision && revision != 'unknown'
350356

357+
// Build web URLs for workspace and compute environment
358+
final webUrl = getWebUrlFromApiEndpoint(apiEndpoint)
359+
String workspaceUrl = null
360+
String computeEnvUrl = null
361+
if (orgName && workspaceName) {
362+
workspaceUrl = "${webUrl}/orgs/${orgName}/workspaces/${workspaceName}/launchpad"
363+
if (computeEnvId) {
364+
computeEnvUrl = "${webUrl}/orgs/${orgName}/workspaces/${workspaceName}/compute-envs/${computeEnvId}"
365+
}
366+
} else if (userName) {
367+
workspaceUrl = "${webUrl}/user/${userName}/launchpad"
368+
if (computeEnvId) {
369+
computeEnvUrl = "${webUrl}/user/${userName}/compute-envs/${computeEnvId}"
370+
}
371+
}
372+
351373
if (ColorUtil.isAnsiEnabled()) {
352374
def debugMsg = "Launched `${repo}` [${runName}]"
353375
if (showRevision) {
@@ -365,7 +387,7 @@ class LaunchCommandImpl extends BaseCommandImpl implements CmdLaunch.LaunchComma
365387
print(ColorUtil.colorize("]", "dim", true))
366388
if (showRevision) {
367389
print(" - ")
368-
print(ColorUtil.colorize("revision: $commitId", "cyan", true))
390+
print(ColorUtil.colorize(ColorUtil.hyperlink("revision: $commitId", commitUrl), "cyan", true))
369391
}
370392
if (showRevisionBrackets) {
371393
print(ColorUtil.colorize(" [", "dim", true))
@@ -381,16 +403,19 @@ class LaunchCommandImpl extends BaseCommandImpl implements CmdLaunch.LaunchComma
381403

382404
// Print workspace
383405
if (orgName && workspaceName) {
384-
println(" 🏢 workspace: ${ColorUtil.colorize(orgName + ' / ' + workspaceName, 'cyan', true)}")
406+
final workspaceText = ColorUtil.hyperlink(orgName + ' / ' + workspaceName, workspaceUrl)
407+
println(" 🏢 workspace: ${ColorUtil.colorize(workspaceText, 'cyan', true)}")
385408
} else {
386-
println(" 🏢 workspace: ${ColorUtil.colorize("Personal workspace", 'cyan', true)}")
409+
final personalText = ColorUtil.hyperlink("Personal workspace", workspaceUrl)
410+
println(" 🏢 workspace: ${ColorUtil.colorize(personalText, 'cyan', true)}")
387411
}
388412

389413
// Print work directory
390414
println(" 📁 workdir: ${ColorUtil.colorize(workDir, 'cyan', true)}")
391415

392416
// Print compute environment
393-
println(" ☁️ compute: ${ColorUtil.colorize(computeEnvName, 'cyan', true)}\n")
417+
final computeEnvText = ColorUtil.hyperlink(computeEnvName, computeEnvUrl)
418+
println(" ☁️ compute: ${ColorUtil.colorize(computeEnvText, 'cyan', true)}\n")
394419
} else {
395420
def plainMsg = "Launched `${repo}` [${runName}]"
396421
if (showRevision) {
@@ -945,6 +970,21 @@ class LaunchCommandImpl extends BaseCommandImpl implements CmdLaunch.LaunchComma
945970
}
946971
}
947972

973+
/**
974+
* Construct a browse URL for the given repository and commit ID using the SCM provider
975+
*/
976+
protected String getCommitBrowseUrl(String pipelineName, String commitId) {
977+
if (!pipelineName || !commitId || commitId == 'unknown')
978+
return null
979+
try {
980+
def assetManager = new AssetManager(pipelineName)
981+
return assetManager.getProvider()?.getBrowseUrl(commitId)
982+
} catch (Exception e) {
983+
log.debug "Failed to get commit browse URL: ${e.message}"
984+
return null
985+
}
986+
}
987+
948988
// ===== API Helper Methods =====
949989

950990
protected Map apiGet(String path, Map queryParams = [:], String accessToken, String apiEndpoint) {

0 commit comments

Comments
 (0)