From 4605dfdd38598ea31978d6187c3524c1f445fe26 Mon Sep 17 00:00:00 2001 From: strangelookingnerd <49242855+strangelookingnerd@users.noreply.github.com> Date: Sat, 25 Jun 2022 11:36:02 +0200 Subject: [PATCH 1/2] (JENKINS-34502) provide health metric for a named child of the folder --- .../hudson/plugins/folder/AbstractFolder.java | 78 +++++---- .../folder/health/FolderHealthMetric.java | 21 ++- .../folder/health/NamedChildHealthMetric.java | 153 ++++++++++++++++++ .../plugins/folder/health/Messages.properties | 3 +- .../folder/health/Messages_ja.properties | 3 +- .../NamedChildHealthMetric/config.jelly | 25 +++ .../NamedChildHealthMetric/config.properties | 1 + .../help-childName.html | 3 + .../health/NamedChildHealthMetricTest.java | 76 +++++++++ 9 files changed, 312 insertions(+), 51 deletions(-) create mode 100644 src/main/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric.java create mode 100644 src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/config.jelly create mode 100644 src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/config.properties create mode 100644 src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/help-childName.html create mode 100644 src/test/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetricTest.java diff --git a/src/main/java/com/cloudbees/hudson/plugins/folder/AbstractFolder.java b/src/main/java/com/cloudbees/hudson/plugins/folder/AbstractFolder.java index da7a9acb..001566ef 100644 --- a/src/main/java/com/cloudbees/hudson/plugins/folder/AbstractFolder.java +++ b/src/main/java/com/cloudbees/hudson/plugins/folder/AbstractFolder.java @@ -894,33 +894,48 @@ public List getBuildHealthReports() { // ensure we refresh on average once every HEALTH_REPORT_CACHE_REFRESH_MIN but not all at once nextHealthReportsRefreshMillis = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(HEALTH_REPORT_CACHE_REFRESH_MIN * 3 / 4) - + ENTROPY.nextInt((int)TimeUnit.MINUTES.toMillis(HEALTH_REPORT_CACHE_REFRESH_MIN / 2)); + + ENTROPY.nextInt((int) TimeUnit.MINUTES.toMillis(HEALTH_REPORT_CACHE_REFRESH_MIN / 2)); reports = new ArrayList(); - List reporters = new ArrayList(healthMetrics.size()); - boolean recursive = false; - boolean topLevelOnly = true; + for (FolderHealthMetric metric : healthMetrics) { - recursive = recursive || metric.getType().isRecursive(); - topLevelOnly = topLevelOnly && metric.getType().isTopLevelItems(); - reporters.add(metric.reporter()); + FolderHealthMetric.Reporter reporter = metric.reporter(); + observeMetric(metric.getType(), reporter); + reports.addAll(reporter.report()); + } for (AbstractFolderProperty p : getProperties()) { for (FolderHealthMetric metric : p.getHealthMetrics()) { - recursive = recursive || metric.getType().isRecursive(); - topLevelOnly = topLevelOnly && metric.getType().isTopLevelItems(); - reporters.add(metric.reporter()); + FolderHealthMetric.Reporter reporter = metric.reporter(); + observeMetric(metric.getType(), reporter); + reports.addAll(p.getHealthReports()); } } - if (recursive) { - Stack> stack = new Stack>(); - stack.push(getItems()); - if (topLevelOnly) { - while (!stack.isEmpty()) { - for (Item item : stack.pop()) { - if (item instanceof TopLevelItem) { - for (FolderHealthMetric.Reporter reporter : reporters) { + + Collections.sort(reports); + healthReports = reports; // idempotent write + return reports; + } + + private void observeMetric(FolderHealthMetric.Type type, FolderHealthMetric.Reporter reporter) { + if (type.isWithChildren()) { + if (type.isRecursive()) { + Stack> stack = new Stack>(); + stack.push(getItems()); + if (type.isTopLevelItems()) { + while (!stack.isEmpty()) { + for (Item item : stack.pop()) { + if (item instanceof TopLevelItem) { reporter.observe(item); + if (item instanceof Folder) { + stack.push(((Folder) item).getItems()); + } } + } + } + } else { + while (!stack.isEmpty()) { + for (Item item : stack.pop()) { + reporter.observe(item); if (item instanceof Folder) { stack.push(((Folder) item).getItems()); } @@ -928,34 +943,13 @@ public List getBuildHealthReports() { } } } else { - while (!stack.isEmpty()) { - for (Item item : stack.pop()) { - for (FolderHealthMetric.Reporter reporter : reporters) { - reporter.observe(item); - } - if (item instanceof Folder) { - stack.push(((Folder) item).getItems()); - } - } - } - } - } else { - for (Item item: getItems()) { - for (FolderHealthMetric.Reporter reporter : reporters) { + for (Item item : getItems()) { reporter.observe(item); } } + } else { + reporter.observe(this); } - for (FolderHealthMetric.Reporter reporter : reporters) { - reports.addAll(reporter.report()); - } - for (AbstractFolderProperty p : getProperties()) { - reports.addAll(p.getHealthReports()); - } - - Collections.sort(reports); - healthReports = reports; // idempotent write - return reports; } public DescribableList getHealthMetrics() { diff --git a/src/main/java/com/cloudbees/hudson/plugins/folder/health/FolderHealthMetric.java b/src/main/java/com/cloudbees/hudson/plugins/folder/health/FolderHealthMetric.java index 064d0147..1db95b5f 100644 --- a/src/main/java/com/cloudbees/hudson/plugins/folder/health/FolderHealthMetric.java +++ b/src/main/java/com/cloudbees/hudson/plugins/folder/health/FolderHealthMetric.java @@ -70,19 +70,26 @@ public static interface Reporter { } public enum Type { - IMMEDIATE_TOP_LEVEL_ITEMS(false, true), - RECURSIVE_TOP_LEVEL_ITEMS(true, true), - IMMEDIATE_ALL_ITEMS(false, false), - RECURSIVE_ALL_ITEMS(true, false); - + + SELF_ONLY(false, false, false), + IMMEDIATE_TOP_LEVEL_ITEMS(true, false, true), + RECURSIVE_TOP_LEVEL_ITEMS(true, true, true), + IMMEDIATE_ALL_ITEMS(true, false, false), + RECURSIVE_ALL_ITEMS(true, true, false); + + private final boolean withChildren; private final boolean recursive; - private final boolean topLevelItems; - Type(boolean recursive, boolean topLevelItems) { + Type(boolean withChildren, boolean recursive, boolean topLevelItems) { + this.withChildren = withChildren; this.recursive = recursive; this.topLevelItems = topLevelItems; } + + public boolean isWithChildren() { + return withChildren; + } public boolean isRecursive() { return recursive; diff --git a/src/main/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric.java b/src/main/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric.java new file mode 100644 index 00000000..6b80d853 --- /dev/null +++ b/src/main/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric.java @@ -0,0 +1,153 @@ +/* + * The MIT License + * + * Copyright 2013 CloudBees. + * + * 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 com.cloudbees.hudson.plugins.folder.health; + +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; +import org.kohsuke.stapler.AncestorInPath; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; + +import hudson.Extension; +import hudson.model.AutoCompletionCandidates; +import hudson.model.HealthReport; +import hudson.model.Item; +import hudson.model.ItemGroup; + +/** + * A health metric for a named child item. + * + * @author strangelookingnerd + * + */ +public class NamedChildHealthMetric extends FolderHealthMetric { + + private String childName; + + /** + * Ctor. + * + * @param childName + * name of the child + */ + @DataBoundConstructor + public NamedChildHealthMetric(String childName) { + this.childName = childName; + } + + /** + * @return the name of the child. + */ + public String getChildName() { + return childName; + } + + @Override + public Type getType() { + return Type.SELF_ONLY; + } + + @Override + public Reporter reporter() { + return new ReporterImpl(childName); + } + + /** + * Descriptor Implementation. + * + * @author strangelookingnerd + * + */ + @Extension(ordinal = 400) + public static class DescriptorImpl extends FolderHealthMetricDescriptor { + + private static final String DEFAULT = ""; + + @Override + public String getDisplayName() { + return Messages.NamedChildHealthMetric_DisplayName(); + } + + @Override + public FolderHealthMetric createDefault() { + return new NamedChildHealthMetric(DEFAULT); + } + + /** + * Auto-completion for the "child names" field in the configuration. + * + * @param value + * the input + * @param container + * the context + * @return the candidates + */ + @Restricted(DoNotUse.class) + public AutoCompletionCandidates doAutoCompleteChildName(@QueryParameter String value, + @AncestorInPath ItemGroup container) { + AutoCompletionCandidates candidates = new AutoCompletionCandidates(); + for (Item item : container.getItems()) { + if (StringUtils.startsWith(item.getName(), value)) { + candidates.add(item.getName()); + } + } + return candidates; + } + } + + private static class ReporterImpl implements Reporter { + private HealthReport report = null; + private String childName; + + /** + * Ctor. + * + * @param childName + * name of the child + */ + public ReporterImpl(String childName) { + this.childName = childName; + } + + @Override + public void observe(Item item) { + if (item instanceof ItemGroup) { + Item child = ((ItemGroup) item).getItem(childName); + if (child != null) { + report = getHealthReport(child); + } + } + } + + @Override + public List report() { + return report != null ? Collections.singletonList(report) : Collections.emptyList(); + } + } +} diff --git a/src/main/resources/com/cloudbees/hudson/plugins/folder/health/Messages.properties b/src/main/resources/com/cloudbees/hudson/plugins/folder/health/Messages.properties index 3b0e0772..7a793b7a 100644 --- a/src/main/resources/com/cloudbees/hudson/plugins/folder/health/Messages.properties +++ b/src/main/resources/com/cloudbees/hudson/plugins/folder/health/Messages.properties @@ -1,2 +1,3 @@ Folder.HealthWrap=Worst health: {0}: {1} -WorstChildHealthMetric.DisplayName=Child item with worst health \ No newline at end of file +WorstChildHealthMetric.DisplayName=Child item with worst health +NamedChildHealthMetric.DisplayName=Child item with the given name \ No newline at end of file diff --git a/src/main/resources/com/cloudbees/hudson/plugins/folder/health/Messages_ja.properties b/src/main/resources/com/cloudbees/hudson/plugins/folder/health/Messages_ja.properties index 26aedee7..9dceed1d 100644 --- a/src/main/resources/com/cloudbees/hudson/plugins/folder/health/Messages_ja.properties +++ b/src/main/resources/com/cloudbees/hudson/plugins/folder/health/Messages_ja.properties @@ -21,4 +21,5 @@ # THE SOFTWARE. Folder.HealthWrap=\u6700\u60aa\u306e\u5065\u5168\u6027: {0}: {1} -WorstChildHealthMetric.DisplayName=Child item with worst health \ No newline at end of file +WorstChildHealthMetric.DisplayName=Child item with worst health +NamedChildHealthMetric.DisplayName=Child item with the given name \ No newline at end of file diff --git a/src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/config.jelly b/src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/config.jelly new file mode 100644 index 00000000..47d5188c --- /dev/null +++ b/src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/config.jelly @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/config.properties b/src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/config.properties new file mode 100644 index 00000000..b8ca1b2a --- /dev/null +++ b/src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/config.properties @@ -0,0 +1 @@ +ChildName=Child Name \ No newline at end of file diff --git a/src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/help-childName.html b/src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/help-childName.html new file mode 100644 index 00000000..24fb8023 --- /dev/null +++ b/src/main/resources/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric/help-childName.html @@ -0,0 +1,3 @@ +
+ Controls the child item within this folder representing to the health of this folder. +
\ No newline at end of file diff --git a/src/test/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetricTest.java b/src/test/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetricTest.java new file mode 100644 index 00000000..da4e62f4 --- /dev/null +++ b/src/test/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetricTest.java @@ -0,0 +1,76 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees. + * + * 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 com.cloudbees.hudson.plugins.folder.health; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; + +import java.util.List; + +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import com.cloudbees.hudson.plugins.folder.Folder; + +import hudson.model.HealthReport; + +public class NamedChildHealthMetricTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + public void childExists() throws Exception { + Folder folder = j.jenkins.createProject(Folder.class, "myFolder"); + folder.createProject(Folder.class, "mySubFolder"); + folder.getHealthMetrics().add(new NamedChildHealthMetric("mySubFolder")); + + List reports = folder.getBuildHealthReports(); + assertThat("report should be available for existing child", reports, hasSize(1)); + } + + @Test + public void childDoesNotExist() throws Exception { + Folder folder = j.jenkins.createProject(Folder.class, "myFolder"); + folder.createProject(Folder.class, "mySubFolder"); + folder.getHealthMetrics().add(new NamedChildHealthMetric("doesnotexist")); + + List reports = folder.getBuildHealthReports(); + assertThat("report should not contain report for non-existent child", reports, hasSize(0)); + } + + @Test + public void nestedChild() throws Exception { + Folder folder = j.jenkins.createProject(Folder.class, "myFolder"); + Folder subFolder = folder.createProject(Folder.class, "mySubFolder"); + subFolder.createProject(Folder.class, "nestedFolder"); + folder.getHealthMetrics().add(new NamedChildHealthMetric("mySubFolder/nestedFolder")); + + List reports = folder.getBuildHealthReports(); + assertThat("report should not contain report for nested child", reports, hasSize(0)); + } + +} From 345481c7fc84c8b9417b288b9c73a2f9c582fdbb Mon Sep 17 00:00:00 2001 From: strangelookingnerd <49242855+strangelookingnerd@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:35:42 +0200 Subject: [PATCH 2/2] Add RequirePOST annotation --- .../folder/health/NamedChildHealthMetric.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric.java b/src/main/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric.java index 6b80d853..6211f228 100644 --- a/src/main/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric.java +++ b/src/main/java/com/cloudbees/hudson/plugins/folder/health/NamedChildHealthMetric.java @@ -33,6 +33,7 @@ import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; import hudson.Extension; import hudson.model.AutoCompletionCandidates; @@ -42,7 +43,7 @@ /** * A health metric for a named child item. - * + * * @author strangelookingnerd * */ @@ -52,7 +53,7 @@ public class NamedChildHealthMetric extends FolderHealthMetric { /** * Ctor. - * + * * @param childName * name of the child */ @@ -80,7 +81,7 @@ public Reporter reporter() { /** * Descriptor Implementation. - * + * * @author strangelookingnerd * */ @@ -101,13 +102,14 @@ public FolderHealthMetric createDefault() { /** * Auto-completion for the "child names" field in the configuration. - * + * * @param value * the input * @param container * the context * @return the candidates */ + @RequirePOST @Restricted(DoNotUse.class) public AutoCompletionCandidates doAutoCompleteChildName(@QueryParameter String value, @AncestorInPath ItemGroup container) { @@ -127,7 +129,7 @@ private static class ReporterImpl implements Reporter { /** * Ctor. - * + * * @param childName * name of the child */