Skip to content

Commit f075bc0

Browse files
[JENKINS-45927] Truncate verbose CauseOfBlockage messages in queue (#11298)
* [JENKINS-45927] Truncate verbose CauseOfBlockage messages in queue When multiple nodes reject a task, CompositeCauseOfBlockage previously joined ALL rejection reasons with "; ", causing extremely verbose tooltips that could fill the entire browser window. This fix truncates the message to show only the first 5 reasons, followed by "... and N more" when there are more than 5 reasons. Signed-off-by: Steve Armstrong <stevearmstrong-dev@users.noreply.github.com> * Fix test assertions to account for TreeMap alphabetical ordering The CompositeCauseOfBlockage uses a TreeMap which sorts keys alphabetically, so "Reason 10" comes before "Reason 2". Updated tests to use zero-padded numbers to ensure predictable ordering. Signed-off-by: W0474997SteveArmstrong <113034949+W0474997SteveArmstrong@users.noreply.github.com> * Fix Checkstyle import order - static imports must come first Jenkins Checkstyle rules require static imports to be in their own group at the top, before regular imports. Signed-off-by: W0474997SteveArmstrong <113034949+W0474997SteveArmstrong@users.noreply.github.com> --------- Signed-off-by: Steve Armstrong <stevearmstrong-dev@users.noreply.github.com> Signed-off-by: W0474997SteveArmstrong <113034949+W0474997SteveArmstrong@users.noreply.github.com> Co-authored-by: Steve Armstrong <stevearmstrong-dev@users.noreply.github.com> Co-authored-by: Mark Waite <mark.earl.waite@gmail.com>
1 parent 9878d67 commit f075bc0

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-1
lines changed

core/src/main/java/jenkins/model/queue/CompositeCauseOfBlockage.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.List;
3030
import java.util.Map;
3131
import java.util.TreeMap;
32+
import java.util.stream.Collectors;
3233
import org.kohsuke.accmod.Restricted;
3334
import org.kohsuke.accmod.restrictions.NoExternalUse;
3435

@@ -47,9 +48,20 @@ public CompositeCauseOfBlockage(List<CauseOfBlockage> delegates) {
4748
}
4849
}
4950

51+
private static final int MAX_REASONS_TO_DISPLAY = 5;
52+
5053
@Override
5154
public String getShortDescription() {
52-
return String.join("; ", uniqueReasons.keySet());
55+
int totalReasons = uniqueReasons.size();
56+
if (totalReasons <= MAX_REASONS_TO_DISPLAY) {
57+
return String.join("; ", uniqueReasons.keySet());
58+
}
59+
// Truncate long lists to avoid extremely verbose tooltips (JENKINS-45927)
60+
String truncatedReasons = uniqueReasons.keySet().stream()
61+
.limit(MAX_REASONS_TO_DISPLAY)
62+
.collect(Collectors.joining("; "));
63+
int remaining = totalReasons - MAX_REASONS_TO_DISPLAY;
64+
return truncatedReasons + "; ... and " + remaining + " more";
5365
}
5466

5567
@Override
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright 2025 Steve Armstrong
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.queue;
26+
27+
import static org.junit.jupiter.api.Assertions.assertEquals;
28+
import static org.junit.jupiter.api.Assertions.assertTrue;
29+
30+
import hudson.model.queue.CauseOfBlockage;
31+
import java.util.ArrayList;
32+
import java.util.List;
33+
import org.junit.jupiter.api.Test;
34+
35+
class CompositeCauseOfBlockageTest {
36+
37+
@Test
38+
void getShortDescriptionWithFewReasons() {
39+
List<CauseOfBlockage> reasons = new ArrayList<>();
40+
reasons.add(createCause("Reason 1"));
41+
reasons.add(createCause("Reason 2"));
42+
reasons.add(createCause("Reason 3"));
43+
44+
CompositeCauseOfBlockage composite = new CompositeCauseOfBlockage(reasons);
45+
String description = composite.getShortDescription();
46+
47+
assertEquals("Reason 1; Reason 2; Reason 3", description);
48+
}
49+
50+
@Test
51+
void getShortDescriptionWithExactlyMaxReasons() {
52+
List<CauseOfBlockage> reasons = new ArrayList<>();
53+
for (int i = 1; i <= 5; i++) {
54+
reasons.add(createCause("Reason " + i));
55+
}
56+
57+
CompositeCauseOfBlockage composite = new CompositeCauseOfBlockage(reasons);
58+
String description = composite.getShortDescription();
59+
60+
assertEquals("Reason 1; Reason 2; Reason 3; Reason 4; Reason 5", description);
61+
}
62+
63+
@Test
64+
void getShortDescriptionTruncatesLongList() {
65+
List<CauseOfBlockage> reasons = new ArrayList<>();
66+
for (int i = 1; i <= 10; i++) {
67+
// Use zero-padded numbers to ensure correct alphabetical sorting in TreeMap
68+
reasons.add(createCause(String.format("Reason %02d", i)));
69+
}
70+
71+
CompositeCauseOfBlockage composite = new CompositeCauseOfBlockage(reasons);
72+
String description = composite.getShortDescription();
73+
74+
assertTrue(description.contains("... and 5 more"));
75+
assertTrue(description.startsWith("Reason 01; Reason 02; Reason 03; Reason 04; Reason 05"));
76+
}
77+
78+
@Test
79+
void getShortDescriptionWithManyReasons() {
80+
List<CauseOfBlockage> reasons = new ArrayList<>();
81+
for (int i = 1; i <= 100; i++) {
82+
// Use zero-padded numbers to ensure correct alphabetical sorting in TreeMap
83+
reasons.add(createCause(String.format("Node %03d doesn't have label", i)));
84+
}
85+
86+
CompositeCauseOfBlockage composite = new CompositeCauseOfBlockage(reasons);
87+
String description = composite.getShortDescription();
88+
89+
assertTrue(description.contains("... and 95 more"));
90+
// Should not be extremely long
91+
assertTrue(description.length() < 500);
92+
}
93+
94+
private CauseOfBlockage createCause(String message) {
95+
return new CauseOfBlockage() {
96+
@Override
97+
public String getShortDescription() {
98+
return message;
99+
}
100+
};
101+
}
102+
}

0 commit comments

Comments
 (0)