From 85584e283a6af1681cb1274e8760f80851b07151 Mon Sep 17 00:00:00 2001 From: Daniel Beland Date: Thu, 5 Jan 2017 00:29:22 -0500 Subject: [PATCH 1/2] add support only for groovy postbuild text badges --- .../questions/ProjectWidget.java | 5 + .../project_widget/ProjectBadgesState.java | 27 +++++ .../tasks/ModifyControlPanelOptions.java | 38 +++++++ .../build_monitor/tasks/ShowBadges.java | 24 +++++ .../user_interface/BuildMonitorDashboard.java | 4 + .../AddAGroovyPostbuildScript.java | 31 ++++++ .../build_steps/GroovyScript.java | 58 +++++++++++ .../build_steps/GroovyScriptThat.java | 5 + .../build_steps/GroovyPostBuildStep.java | 7 ++ .../java/features/ShouldDisplayBadges.java | 64 ++++++++++++ build-monitor-plugin/pom.xml | 6 ++ .../buildmonitor/viewmodel/BuildView.java | 6 ++ .../viewmodel/BuildViewModel.java | 3 + .../buildmonitor/viewmodel/JobViews.java | 5 + .../buildmonitor/viewmodel/NullBuildView.java | 8 ++ .../viewmodel/features/HasBadges.java | 98 +++++++++++++++++++ .../BuildMonitorView/main-settings.jelly | 7 ++ .../BuildMonitorView/widgets.jelly | 3 + .../src/main/webapp/scripts/settings.js | 1 + .../viewmodel/features/HasBadgesTest.java | 51 ++++++++++ .../viewmodel/syntacticsugar/BadgeRecipe.java | 36 +++++++ .../syntacticsugar/BuildStateRecipe.java | 15 ++- .../syntacticsugar/JobStateRecipe.java | 2 +- .../viewmodel/syntacticsugar/Sugar.java | 4 + 24 files changed, 506 insertions(+), 2 deletions(-) create mode 100644 build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/questions/project_widget/ProjectBadgesState.java create mode 100644 build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/tasks/ModifyControlPanelOptions.java create mode 100644 build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/tasks/ShowBadges.java create mode 100644 build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/AddAGroovyPostbuildScript.java create mode 100644 build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScript.java create mode 100644 build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScriptThat.java create mode 100644 build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/user_interface/project_configuration/build_steps/GroovyPostBuildStep.java create mode 100644 build-monitor-acceptance/src/test/java/features/ShouldDisplayBadges.java create mode 100644 build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/features/HasBadges.java create mode 100644 build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/features/HasBadgesTest.java create mode 100644 build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/BadgeRecipe.java diff --git a/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/questions/ProjectWidget.java b/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/questions/ProjectWidget.java index 6eaae0245..977a894ee 100644 --- a/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/questions/ProjectWidget.java +++ b/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/questions/ProjectWidget.java @@ -1,6 +1,7 @@ package com.smartcodeltd.jenkinsci.plugins.build_monitor.questions; import com.smartcodeltd.jenkinsci.plugins.build_monitor.model.ProjectInformation; +import com.smartcodeltd.jenkinsci.plugins.build_monitor.questions.project_widget.ProjectBadgesState; import com.smartcodeltd.jenkinsci.plugins.build_monitor.questions.project_widget.ProjectWidgetDetails; import com.smartcodeltd.jenkinsci.plugins.build_monitor.questions.project_widget.ProjectWidgetInformation; import com.smartcodeltd.jenkinsci.plugins.build_monitor.questions.project_widget.ProjectWidgetState; @@ -25,6 +26,10 @@ public Question details() { return new ProjectWidgetDetails(projectOfInterest); } + public Question badges() { + return new ProjectBadgesState(projectOfInterest); + } + public ProjectWidget(String projectOfInterest) { this.projectOfInterest = projectOfInterest; } diff --git a/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/questions/project_widget/ProjectBadgesState.java b/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/questions/project_widget/ProjectBadgesState.java new file mode 100644 index 000000000..c13f411cc --- /dev/null +++ b/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/questions/project_widget/ProjectBadgesState.java @@ -0,0 +1,27 @@ +package com.smartcodeltd.jenkinsci.plugins.build_monitor.questions.project_widget; + +import com.smartcodeltd.jenkinsci.plugins.build_monitor.user_interface.BuildMonitorDashboard; +import net.serenitybdd.core.pages.WebElementState; +import net.serenitybdd.screenplay.Actor; +import net.serenitybdd.screenplay.Question; +import net.serenitybdd.screenplay.annotations.Subject; +import net.serenitybdd.screenplay.targets.Target; + +import static net.serenitybdd.screenplay.questions.WebElementQuestion.stateOf; + +@Subject("the badges of widget representing the '#projectName' project on the Build Monitor") +public class ProjectBadgesState implements Question { + + @Override + public WebElementState answeredBy(Actor actor) { + Target widget = BuildMonitorDashboard.Project_Widget_Badges.of(projectName); + + return stateOf(widget).answeredBy(actor); + } + + public ProjectBadgesState(String projectName) { + this.projectName = projectName; + } + + private final String projectName; +} diff --git a/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/tasks/ModifyControlPanelOptions.java b/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/tasks/ModifyControlPanelOptions.java new file mode 100644 index 000000000..a1a0c97ad --- /dev/null +++ b/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/tasks/ModifyControlPanelOptions.java @@ -0,0 +1,38 @@ +package com.smartcodeltd.jenkinsci.plugins.build_monitor.tasks; + +import com.smartcodeltd.jenkinsci.plugins.build_monitor.user_interface.BuildMonitorDashboard; + +import net.serenitybdd.screenplay.jenkins.tasks.configuration.TodoList; +import net.serenitybdd.screenplay.Actor; +import net.serenitybdd.screenplay.Performable; +import net.serenitybdd.screenplay.Task; +import net.serenitybdd.screenplay.actions.Click; +import net.thucydides.core.annotations.Step; + +import java.util.List; + +import static java.util.Arrays.asList; +import static net.serenitybdd.screenplay.Tasks.instrumented; + +public class ModifyControlPanelOptions implements Task { + + public static ModifyControlPanelOptions to(Task... configurationTasks) { + return instrumented(ModifyControlPanelOptions.class, asList(configurationTasks)); + } + + @Override + @Step("{0} modifies the Build Monitor View control panel options") + public void performAs(T actor) { + actor.attemptsTo( + Click.on(BuildMonitorDashboard.Control_Panel), + configureTheView, + Click.on(BuildMonitorDashboard.Control_Panel) + ); + } + + public ModifyControlPanelOptions(List actions) { + this.configureTheView.addAll(actions); + } + + private TodoList configureTheView = TodoList.empty(); +} diff --git a/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/tasks/ShowBadges.java b/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/tasks/ShowBadges.java new file mode 100644 index 000000000..b8916d976 --- /dev/null +++ b/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/tasks/ShowBadges.java @@ -0,0 +1,24 @@ +package com.smartcodeltd.jenkinsci.plugins.build_monitor.tasks; + +import com.smartcodeltd.jenkinsci.plugins.build_monitor.user_interface.BuildMonitorDashboard; + +import net.serenitybdd.screenplay.Actor; +import net.serenitybdd.screenplay.Task; +import net.serenitybdd.screenplay.actions.Click; +import net.thucydides.core.annotations.Step; + +import static net.serenitybdd.screenplay.Tasks.instrumented; + +public class ShowBadges implements Task { + public static Task onTheDashboard() { + return instrumented(ShowBadges.class); + } + + @Step("{0} decides to display the badges on the dashboard") + @Override + public void performAs(T actor) { + actor.attemptsTo( + Click.on(BuildMonitorDashboard.Show_Badges) + ); + } +} diff --git a/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/user_interface/BuildMonitorDashboard.java b/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/user_interface/BuildMonitorDashboard.java index 78f9a50d9..65b5eb9fb 100644 --- a/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/user_interface/BuildMonitorDashboard.java +++ b/build-monitor-acceptance/src/main/java/com/smartcodeltd/jenkinsci/plugins/build_monitor/user_interface/BuildMonitorDashboard.java @@ -8,4 +8,8 @@ public class BuildMonitorDashboard extends PageObject { public static final Target Add_Some_Projects_link = Link.called("add some projects"); public static final Target Project_Widget = Target.the("Project Widget").locatedBy("//li[header/h2[.='{0}']]"); public static final Target Project_Widget_Details = Target.the("Project Widget Details").locatedBy("//li[header/h2[.='{0}']]//*[@class='details']"); + public static final Target Project_Widget_Badges = Target.the("Project Widget Badges").locatedBy("//li[header/h2[.='{0}']]//*[@class='build-badge']"); + + public static final Target Control_Panel = Target.the("Control Panel").locatedBy("//label[@for='settings-toggle']"); + public static final Target Show_Badges = Target.the("Show Badges Toggle").locatedBy("//input[@id='settings-show-badges']"); } diff --git a/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/AddAGroovyPostbuildScript.java b/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/AddAGroovyPostbuildScript.java new file mode 100644 index 000000000..e89117fa6 --- /dev/null +++ b/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/AddAGroovyPostbuildScript.java @@ -0,0 +1,31 @@ +package net.serenitybdd.screenplay.jenkins.tasks.configuration.build_steps; + +import net.serenitybdd.screenplay.jenkins.user_interface.project_configuration.build_steps.GroovyPostBuildStep; +import net.serenitybdd.screenplay.Actor; +import net.serenitybdd.screenplay.Task; +import net.serenitybdd.screenplay.actions.Enter; +import net.thucydides.core.annotations.Step; + +import static net.serenitybdd.screenplay.Tasks.instrumented; + +public class AddAGroovyPostbuildScript implements Task { + + public static Task that(GroovyScript expectedOutcome) { + return instrumented(AddAGroovyPostbuildScript.class, expectedOutcome); + } + + @Step("{0} configures the Groovy PostBuild Step to execute a script that '#scriptOutcome'") + @Override + public void performAs(T actor) { + actor.attemptsTo( + AddAPostBuildAction.called("Groovy Postbuild"), + Enter.theValue(scriptOutcome.code()).into(GroovyPostBuildStep.Editor) + ); + } + + public AddAGroovyPostbuildScript(GroovyScript script) { + this.scriptOutcome = script; + } + + private final GroovyScript scriptOutcome; +} diff --git a/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScript.java b/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScript.java new file mode 100644 index 000000000..525aeed62 --- /dev/null +++ b/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScript.java @@ -0,0 +1,58 @@ +package net.serenitybdd.screenplay.jenkins.tasks.configuration.build_steps; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; + +import javax.annotation.Nullable; +import java.util.List; + +import static com.google.common.collect.Lists.transform; +import static java.util.Arrays.asList; + +public class GroovyScript { + + public static GroovyScript that(String descriptionOfScriptsBehaviour) { + return new GroovyScript(descriptionOfScriptsBehaviour); + } + + public GroovyScript definedAs(String... lines) { + return this.definedAs(asList(lines)); + } + + public GroovyScript definedAs(List lines) { + this.code = Joiner.on('\n').join(lines); + + return this; + } + + public GroovyScript andOutputs(String... lines) { + return definedAs(transform(asList(lines), mapEachLineTo("echo \"%s\";"))); + } + + public String code() { + return code; + } + + @Override + public String toString() { + return description; + } + + private GroovyScript(String descriptionOfScriptsBehaviour) { + this.description = descriptionOfScriptsBehaviour; + } + + private Function mapEachLineTo(final String template) { + return new Function() { + @Nullable + @Override + public String apply(@Nullable String line) { + return String.format(template, line); + } + }; + } + + private final String description; + + private String code = ""; +} diff --git a/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScriptThat.java b/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScriptThat.java new file mode 100644 index 000000000..7f4adb10f --- /dev/null +++ b/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScriptThat.java @@ -0,0 +1,5 @@ +package net.serenitybdd.screenplay.jenkins.tasks.configuration.build_steps; + +public class GroovyScriptThat { + public static final GroovyScript Adds_A_Badge = GroovyScript.that("Adds a badge").definedAs("manager.addShortText('Coverage', 'black', 'repeating-linear-gradient(45deg, yellow, yellow 10px, Orange 10px, Orange 20px)', '0px', 'white')"); +} \ No newline at end of file diff --git a/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/user_interface/project_configuration/build_steps/GroovyPostBuildStep.java b/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/user_interface/project_configuration/build_steps/GroovyPostBuildStep.java new file mode 100644 index 000000000..4282fcd5d --- /dev/null +++ b/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/user_interface/project_configuration/build_steps/GroovyPostBuildStep.java @@ -0,0 +1,7 @@ +package net.serenitybdd.screenplay.jenkins.user_interface.project_configuration.build_steps; + +import net.serenitybdd.screenplay.targets.Target; + +public class GroovyPostBuildStep { + public static final Target Editor = Target.the("code editor").locatedBy("(//div[@descriptorid='org.jvnet.hudson.plugins.groovypostbuild.GroovyPostbuildRecorder']//textarea)[last()]"); +} diff --git a/build-monitor-acceptance/src/test/java/features/ShouldDisplayBadges.java b/build-monitor-acceptance/src/test/java/features/ShouldDisplayBadges.java new file mode 100644 index 000000000..87e9238b4 --- /dev/null +++ b/build-monitor-acceptance/src/test/java/features/ShouldDisplayBadges.java @@ -0,0 +1,64 @@ +package features; + +import com.smartcodeltd.jenkinsci.plugins.build_monitor.questions.ProjectWidget; +import com.smartcodeltd.jenkinsci.plugins.build_monitor.tasks.HaveABuildMonitorViewCreated; +import com.smartcodeltd.jenkinsci.plugins.build_monitor.tasks.ModifyControlPanelOptions; +import com.smartcodeltd.jenkinsci.plugins.build_monitor.tasks.ShowBadges; +import environment.JenkinsSandbox; +import net.serenitybdd.integration.jenkins.JenkinsInstance; +import net.serenitybdd.integration.jenkins.environment.rules.InstallPlugins; +import net.serenitybdd.junit.runners.SerenityRunner; +import net.serenitybdd.screenplay.Actor; +import net.serenitybdd.screenplay.abilities.BrowseTheWeb; +import net.serenitybdd.screenplay.jenkins.HaveAProjectCreated; +import net.serenitybdd.screenplay.jenkins.tasks.ScheduleABuild; +import net.serenitybdd.screenplay.jenkins.tasks.configuration.build_steps.AddAGroovyPostbuildScript; +import net.serenitybdd.screenplay.jenkins.tasks.configuration.build_steps.ExecuteAShellScript; +import net.serenitybdd.screenplay.jenkins.tasks.configuration.build_steps.GroovyScriptThat; +import net.serenitybdd.screenplayx.actions.Navigate; +import net.thucydides.core.annotations.Managed; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.openqa.selenium.WebDriver; + +import static net.serenitybdd.screenplay.GivenWhenThen.*; +import static net.serenitybdd.screenplay.jenkins.tasks.configuration.build_steps.ShellScriptThat.Finishes_With_Success; +import static net.serenitybdd.screenplay.matchers.WebElementStateMatchers.isCurrentlyVisible; + +@RunWith(SerenityRunner.class) +public class ShouldDisplayBadges { + + Actor paul = Actor.named("Paul"); + + @Managed public WebDriver hisBrowser; + + @Rule public JenkinsInstance jenkins = JenkinsSandbox.configure().afterStart( + InstallPlugins.fromUpdateCenter("buildtriggerbadge", "groovy-postbuild") + ).create(); + + @Before + public void actorCanBrowseTheWeb() { + paul.can(BrowseTheWeb.with(hisBrowser)); + } + + @Test + public void display_build_badges() throws Exception { + givenThat(paul).wasAbleTo( + Navigate.to(jenkins.url()), + HaveAProjectCreated.called("My App").andConfiguredTo( + ExecuteAShellScript.that(Finishes_With_Success), + AddAGroovyPostbuildScript.that(GroovyScriptThat.Adds_A_Badge) + ), + ScheduleABuild.of("My App"), + HaveABuildMonitorViewCreated.showingAllTheProjects() + ); + + when(paul).attemptsTo(ModifyControlPanelOptions.to(ShowBadges.onTheDashboard())); + + then(paul).should(seeThat(ProjectWidget.of("My App").badges(), + isCurrentlyVisible() + )); + } +} diff --git a/build-monitor-plugin/pom.xml b/build-monitor-plugin/pom.xml index 45aa6e437..df215f521 100644 --- a/build-monitor-plugin/pom.xml +++ b/build-monitor-plugin/pom.xml @@ -163,6 +163,12 @@ 1.26 true + + org.jvnet.hudson.plugins + groovy-postbuild + 2.3.1 + true + diff --git a/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/BuildView.java b/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/BuildView.java index 6354aebc6..ae336293f 100644 --- a/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/BuildView.java +++ b/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/BuildView.java @@ -11,6 +11,7 @@ import hudson.scm.ChangeLogSet; import java.util.Date; +import java.util.List; import java.util.Set; import java.util.TreeSet; @@ -158,6 +159,11 @@ private T nonNullIterable(T list) { public Optional detailsOf(Class jenkinsAction) { return Optional.fromNullable(build.getAction(jenkinsAction)); } + + @Override + public List allDetailsOf(Class jenkinsAction) { + return build.getActions(jenkinsAction); + } @Override public String toString() { diff --git a/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/BuildViewModel.java b/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/BuildViewModel.java index 62315f90e..5e5a3cd22 100644 --- a/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/BuildViewModel.java +++ b/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/BuildViewModel.java @@ -3,8 +3,10 @@ import com.google.common.base.Optional; import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.duration.Duration; import hudson.model.Action; +import hudson.model.BuildBadgeAction; import hudson.model.Result; +import java.util.List; import java.util.Set; public interface BuildViewModel { @@ -27,4 +29,5 @@ public interface BuildViewModel { Set committers(); Optional detailsOf(Class jenkinsAction); + List allDetailsOf(Class jenkinsAction); } diff --git a/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/JobViews.java b/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/JobViews.java index 4357c0c50..29c87ef34 100644 --- a/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/JobViews.java +++ b/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/JobViews.java @@ -15,6 +15,7 @@ public class JobViews { public static final String Claim = "claim"; public static final String Build_Failure_Analyzer = "build-failure-analyzer"; + public static final String Groovy_Post_Build = "groovy-postbuild"; private final StaticJenkinsAPIs jenkins; private final com.smartcodeltd.jenkinsci.plugins.buildmonitor.Config config; @@ -39,6 +40,10 @@ public JobView viewOf(Job job) { viewFeatures.add(new CanBeDiagnosedForProblems()); } + if (jenkins.hasPlugin(Groovy_Post_Build)) { + viewFeatures.add(new HasBadges()); + } + return JobView.of(job, viewFeatures); } } diff --git a/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/NullBuildView.java b/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/NullBuildView.java index aa0e238bf..71523f119 100644 --- a/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/NullBuildView.java +++ b/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/NullBuildView.java @@ -3,9 +3,12 @@ import com.google.common.base.Optional; import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.duration.Duration; import hudson.model.Action; +import hudson.model.BuildBadgeAction; import hudson.model.Result; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; public class NullBuildView implements BuildViewModel { @@ -84,4 +87,9 @@ public Set committers() { public Optional detailsOf(Class jenkinsAction) { return Optional.absent(); } + + @Override + public List allDetailsOf(Class jenkinsAction) { + return new ArrayList(); + } } diff --git a/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/features/HasBadges.java b/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/features/HasBadges.java new file mode 100644 index 000000000..b9342b779 --- /dev/null +++ b/build-monitor-plugin/src/main/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/features/HasBadges.java @@ -0,0 +1,98 @@ +package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.features; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.JobView; + +import static com.google.common.collect.Lists.newArrayList; + +import java.util.Iterator; +import java.util.List; + +import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.annotate.JsonValue; +import org.jvnet.hudson.plugins.groovypostbuild.GroovyPostbuildAction; + +/** + * @author Daniel Beland + */ +public class HasBadges implements Feature { + private ActionFilter filter = new ActionFilter(); + private JobView job; + + public HasBadges() { + } + + @Override + public HasBadges of(JobView jobView) { + this.job = jobView; + + return this; + } + + @Override + public Badges asJson() { + Iterator badges = Iterables.filter(job.lastCompletedBuild().allDetailsOf(GroovyPostbuildAction.class), filter).iterator(); + + return badges.hasNext() + ? new Badges(badges) + : null; // `null` because we don't want to serialise an empty object + } + + public static class Badges { + private final List badges = newArrayList(); + + public Badges(Iterator badgeActions) { + while (badgeActions.hasNext()) { + badges.add(new Badge(badgeActions.next())); + } + } + + @JsonValue + public List value() { + return ImmutableList.copyOf(badges); + } + } + + public static class Badge { + private final GroovyPostbuildAction badge; + + public Badge(GroovyPostbuildAction badge) { + this.badge = badge; + } + + @JsonProperty + public final String text() { + return badge.getText(); + } + + @JsonProperty + public final String color() { + return badge.getColor(); + } + + @JsonProperty + public final String background() { + return badge.getBackground(); + } + + @JsonProperty + public final String border() { + return badge.getBorder(); + } + + @JsonProperty + public final String borderColor() { + return badge.getBorderColor(); + } + } + + private static class ActionFilter implements Predicate { + @Override + public boolean apply(GroovyPostbuildAction action) { + return action.getIconPath() == null; + } + } + +} diff --git a/build-monitor-plugin/src/main/resources/com/smartcodeltd/jenkinsci/plugins/buildmonitor/BuildMonitorView/main-settings.jelly b/build-monitor-plugin/src/main/resources/com/smartcodeltd/jenkinsci/plugins/buildmonitor/BuildMonitorView/main-settings.jelly index a44f9c46d..15506990c 100644 --- a/build-monitor-plugin/src/main/resources/com/smartcodeltd/jenkinsci/plugins/buildmonitor/BuildMonitorView/main-settings.jelly +++ b/build-monitor-plugin/src/main/resources/com/smartcodeltd/jenkinsci/plugins/buildmonitor/BuildMonitorView/main-settings.jelly @@ -34,6 +34,13 @@ id="settings-reduce-motion" type="checkbox" /> +
  • + + +
  • {{ name }}
  • +
  • + {{ badge.text }} +
  • diff --git a/build-monitor-plugin/src/main/webapp/scripts/settings.js b/build-monitor-plugin/src/main/webapp/scripts/settings.js index 80cc7082e..39198a01c 100644 --- a/build-monitor-plugin/src/main/webapp/scripts/settings.js +++ b/build-monitor-plugin/src/main/webapp/scripts/settings.js @@ -9,6 +9,7 @@ angular. $scope.settings.numberOfColumns = cookieJar.get('numberOfColumns', 2); $scope.settings.colourBlind = cookieJar.get('colourBlind', 0); $scope.settings.reduceMotion = cookieJar.get('reduceMotion', 0); + $scope.settings.showBadges = cookieJar.get('showBadges', 0); angular.forEach($scope.settings, function(value, name) { $scope.$watch('settings.' + name, function(currentValue) { diff --git a/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/features/HasBadgesTest.java b/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/features/HasBadgesTest.java new file mode 100644 index 000000000..ef416a1b1 --- /dev/null +++ b/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/features/HasBadgesTest.java @@ -0,0 +1,51 @@ +package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.features; + +import com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.JobView; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.powermock.core.classloader.annotations.PrepareForTest; + +import static com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.syntacticsugar.Sugar.*; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import java.net.URL; + +@PrepareForTest({URL.class}) +public class HasBadgesTest { + private JobView job; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void should_support_job_without_badges() throws Exception { + job = a(jobView().which(new HasBadges()).of( + a(job()))); + + assertThat(serialisedBadgesDetailsOf(job), is(nullValue())); + } + + @Test + public void should_convert_badges_to_json() throws Exception { + job = a(jobView().which(new HasBadges()).of( + a(job().whereTheLast(build().hasBadges(badge().withText("badge1"), badge().withText("badge2")))))); + + assertThat(serialisedBadgesDetailsOf(job).value(), hasSize(2)); + } + + @Test + public void should_ignore_badges_with_icon() throws Exception { + job = a(jobView().which(new HasBadges()).of( + a(job().whereTheLast(build().hasBadges(badge().withIcon("icon.gif", "badge1"), badge().withText("badge2")))))); + + assertThat(serialisedBadgesDetailsOf(job).value(), hasSize(1)); + } + + private HasBadges.Badges serialisedBadgesDetailsOf(JobView job) { + return job.which(HasBadges.class).asJson(); + } +} diff --git a/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/BadgeRecipe.java b/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/BadgeRecipe.java new file mode 100644 index 000000000..552f08c40 --- /dev/null +++ b/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/BadgeRecipe.java @@ -0,0 +1,36 @@ +package com.smartcodeltd.jenkinsci.plugins.buildmonitor.viewmodel.syntacticsugar; + +import com.google.common.base.Supplier; + +import org.jvnet.hudson.plugins.groovypostbuild.GroovyPostbuildAction; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Daniel Beland + */ +public class BadgeRecipe implements Supplier { + private GroovyPostbuildAction badge; + + public BadgeRecipe() { + badge = mock(GroovyPostbuildAction.class); + } + + public BadgeRecipe withText(String text) throws Exception { + when(badge.getIconPath()).thenReturn(null); + when(badge.getText()).thenReturn(text); + return this; + } + + public BadgeRecipe withIcon(String icon, String text) throws Exception { + when(badge.getIconPath()).thenReturn(icon); + when(badge.getText()).thenReturn(text); + return this; + } + + @Override + public GroovyPostbuildAction get() { + return badge; + } +} \ No newline at end of file diff --git a/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/BuildStateRecipe.java b/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/BuildStateRecipe.java index 9d8c1adc4..6b9fef769 100644 --- a/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/BuildStateRecipe.java +++ b/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/BuildStateRecipe.java @@ -12,6 +12,8 @@ import hudson.scm.ChangeLogSet; import jenkins.model.CauseOfInterruption; import jenkins.model.InterruptedBuildAction; + +import org.jvnet.hudson.plugins.groovypostbuild.GroovyPostbuildAction; import org.powermock.api.mockito.PowerMockito; import java.text.SimpleDateFormat; @@ -20,6 +22,7 @@ import java.util.Iterator; import java.util.List; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mockStatic; @@ -35,7 +38,7 @@ public BuildStateRecipe() { build = mock(AbstractBuild.class); AbstractProject parent = mock(AbstractProject.class); - when(build.getParent()).thenReturn(parent); + doReturn(parent).when(build).getParent(); } public BuildStateRecipe hasNumber(int number) { @@ -195,6 +198,16 @@ private FoundFailureCause failure(String name) { when(failure.getDescription()).thenReturn(name); return failure; } + + public BuildStateRecipe hasBadges(BadgeRecipe... badges) { + List actions = new ArrayList(); + for (int i = 0; i < badges.length; i++) { + actions.add(badges[i].get()); + } + when(build.getActions(GroovyPostbuildAction.class)).thenReturn(actions); + + return this; + } public BuildStateRecipe and() { return this; diff --git a/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/JobStateRecipe.java b/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/JobStateRecipe.java index 3560ffbff..d9a071fe1 100644 --- a/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/JobStateRecipe.java +++ b/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/JobStateRecipe.java @@ -95,7 +95,7 @@ public JobStateRecipe andThePrevious(BuildStateRecipe recipe) { // pick the first build from the build history and make it the "last build" if (buildHistory.size() == 1) { - when(job.getLastBuild()).thenReturn(buildHistory.pop()); + doReturn(buildHistory.pop()).when(job).getLastBuild(); } return job; diff --git a/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/Sugar.java b/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/Sugar.java index 19b71b2fc..6ae71025e 100644 --- a/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/Sugar.java +++ b/build-monitor-plugin/src/test/java/com/smartcodeltd/jenkinsci/plugins/buildmonitor/viewmodel/syntacticsugar/Sugar.java @@ -21,6 +21,10 @@ public static BuildStateRecipe build() { return new BuildStateRecipe(); } + public static BadgeRecipe badge() { + return new BadgeRecipe(); + } + public static X a(Supplier recipe) { return recipe.get(); } From 85a8e10e3a391d0aec63a8be6c9ebd12aa2cca87 Mon Sep 17 00:00:00 2001 From: jan-molak Date: Sat, 7 Jan 2017 03:16:10 +0000 Subject: [PATCH 2/2] - basic.less - Some additional css style makes the badges look pretty - widgets.jelly - older versions of IE tend to ignore Angular.js tokens inside the `style` tag; replaced with data-ng-style which is cross-browser compatible Closes #278 --- .../configuration/build_steps/GroovyScriptThat.java | 3 ++- .../src/test/java/features/ShouldDisplayBadges.java | 2 +- .../plugins/buildmonitor/BuildMonitorView/widgets.jelly | 8 ++++++-- .../src/main/webapp/less/module/widget/basic.less | 9 +++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScriptThat.java b/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScriptThat.java index 7f4adb10f..930fea8f3 100644 --- a/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScriptThat.java +++ b/build-monitor-acceptance/src/main/java/net/serenitybdd/screenplay/jenkins/tasks/configuration/build_steps/GroovyScriptThat.java @@ -1,5 +1,6 @@ package net.serenitybdd.screenplay.jenkins.tasks.configuration.build_steps; public class GroovyScriptThat { - public static final GroovyScript Adds_A_Badge = GroovyScript.that("Adds a badge").definedAs("manager.addShortText('Coverage', 'black', 'repeating-linear-gradient(45deg, yellow, yellow 10px, Orange 10px, Orange 20px)', '0px', 'white')"); + public static final GroovyScript Adds_A_Badge = GroovyScript.that("Adds a badge") + .definedAs("manager.addShortText('Coverage', 'black', 'repeating-linear-gradient(45deg, yellow, yellow 10px, Orange 10px, Orange 20px)', '0px', 'white')"); } \ No newline at end of file diff --git a/build-monitor-acceptance/src/test/java/features/ShouldDisplayBadges.java b/build-monitor-acceptance/src/test/java/features/ShouldDisplayBadges.java index 87e9238b4..909597b22 100644 --- a/build-monitor-acceptance/src/test/java/features/ShouldDisplayBadges.java +++ b/build-monitor-acceptance/src/test/java/features/ShouldDisplayBadges.java @@ -44,7 +44,7 @@ public void actorCanBrowseTheWeb() { } @Test - public void display_build_badges() throws Exception { + public void displaying_build_badges() throws Exception { givenThat(paul).wasAbleTo( Navigate.to(jenkins.url()), HaveAProjectCreated.called("My App").andConfiguredTo( diff --git a/build-monitor-plugin/src/main/resources/com/smartcodeltd/jenkinsci/plugins/buildmonitor/BuildMonitorView/widgets.jelly b/build-monitor-plugin/src/main/resources/com/smartcodeltd/jenkinsci/plugins/buildmonitor/BuildMonitorView/widgets.jelly index 7c232648e..58518bd34 100644 --- a/build-monitor-plugin/src/main/resources/com/smartcodeltd/jenkinsci/plugins/buildmonitor/BuildMonitorView/widgets.jelly +++ b/build-monitor-plugin/src/main/resources/com/smartcodeltd/jenkinsci/plugins/buildmonitor/BuildMonitorView/widgets.jelly @@ -35,8 +35,12 @@
  • {{ name }}
  • -
  • - {{ badge.text }} +
  • + + {{ badge.text }} +
  • diff --git a/build-monitor-plugin/src/main/webapp/less/module/widget/basic.less b/build-monitor-plugin/src/main/webapp/less/module/widget/basic.less index 74be55b10..5565f9c6d 100644 --- a/build-monitor-plugin/src/main/webapp/less/module/widget/basic.less +++ b/build-monitor-plugin/src/main/webapp/less/module/widget/basic.less @@ -77,6 +77,15 @@ text-align: center; li { margin: 0 0 0.25em 0; } + + li.badges .badge { + border: 1px solid black; + background: lightgreen; + color: #000; + display: inline-block; + padding: 1px 5px; + border-radius: 3px; + } } .build-failure-analyzer-plugin ul.identified-failures { list-style-type: none; display: inline-block; margin: 0; padding: 0; }