Skip to content

Commit a2146f3

Browse files
janfaraciktimja
andauthored
Add experimental job page UI (#11194)
Co-authored-by: Tim Jacomb <[email protected]>
1 parent 2b583fb commit a2146f3

File tree

22 files changed

+822
-112
lines changed

22 files changed

+822
-112
lines changed

core/src/main/java/hudson/model/Job.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
import java.util.SortedMap;
9090
import java.util.logging.Level;
9191
import java.util.logging.Logger;
92+
import java.util.stream.Stream;
9293
import jenkins.model.BuildDiscarder;
9394
import jenkins.model.BuildDiscarderProperty;
9495
import jenkins.model.DirectlyModifiableTopLevelItemGroup;
@@ -98,6 +99,12 @@
9899
import jenkins.model.ModelObjectWithChildren;
99100
import jenkins.model.PeepholePermalink;
100101
import jenkins.model.ProjectNamingStrategy;
102+
import jenkins.model.Tab;
103+
import jenkins.model.details.Detail;
104+
import jenkins.model.details.DetailFactory;
105+
import jenkins.model.details.DownstreamProjectsDetail;
106+
import jenkins.model.details.ProjectNameDetail;
107+
import jenkins.model.details.UpstreamProjectsDetail;
101108
import jenkins.model.lazy.LazyBuildMixIn;
102109
import jenkins.scm.RunWithSCM;
103110
import jenkins.security.HexStringConfidentialKey;
@@ -1689,4 +1696,30 @@ public BuildTimelineWidget getTimeline() {
16891696
}
16901697

16911698
private static final HexStringConfidentialKey SERVER_COOKIE = new HexStringConfidentialKey(Job.class, "serverCookie", 16);
1699+
1700+
@Extension
1701+
public static final class BasicJobDetailFactory extends DetailFactory<Job> {
1702+
1703+
@Override
1704+
public Class<Job> type() {
1705+
return Job.class;
1706+
}
1707+
1708+
@NonNull @Override public List<? extends Detail> createFor(@NonNull Job target) {
1709+
return Stream.of(new UpstreamProjectsDetail(target), new DownstreamProjectsDetail(target), new ProjectNameDetail(target)).filter(e -> e.getIconClassName() != null).toList();
1710+
}
1711+
}
1712+
1713+
/**
1714+
* Retrieves the tabs for a given job
1715+
*/
1716+
@Restricted(NoExternalUse.class)
1717+
public List<Tab> getJobTabs() {
1718+
return getActions(Tab.class);
1719+
}
1720+
1721+
@Restricted(NoExternalUse.class)
1722+
public final ParametersDefinitionProperty getParametersDefinitionProperty() {
1723+
return getProperty(ParametersDefinitionProperty.class);
1724+
}
16921725
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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.job;
26+
27+
import hudson.model.Actionable;
28+
import jenkins.model.Tab;
29+
30+
public class OverviewTab extends Tab {
31+
32+
public OverviewTab(Actionable object) {
33+
super(object);
34+
}
35+
36+
@Override
37+
public String getIconFileName() {
38+
return "symbol-overview";
39+
}
40+
41+
@Override
42+
public String getDisplayName() {
43+
return "Overview";
44+
}
45+
46+
@Override
47+
public String getUrlName() {
48+
return null;
49+
}
50+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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.job;
26+
27+
import edu.umd.cs.findbugs.annotations.NonNull;
28+
import hudson.Extension;
29+
import hudson.model.Job;
30+
import java.util.Collection;
31+
import java.util.Collections;
32+
import jenkins.model.Tab;
33+
import jenkins.model.TransientActionFactory;
34+
import jenkins.model.experimentalflags.NewJobPageUserExperimentalFlag;
35+
36+
@Extension(ordinal = Integer.MAX_VALUE)
37+
public class OverviewTabFactory extends TransientActionFactory<Job> {
38+
39+
@Override
40+
public Class<Job> type() {
41+
return Job.class;
42+
}
43+
44+
@NonNull
45+
@Override
46+
public Collection<? extends Tab> createFor(@NonNull Job target) {
47+
boolean isExperimentalUiEnabled = new NewJobPageUserExperimentalFlag().getFlagValue();
48+
49+
if (!isExperimentalUiEnabled) {
50+
return Collections.emptySet();
51+
}
52+
53+
return Collections.singleton(new OverviewTab(target));
54+
}
55+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package jenkins.model.details;
2+
3+
import edu.umd.cs.findbugs.annotations.Nullable;
4+
import hudson.model.AbstractProject;
5+
import hudson.model.Actionable;
6+
import hudson.model.Item;
7+
import java.util.List;
8+
9+
/**
10+
* Displays downstream projects of a project (if any)
11+
*/
12+
public class DownstreamProjectsDetail extends Detail {
13+
14+
public DownstreamProjectsDetail(Actionable object) {
15+
super(object);
16+
}
17+
18+
public @Nullable String getIconClassName() {
19+
if (getProjects().isEmpty()) {
20+
return null;
21+
}
22+
23+
return "symbol-arrow-down-circle-outline plugin-ionicons-api";
24+
}
25+
26+
@Override
27+
public @Nullable String getDisplayName() {
28+
int projectSize = getProjects().size();
29+
30+
if (projectSize == 1) {
31+
return "1 downstream project";
32+
}
33+
34+
return projectSize + " downstream projects";
35+
}
36+
37+
public List<AbstractProject> getProjects() {
38+
if (!(getObject() instanceof AbstractProject)) {
39+
return List.of();
40+
}
41+
42+
List<AbstractProject> projects = ((AbstractProject) getObject()).getDownstreamProjects();
43+
return projects.stream().filter(e -> e.hasPermission(Item.READ)).toList();
44+
}
45+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package jenkins.model.details;
2+
3+
import edu.umd.cs.findbugs.annotations.Nullable;
4+
import hudson.model.Actionable;
5+
import hudson.model.Hudson;
6+
import hudson.model.Job;
7+
import java.util.Objects;
8+
9+
/**
10+
* Displays the full name of a project (if necessary)
11+
*/
12+
public class ProjectNameDetail extends Detail {
13+
14+
public ProjectNameDetail(Actionable object) {
15+
super(object);
16+
}
17+
18+
public @Nullable String getIconClassName() {
19+
if (getDisplayName() == null) {
20+
return null;
21+
}
22+
23+
return "symbol-information-circle";
24+
}
25+
26+
@Override
27+
public @Nullable String getDisplayName() {
28+
var it = (Job<?, ?>) getObject();
29+
30+
if (Objects.equals(it.getFullName(), it.getFullDisplayName()) || it.getClass().getName().equals("MatrixConfiguration")) {
31+
return null;
32+
}
33+
34+
boolean nested = it.getParent().getClass() != Hudson.class;
35+
String label = nested ? "Full project name" : "Project name";
36+
37+
return label + ": " + it.getFullName();
38+
}
39+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package jenkins.model.details;
2+
3+
import edu.umd.cs.findbugs.annotations.Nullable;
4+
import hudson.model.AbstractProject;
5+
import hudson.model.Actionable;
6+
import hudson.model.Item;
7+
import java.util.List;
8+
9+
/**
10+
* Displays upstream projects of a project (if any)
11+
*/
12+
public class UpstreamProjectsDetail extends Detail {
13+
14+
public UpstreamProjectsDetail(Actionable object) {
15+
super(object);
16+
}
17+
18+
public @Nullable String getIconClassName() {
19+
if (getProjects().isEmpty()) {
20+
return null;
21+
}
22+
23+
return "symbol-arrow-up-circle-outline plugin-ionicons-api";
24+
}
25+
26+
@Override
27+
public @Nullable String getDisplayName() {
28+
int projectSize = getProjects().size();
29+
30+
if (projectSize == 1) {
31+
return "1 upstream project";
32+
}
33+
34+
return projectSize + " upstream projects";
35+
}
36+
37+
public List<AbstractProject> getProjects() {
38+
if (!(getObject() instanceof AbstractProject)) {
39+
return List.of();
40+
}
41+
42+
List<AbstractProject> projects = ((AbstractProject) getObject()).getUpstreamProjects();
43+
return projects.stream().filter(e -> e.hasPermission(Item.READ)).toList();
44+
}
45+
}
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 NewJobPageUserExperimentalFlag extends BooleanUserExperimentalFlag {
35+
public NewJobPageUserExperimentalFlag() {
36+
super("new-job-page.flag");
37+
}
38+
39+
@Override
40+
public String getDisplayName() {
41+
return "New job page";
42+
}
43+
44+
@Nullable
45+
@Override
46+
public String getShortDescription() {
47+
return "Enables a revamped job page. This feature is still a work in progress, so some things might not work perfectly yet.";
48+
}
49+
}

core/src/main/resources/hudson/model/AbstractProject/main.jelly

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,8 @@ THE SOFTWARE.
4646
<st:include page="jobMain.jelly" it="${a}" optional="true" />
4747
</j:forEach>
4848

49-
<p:upstream-downstream />
49+
<l:userExperimentalFlag var="newJobPage" flagClassName="jenkins.model.experimentalflags.NewJobPageUserExperimentalFlag" />
50+
<j:if test="${!newJobPage}">
51+
<p:upstream-downstream />
52+
</j:if>
5053
</j:jelly>

0 commit comments

Comments
 (0)