diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputAction.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputAction.java index f759fdd9..c007c891 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputAction.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputAction.java @@ -17,10 +17,13 @@ import org.jenkinsci.plugins.workflow.flow.FlowExecution; import org.jenkinsci.plugins.workflow.flow.FlowExecutionList; import org.jenkinsci.plugins.workflow.steps.StepExecution; +import org.kohsuke.stapler.export.Exported; +import org.kohsuke.stapler.export.ExportedBean; /** * Records the pending inputs required. */ +@ExportedBean public class InputAction implements RunAction2 { private static final Logger LOGGER = Logger.getLogger(InputAction.class.getName()); @@ -106,6 +109,7 @@ public String getIconFileName() { } } + @Exported @Override public String getDisplayName() { if (ids == null || ids.isEmpty()) { @@ -142,6 +146,7 @@ public synchronized InputStepExecution getExecution(String id) throws Interrupte return null; } + @Exported public synchronized List getExecutions() throws InterruptedException, TimeoutException { loadExecutions(); if (executions == null) { @@ -150,6 +155,11 @@ public synchronized List getExecutions() throws InterruptedE return new ArrayList(executions); } + @Exported + public boolean isWaitingForInput() throws InterruptedException, TimeoutException { + return !getExecutions().isEmpty(); + } + /** * Called when {@link InputStepExecution} is completed to remove it from the active input list. */ diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStep.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStep.java index 6f63a8b7..2cc1c70d 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStep.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStep.java @@ -40,12 +40,15 @@ import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.export.Exported; +import org.kohsuke.stapler.export.ExportedBean; /** * {@link Step} that pauses for human input. * * @author Kohsuke Kawaguchi */ +@ExportedBean(defaultVisibility = 2) public class InputStep extends AbstractStepImpl implements Serializable { private static final boolean ALLOW_POTENTIALLY_UNSAFE_IDS = SystemProperties.getBoolean(InputStep.class.getName() + ".ALLOW_UNSAFE_IDS"); @@ -95,12 +98,14 @@ public void setId(String id) { this.id = _id; } + @Exported public String getId() { if (id==null) id = capitalize(Util.getDigestOf(message)); return id; } + @Exported public String getSubmitter() { return submitter; } @@ -109,6 +114,7 @@ public String getSubmitter() { this.submitter = Util.fixEmptyAndTrim(submitter); } + @Exported public String getSubmitterParameter() { return submitterParameter; } @DataBoundSetter public void setSubmitterParameter(String submitterParameter) { @@ -130,6 +136,7 @@ private String capitalize(String id) { /** * Caption of the OK button. */ + @Exported public String getOk() { return ok!=null ? ok : Messages.proceed(); } @@ -138,6 +145,7 @@ public String getOk() { this.ok = Util.fixEmptyAndTrim(ok); } + @Exported public List getParameters() { return parameters; } @@ -146,6 +154,7 @@ public List getParameters() { this.parameters = parameters; } + @Exported public String getMessage() { return message; } diff --git a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStepExecution.java b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStepExecution.java index b0c3cee2..971574ad 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStepExecution.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStepExecution.java @@ -38,6 +38,8 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.export.Exported; +import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.interceptor.RequirePOST; import edu.umd.cs.findbugs.annotations.CheckForNull; @@ -61,6 +63,7 @@ /** * @author Kohsuke Kawaguchi */ +@ExportedBean(defaultVisibility = 2) public class InputStepExecution extends AbstractStepExecutionImpl implements ModelObject { private static final Logger LOGGER = Logger.getLogger(InputStepExecution.class.getName()); @@ -142,10 +145,12 @@ public void stop(Throwable cause) throws Exception { }); } + @Exported public String getId() { return input.getId(); } + @Exported public InputStep getInput() { return input; } @@ -165,6 +170,7 @@ private TaskListener getListener() throws IOException, InterruptedException { /** * If this input step has been decided one way or the other. */ + @Exported public boolean isSettled() { return outcome!=null; } @@ -180,7 +186,7 @@ private InputAction getPauseAction() throws IOException, InterruptedException { return a; } - @Override + @Override @Exported public String getDisplayName() { String message = getInput().getMessage(); if (message.length()<32) return message; diff --git a/src/test/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStepTest.java b/src/test/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStepTest.java index 68e7f168..85ee8fbb 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStepTest.java @@ -57,8 +57,9 @@ import hudson.util.FormValidation.Kind; import hudson.util.Secret; import jenkins.model.IdStrategy; -import jenkins.model.InterruptedBuildAction; import jenkins.model.Jenkins; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; @@ -78,6 +79,7 @@ import java.util.Arrays; import java.util.Map; +import java.util.Optional; import java.util.UUID; import org.jvnet.hudson.test.MockAuthorizationStrategy; @@ -87,9 +89,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -611,5 +612,92 @@ public void test_unsafe_ids_generate_formValidation() throws Exception { assertThat("> should be rejected", d.doCheckId("this-is-also>-not-ok"), JenkinsMatchers.hasKind(Kind.ERROR)); } + @Test + public void test_api_contains_waitingForInput() throws Exception { + //set up dummy security real + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + // job setup + WorkflowJob foo = j.jenkins.createProject(WorkflowJob.class, "foo"); + foo.setDefinition(new CpsFlowDefinition(StringUtils.join(Arrays.asList( + "def x = input message:'Continue?';", + "echo(\"after: ${x}\");"),"\n"),true)); + // get the build going, and wait until workflow pauses + QueueTaskFuture q = foo.scheduleBuild2(0); + WorkflowRun b = q.getStartCondition().get(); + j.waitForMessage("Continue?", b); + + final JenkinsRule.WebClient webClient = j.createWebClient(); + JenkinsRule.JSONWebResponse json = webClient.getJSON(b.getUrl() + "api/json?depth=1"); + JSONArray actions = json.getJSONObject().getJSONArray("actions"); + Optional obj = actions.stream().filter(oo -> + ((JSONObject)oo).get("_class").equals("org.jenkinsci.plugins.workflow.support.steps.input.InputAction") + ).findFirst(); + assertTrue(obj.isPresent()); + JSONObject o = (JSONObject)obj.get(); + assertTrue(o.has("waitingForInput")); + assertTrue(o.getBoolean("waitingForInput")); + + InputAction inputAction = b.getAction(InputAction.class); + InputStepExecution is = inputAction.getExecutions().get(0); + HtmlPage p = webClient.getPage(b, inputAction.getUrlName()); + j.submit(p.getFormByName(is.getId()), "proceed"); + + json = webClient.getJSON(b.getUrl() + "api/json?depth=1"); + actions = json.getJSONObject().getJSONArray("actions"); + obj = actions.stream().filter(oo -> + ((JSONObject)oo).get("_class").equals("org.jenkinsci.plugins.workflow.support.steps.input.InputAction") + ).findFirst(); + assertTrue(obj.isPresent()); + o = (JSONObject)obj.get(); + assertTrue(o.has("waitingForInput")); + assertFalse(o.getBoolean("waitingForInput")); + } + + @Test + public void test_api_contains_details() throws Exception { + //set up dummy security real + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + // job setup + WorkflowJob foo = j.jenkins.createProject(WorkflowJob.class, "foo"); + foo.setDefinition(new CpsFlowDefinition(StringUtils.join(Arrays.asList( + "def chosen = input message: 'Can we settle on this thing?', ok: 'Yep', parameters: [choice(choices: ['Apple', 'Blueberry', 'Banana'], description: 'The fruit in question.', name: 'fruit')], submitter: 'bobby', submitterParameter: 'dd'", + "echo(\"after: ${chosen}\");"),"\n"),true)); + + // get the build going, and wait until workflow pauses + QueueTaskFuture q = foo.scheduleBuild2(0); + WorkflowRun b = q.getStartCondition().get(); + j.waitForMessage("Input requested", b); + + final JenkinsRule.WebClient webClient = j.createWebClient(); + final JenkinsRule.JSONWebResponse json = webClient.getJSON(b.getUrl() + "api/json?depth=2"); + final JSONArray actions = json.getJSONObject().getJSONArray("actions"); + final Optional obj = actions.stream().filter(oo -> + ((JSONObject)oo).get("_class").equals("org.jenkinsci.plugins.workflow.support.steps.input.InputAction") + ).findFirst(); + assertTrue(obj.isPresent()); + final JSONObject o = (JSONObject)obj.get(); + assertTrue(o.has("waitingForInput")); + assertTrue(o.getBoolean("waitingForInput")); + + assertTrue(o.has("executions")); + JSONObject exs = o.getJSONArray("executions").getJSONObject(0); + assertEquals("Can we settle on this thing?", exs.getString("displayName")); + assertTrue(exs.has("input")); + JSONObject input = exs.getJSONObject("input"); + assertEquals("Can we settle on this thing?", input.getString("message")); + assertEquals("Yep", input.getString("ok")); + assertEquals("bobby", input.getString("submitter")); + assertTrue(input.has("parameters")); + JSONObject param = input.getJSONArray("parameters").getJSONObject(0); + assertEquals("fruit", param.getString("name")); + assertEquals("ChoiceParameterDefinition", param.getString("type")); + assertThat(param.getJSONArray("choices").toArray(), arrayContaining("Apple", "Blueberry", "Banana")); + + InputAction inputAction = b.getAction(InputAction.class); + InputStepExecution is = inputAction.getExecutions().get(0); + HtmlPage p = webClient.getPage(b, inputAction.getUrlName()); + j.submit(p.getFormByName(is.getId()), "proceed"); + j.assertBuildStatusSuccess(j.waitForCompletion(b)); + } }