Skip to content

Commit 331c768

Browse files
janfaraciktimja
andauthored
Display Console Output on the build page (behind an experimental flag) (jenkinsci#10115)
Co-authored-by: Tim Jacomb <[email protected]>
1 parent b6e5833 commit 331c768

File tree

13 files changed

+282
-82
lines changed

13 files changed

+282
-82
lines changed

core/src/main/java/hudson/Functions.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
import java.util.List;
144144
import java.util.Locale;
145145
import java.util.Map;
146+
import java.util.Optional;
146147
import java.util.Set;
147148
import java.util.SortedMap;
148149
import java.util.TimeZone;
@@ -158,6 +159,7 @@
158159
import java.util.regex.Pattern;
159160
import java.util.stream.Collectors;
160161
import jenkins.console.ConsoleUrlProvider;
162+
import jenkins.console.DefaultConsoleUrlProvider;
161163
import jenkins.console.WithConsoleUrl;
162164
import jenkins.model.GlobalConfiguration;
163165
import jenkins.model.GlobalConfigurationCategory;
@@ -1993,6 +1995,14 @@ public static String joinPath(String... components) {
19931995
return consoleUrl != null ? Stapler.getCurrentRequest().getContextPath() + '/' + consoleUrl : null;
19941996
}
19951997

1998+
/**
1999+
* @param run the run
2000+
* @return the Console Provider for the given run, if null, the default Console Provider
2001+
*/
2002+
public static ConsoleUrlProvider getConsoleProviderFor(Run<?, ?> run) {
2003+
return Optional.ofNullable(ConsoleUrlProvider.getProvider(run)).orElse(new DefaultConsoleUrlProvider());
2004+
}
2005+
19962006
/**
19972007
* Escapes the character unsafe for e-mail address.
19982008
* See <a href="https://en.wikipedia.org/wiki/Email_address">the Wikipedia page</a> for the details,

core/src/main/java/jenkins/console/ConsoleUrlProvider.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,7 @@ default Descriptor<ConsoleUrlProvider> getDescriptor() {
8383
return Stapler.getCurrentRequest().getContextPath() + '/' + run.getConsoleUrl();
8484
}
8585

86-
/**
87-
* Looks up the {@link #getConsoleUrl} value from the first provider to offer one.
88-
* @since 2.476
89-
*/
90-
static @NonNull String consoleUrlOf(Run<?, ?> run) {
86+
static List<ConsoleUrlProvider> all() {
9187
final List<ConsoleUrlProvider> providers = new ArrayList<>();
9288
User currentUser = User.current();
9389
if (currentUser != null) {
@@ -105,8 +101,20 @@ default Descriptor<ConsoleUrlProvider> getDescriptor() {
105101
if (globalProviders != null) {
106102
providers.addAll(globalProviders);
107103
}
104+
return providers;
105+
}
106+
107+
static ConsoleUrlProvider getProvider(Run<?, ?> run) {
108+
return all().stream().filter(provider -> provider.getConsoleUrl(run) != null).findFirst().orElse(null);
109+
}
110+
111+
/**
112+
* Looks up the {@link #getConsoleUrl} value from the first provider to offer one.
113+
* @since 2.476
114+
*/
115+
static @NonNull String consoleUrlOf(Run<?, ?> run) {
108116
String url = null;
109-
for (ConsoleUrlProvider provider : providers) {
117+
for (ConsoleUrlProvider provider : all()) {
110118
try {
111119
String tempUrl = provider.getConsoleUrl(run);
112120
if (tempUrl != null) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2025, Jan Faracik
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package jenkins.model.experimentalflags;
26+
27+
import edu.umd.cs.findbugs.annotations.Nullable;
28+
import hudson.Extension;
29+
import org.kohsuke.accmod.Restricted;
30+
import org.kohsuke.accmod.restrictions.NoExternalUse;
31+
32+
@Extension
33+
@Restricted(NoExternalUse.class)
34+
public class NewBuildPageUserExperimentalFlag extends BooleanUserExperimentalFlag {
35+
public NewBuildPageUserExperimentalFlag() {
36+
super("new-build-page.flag");
37+
}
38+
39+
@Override
40+
public String getDisplayName() {
41+
return "New build page";
42+
}
43+
44+
@Nullable
45+
@Override
46+
public String getShortDescription() {
47+
return "Enables a revamped build page. This feature is still a work in progress, so some things might not work perfectly yet.";
48+
}
49+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:t="/lib/hudson">
3+
<j:set var="threshold" value="${h.getSystemProperty('hudson.consoleTailKB')?:'150'}" />
4+
<!-- Show at most last 150KB (can override with system property) unless consoleFull is set -->
5+
<j:set var="offset" value="${empty(consoleFull) ? it.logText.length()-threshold*1024 : 0}" />
6+
<j:choose>
7+
<j:when test="${offset > 0}">
8+
<a class="jenkins-button jenkins-!-accent-color jenkins-!-padding-2 jenkins-!-margin-bottom-2" style="width: 100%; justify-content: start" href="consoleFull">
9+
<l:icon src="symbol-help-circle" />
10+
${%skipSome(offset / 1024)}
11+
</a>
12+
</j:when>
13+
<j:otherwise>
14+
<j:set var="offset" value="${0}" />
15+
</j:otherwise>
16+
</j:choose>
17+
18+
<j:out value="${h.generateConsoleAnnotationScriptAndStylesheet()}"/>
19+
20+
<j:choose>
21+
<!-- Do progressive console output -->
22+
<j:when test="${it.isLogUpdated()}">
23+
<pre id="out" class="console-output" />
24+
<div id="spinner">
25+
<l:progressAnimation/>
26+
</div>
27+
<t:progressiveText href="logText/progressiveHtml" idref="out" spinner="spinner"
28+
startOffset="${offset}" onFinishEvent="jenkins:consoleFinished"/>
29+
</j:when>
30+
<!-- output is completed now. -->
31+
<j:otherwise>
32+
<pre class="console-output">
33+
<st:getOutput var="output" />
34+
<j:whitespace>${it.writeLogTo(offset,output)}</j:whitespace>
35+
</pre>
36+
</j:otherwise>
37+
</j:choose>
38+
</j:jelly>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
skipSome=This log is too long to show here, {0,number,integer} KB has been skipped — click to see the complete log

core/src/main/resources/hudson/model/Run/console.jelly

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -50,38 +50,7 @@ THE SOFTWARE.
5050
${%Console Output}
5151
</t:buildCaption>
5252

53-
<j:set var="threshold" value="${h.getSystemProperty('hudson.consoleTailKB')?:'150'}" />
54-
<!-- Show at most last 150KB (can override with system property) unless consoleFull is set -->
55-
<j:set var="offset" value="${empty(consoleFull) ? it.logText.length()-threshold*1024 : 0}" />
56-
<j:choose>
57-
<j:when test="${offset > 0}">
58-
${%skipSome(offset/1024,"consoleFull")}
59-
</j:when>
60-
<j:otherwise>
61-
<j:set var="offset" value="${0}" />
62-
</j:otherwise>
63-
</j:choose>
64-
65-
<j:out value="${h.generateConsoleAnnotationScriptAndStylesheet()}"/>
66-
67-
<j:choose>
68-
<!-- Do progressive console output -->
69-
<j:when test="${it.isLogUpdated()}">
70-
<pre id="out" class="console-output" />
71-
<div id="spinner">
72-
<l:progressAnimation/>
73-
</div>
74-
<t:progressiveText href="logText/progressiveHtml" idref="out" spinner="spinner"
75-
startOffset="${offset}" onFinishEvent="jenkins:consoleFinished"/>
76-
</j:when>
77-
<!-- output is completed now. -->
78-
<j:otherwise>
79-
<pre class="console-output" id="out">
80-
<st:getOutput var="output" />
81-
<j:whitespace>${it.writeLogTo(offset,output)}</j:whitespace>
82-
</pre>
83-
</j:otherwise>
84-
</j:choose>
53+
<st:include page="console-log.jelly" />
8554
</l:main-panel>
8655
</l:layout>
8756
</j:jelly>

core/src/main/resources/hudson/model/Run/index.jelly

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,53 +25,62 @@ THE SOFTWARE.
2525

2626
<?jelly escape-by-default='true'?>
2727
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:i="jelly:fmt">
28-
<l:layout title="${it.fullDisplayName}">
29-
<st:include page="sidepanel.jelly" />
28+
<l:userExperimentalFlag var="newBuildPage" flagClassName="jenkins.model.experimentalflags.NewBuildPageUserExperimentalFlag" />
3029

31-
<!-- no need for additional breadcrumb here as we're on an index page already including breadcrumb -->
32-
<l:main-panel>
33-
<j:set var="controls">
34-
<t:editDescriptionButton permission="${it.UPDATE}"/>
35-
<l:hasPermission permission="${it.UPDATE}">
36-
<st:include page="logKeep.jelly" />
37-
</l:hasPermission>
38-
</j:set>
30+
<j:choose>
31+
<j:when test="${newBuildPage}">
32+
<st:include page="new-build-page.jelly" />
33+
</j:when>
34+
<j:otherwise>
35+
<l:layout title="${it.fullDisplayName}">
36+
<st:include page="sidepanel.jelly" />
3937

40-
<t:buildCaption controls="${controls}">${it.displayName} (<i:formatDate value="${it.timestamp.time}" type="both" dateStyle="medium" timeStyle="medium"/>)</t:buildCaption>
38+
<!-- no need for additional breadcrumb here as we're on an index page already including breadcrumb -->
39+
<l:main-panel>
40+
<j:set var="controls">
41+
<t:editDescriptionButton permission="${it.UPDATE}"/>
42+
<l:hasPermission permission="${it.UPDATE}">
43+
<st:include page="logKeep.jelly" />
44+
</l:hasPermission>
45+
</j:set>
4146

42-
<div>
43-
<t:editableDescription permission="${it.UPDATE}" hideButton="true"/>
44-
</div>
47+
<t:buildCaption controls="${controls}">${it.displayName} (<i:formatDate value="${it.timestamp.time}" type="both" dateStyle="medium" timeStyle="medium"/>)</t:buildCaption>
4548

46-
<div style="float:right; z-index: 1; position:relative; margin-left: 1em">
47-
<div style="margin-top:1em">
48-
${%startedAgo(it.timestampString)}
49-
</div>
50-
<div>
51-
<j:if test="${it.building}">
52-
${%beingExecuted(it.executor.timestampString)}
53-
</j:if>
54-
<j:if test="${!it.building}">
55-
${%Took} <a href="${rootURL}/${it.parent.url}buildTimeTrend">${it.durationString}</a>
56-
</j:if>
57-
<st:include page="details.jelly" optional="true" />
58-
</div>
59-
</div>
49+
<div>
50+
<t:editableDescription permission="${it.UPDATE}" hideButton="true"/>
51+
</div>
6052

53+
<div style="float:right; z-index: 1; position:relative; margin-left: 1em">
54+
<div style="margin-top:1em">
55+
${%startedAgo(it.timestampString)}
56+
</div>
57+
<div>
58+
<j:if test="${it.building}">
59+
${%beingExecuted(it.executor.timestampString)}
60+
</j:if>
61+
<j:if test="${!it.building}">
62+
${%Took} <a href="${rootURL}/${it.parent.url}buildTimeTrend">${it.durationString}</a>
63+
</j:if>
64+
<st:include page="details.jelly" optional="true" />
65+
</div>
66+
</div>
6167

62-
<table>
63-
<t:artifactList build="${it}" caption="${%Build Artifacts}"
64-
permission="${it.ARTIFACTS}" />
6568

66-
<!-- give actions a chance to contribute summary item -->
67-
<j:forEach var="a" items="${it.allActions}">
68-
<st:include page="summary.jelly" from="${a}" optional="true" it="${a}" />
69-
</j:forEach>
69+
<table>
70+
<t:artifactList build="${it}" caption="${%Build Artifacts}"
71+
permission="${it.ARTIFACTS}" />
7072

71-
<st:include page="summary.jelly" optional="true" />
72-
</table>
73+
<!-- give actions a chance to contribute summary item -->
74+
<j:forEach var="a" items="${it.allActions}">
75+
<st:include page="summary.jelly" from="${a}" optional="true" it="${a}" />
76+
</j:forEach>
7377

74-
<st:include page="main.jelly" optional="true" />
75-
</l:main-panel>
76-
</l:layout>
78+
<st:include page="summary.jelly" optional="true" />
79+
</table>
80+
81+
<st:include page="main.jelly" optional="true" />
82+
</l:main-panel>
83+
</l:layout>
84+
</j:otherwise>
85+
</j:choose>
7786
</j:jelly>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<!--
2+
The MIT License
3+
4+
Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Matthew R. Harrah,
5+
Tom Huybrechts, id:cactusman, Romain Seguy
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in
15+
all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
THE SOFTWARE.
24+
-->
25+
26+
<?jelly escape-by-default='true'?>
27+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:i="jelly:fmt">
28+
<l:layout title="${it.fullDisplayName}">
29+
<st:include page="sidepanel.jelly" />
30+
31+
<!-- no need for additional breadcrumb here as we're on an index page already including breadcrumb -->
32+
<l:main-panel>
33+
<j:set var="controls">
34+
<t:editDescriptionButton permission="${it.UPDATE}"/>
35+
<l:hasPermission permission="${it.UPDATE}">
36+
<st:include page="logKeep.jelly" />
37+
</l:hasPermission>
38+
</j:set>
39+
40+
<t:buildCaption controls="${controls}">${it.displayName} (<i:formatDate value="${it.timestamp.time}" type="both" dateStyle="medium" timeStyle="medium"/>)</t:buildCaption>
41+
42+
<div>
43+
<t:editableDescription permission="${it.UPDATE}" hideButton="true"/>
44+
</div>
45+
46+
<st:include page="console.jelly" from="${h.getConsoleProviderFor(it)}" optional="true" />
47+
48+
<div style="float:right; z-index: 1; position:relative; margin-left: 1em">
49+
<div style="margin-top:1em">
50+
${%startedAgo(it.timestampString)}
51+
</div>
52+
<div>
53+
<j:if test="${it.building}">
54+
${%beingExecuted(it.executor.timestampString)}
55+
</j:if>
56+
<j:if test="${!it.building}">
57+
${%Took} <a href="${rootURL}/${it.parent.url}buildTimeTrend">${it.durationString}</a>
58+
</j:if>
59+
<st:include page="details.jelly" optional="true" />
60+
</div>
61+
</div>
62+
63+
<table>
64+
<t:artifactList build="${it}" caption="${%Build Artifacts}"
65+
permission="${it.ARTIFACTS}" />
66+
67+
<!-- give actions a chance to contribute summary item -->
68+
<j:forEach var="a" items="${it.allActions}">
69+
<st:include page="summary.jelly" from="${a}" optional="true" it="${a}" />
70+
</j:forEach>
71+
72+
<st:include page="summary.jelly" optional="true" />
73+
</table>
74+
75+
<st:include page="main.jelly" optional="true" />
76+
</l:main-panel>
77+
</l:layout>
78+
</j:jelly>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
startedAgo=Started {0} ago
2+
beingExecuted=Build has been executing for {0}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout">
3+
<j:set var="controls">
4+
<a href="consoleText" download="${it.displayName}.txt" tooltip="${%Download}" class="jenkins-card__reveal">
5+
<l:icon src="symbol-download" />
6+
</a>
7+
</j:set>
8+
9+
<l:card title="${%Console Output}" expandable="console" controls="${controls}">
10+
<div class="app-console-output-widget progressive-text-container">
11+
<st:include page="console-log.jelly" />
12+
</div>
13+
</l:card>
14+
</j:jelly>

0 commit comments

Comments
 (0)