-
Notifications
You must be signed in to change notification settings - Fork 124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[JENKINS-27394] Collapsible sections in log #21
Changes from all commits
c063677
bbbf951
d9872e5
c0a7b09
1a656d6
457e6f8
0cec455
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,7 @@ | |
import java.io.PrintStream; | ||
import java.nio.charset.Charset; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.Date; | ||
import java.util.HashMap; | ||
import java.util.LinkedHashMap; | ||
|
@@ -94,8 +95,11 @@ | |
import org.jenkinsci.plugins.workflow.flow.GraphListener; | ||
import org.jenkinsci.plugins.workflow.flow.StashManager; | ||
import org.jenkinsci.plugins.workflow.graph.BlockEndNode; | ||
import org.jenkinsci.plugins.workflow.graph.BlockStartNode; | ||
import org.jenkinsci.plugins.workflow.graph.FlowEndNode; | ||
import org.jenkinsci.plugins.workflow.graph.FlowNode; | ||
import org.jenkinsci.plugins.workflow.job.console.NestingNote; | ||
import org.jenkinsci.plugins.workflow.job.console.ShowHideNote; | ||
import org.jenkinsci.plugins.workflow.job.console.WorkflowConsoleLogger; | ||
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException; | ||
import org.jenkinsci.plugins.workflow.steps.StepContext; | ||
|
@@ -370,14 +374,17 @@ private void copyLogs() { | |
AnnotatedLargeText<? extends FlowNode> logText = la.getLogText(); | ||
try { | ||
long old = entry.getValue(); | ||
OutputStream logger; | ||
|
||
String prefix = getLogPrefix(node); | ||
List<String> nesting = getNesting(node); | ||
String encodedNesting = new NestingNote(nesting).encode(); | ||
String linePrefix; | ||
if (prefix != null) { | ||
logger = new LogLinePrefixOutputFilter(listener.getLogger(), "[" + prefix + "] "); | ||
linePrefix = encodedNesting + "[" + prefix + "] "; | ||
} else { | ||
logger = listener.getLogger(); | ||
linePrefix = encodedNesting; | ||
} | ||
OutputStream logger = new LogLinePrefixOutputFilter(listener.getLogger(), linePrefix); | ||
|
||
try { | ||
long revised = writeRawLogTo(logText, old, logger); | ||
|
@@ -454,6 +461,37 @@ private long writeRawLogTo(AnnotatedLargeText<?> text, long start, OutputStream | |
} | ||
} | ||
|
||
@GuardedBy("completed") | ||
private transient LoadingCache<FlowNode,List<String>> nestingCache; | ||
private @Nonnull List<String> getNesting(FlowNode node) { | ||
// TODO could also use FlowScanningUtils.fetchEnclosingBlocks(node) but this would not let us cache intermediate results | ||
synchronized (completed) { | ||
if (nestingCache == null) { | ||
nestingCache = CacheBuilder.newBuilder().weakKeys().build(new CacheLoader<FlowNode,List<String>>() { | ||
@Override public @Nonnull List<String> load(FlowNode node) { | ||
if (node instanceof BlockEndNode) { | ||
return getNesting(((BlockEndNode) node).getStartNode()); | ||
} else { | ||
List<FlowNode> parents = node.getParents(); | ||
if (parents.isEmpty()) { // FlowStartNode | ||
return Collections.emptyList(); | ||
} | ||
List<String> parent = getNesting(parents.get(0)); // multiple parents is only for BlockEndNode after parallel | ||
if (node instanceof BlockStartNode) { | ||
List<String> appended = new ArrayList<>(parent); | ||
appended.add(node.getId()); | ||
return appended; | ||
} else { // AtomNode | ||
return parent; | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
return nestingCache.getUnchecked(node); | ||
} | ||
} | ||
|
||
private static final class LogLinePrefixOutputFilter extends LineTransformationOutputStream { | ||
|
||
private final PrintStream logger; | ||
|
@@ -833,12 +871,26 @@ private final class GraphL implements GraphListener { | |
} | ||
|
||
private void logNodeMessage(FlowNode node) { | ||
List<String> nesting = getNesting(node); | ||
if (!nesting.isEmpty() && nesting.get(nesting.size() - 1).equals(node.getId())) { | ||
// For a BlockStartNode, we do not want to hide itself. | ||
nesting = new ArrayList<>(nesting.subList(0, nesting.size() - 1)); | ||
} | ||
try { | ||
listener.annotate(new NestingNote(nesting)); | ||
} catch (IOException x) { | ||
LOGGER.log(Level.WARNING, null, x); | ||
} | ||
WorkflowConsoleLogger wfLogger = new WorkflowConsoleLogger(listener); | ||
String prefix = getLogPrefix(node); | ||
String text = node.getDisplayFunctionName(); | ||
if (node instanceof BlockStartNode) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be useful to be able to collapse |
||
text += " (" + ShowHideNote.create(node.getId()) + ")"; | ||
} | ||
if (prefix != null) { | ||
wfLogger.log(String.format("[%s] %s", prefix, node.getDisplayFunctionName())); | ||
wfLogger.log(String.format("[%s] %s", prefix, text)); | ||
} else { | ||
wfLogger.log(node.getDisplayFunctionName()); | ||
wfLogger.log(text); | ||
} | ||
// Flushing to keep logs printed in order as much as possible. The copyLogs method uses | ||
// LargeText and possibly LogLinePrefixOutputFilter. Both of these buffer and flush, causing strange | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* The MIT License | ||
* | ||
* Copyright 2016 CloudBees, Inc. | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in | ||
* all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* THE SOFTWARE. | ||
*/ | ||
|
||
package org.jenkinsci.plugins.workflow.job.console; | ||
|
||
import hudson.MarkupText; | ||
import hudson.console.ConsoleAnnotator; | ||
import hudson.console.ConsoleNote; | ||
import hudson.model.Run; | ||
import java.util.List; | ||
|
||
/** | ||
* Encodes the block-scoped nesting of a step. | ||
*/ | ||
public class NestingNote extends ConsoleNote<Run<?,?>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🐛 needs to be |
||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
private final List<String> nesting; | ||
|
||
public NestingNote(List<String> nesting) { | ||
this.nesting = nesting; | ||
} | ||
|
||
@SuppressWarnings("rawtypes") | ||
@Override public ConsoleAnnotator annotate(Run<?,?> context, MarkupText text, int charPos) { | ||
StringBuilder b = new StringBuilder("<span class=\""); | ||
for (int i = 0; i < nesting.size(); i++) { | ||
if (i > 0) { | ||
b.append(' '); | ||
} | ||
b.append("pipeline-sect-").append(nesting.get(i)); | ||
} | ||
b.append("\">"); | ||
text.addMarkup(0, text.length(), b.toString(), "</span>"); | ||
return null; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* The MIT License | ||
* | ||
* Copyright 2016 CloudBees, Inc. | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in | ||
* all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* THE SOFTWARE. | ||
*/ | ||
|
||
package org.jenkinsci.plugins.workflow.job.console; | ||
|
||
import hudson.Extension; | ||
import hudson.console.ConsoleAnnotationDescriptor; | ||
import hudson.console.HyperlinkNote; | ||
import java.io.IOException; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
/** | ||
* Shows or hides a block by nesting. | ||
*/ | ||
public class ShowHideNote extends HyperlinkNote { | ||
|
||
private static final Logger LOGGER = Logger.getLogger(ShowHideNote.class.getName()); | ||
private static final long serialVersionUID = 1L; | ||
|
||
public static String create(String id) { | ||
String text = "hide"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps hide all but the first branch of |
||
try { | ||
return new ShowHideNote(id, text.length()).encode() + text; | ||
} catch (IOException e) { | ||
// impossible, but don't make this a fatal problem | ||
LOGGER.log(Level.WARNING, "Failed to serialize " + ShowHideNote.class, e); | ||
return text; | ||
} | ||
} | ||
|
||
private final String id; | ||
|
||
private ShowHideNote(String id, int length) { | ||
super("#", length); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might as well use |
||
this.id = id; | ||
} | ||
|
||
@Override protected String extraAttributes() { | ||
return " show-hide-id=\"" + id + "\" onclick=\"showHidePipelineSection(this); return false\""; | ||
} | ||
|
||
@Extension public static class DescriptorImpl extends ConsoleAnnotationDescriptor {} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* The MIT License | ||
* | ||
* Copyright 2016 CloudBees, Inc. | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in | ||
* all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* THE SOFTWARE. | ||
*/ | ||
|
||
function showHidePipelineSection(link) { | ||
var id = link.getAttribute('show-hide-id') | ||
var display | ||
if (link.textContent === 'hide') { | ||
display = 'none' | ||
link.textContent = 'show' | ||
} else { | ||
display = 'inline' | ||
link.textContent = 'hide' | ||
} | ||
var sect = '.pipeline-sect-' + id | ||
var ss = document.styleSheets[0] | ||
for (var i = 0; i < ss.rules.length; i++) { | ||
if (ss.rules[i].selectorText === sect) { | ||
ss.rules[i].style.display = display | ||
return | ||
} | ||
} | ||
// TODO order rules, so that hiding and reshowing a high-level section will restore expansion of a lower-level section | ||
ss.insertRule(sect + ' {display: ' + display + '}', ss.rules.length) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐛 produces incorrect results for
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jglick Consider using https://github.com/jenkinsci/workflow-api-plugin/blob/master/src/main/java/org/jenkinsci/plugins/workflow/graphanalysis/SimpleChunkVisitor.java to collect ForkScanner results - you can easily roll up the result with a simple ArrayDequeue of blocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this case I want incremental caching, which AFAICT is not yet supported.