Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Jira Issue update step to pipeline #101

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/main/java/hudson/plugins/jira/JiraRestService.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@
import com.atlassian.jira.rest.client.api.JiraRestClient;
import com.atlassian.jira.rest.client.api.RestClientException;
import com.atlassian.jira.rest.client.api.domain.*;
import com.atlassian.jira.rest.client.api.domain.input.IssueInput;
import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder;
import com.atlassian.jira.rest.client.api.domain.input.TransitionInput;
import com.atlassian.jira.rest.client.api.domain.input.VersionInput;
import com.atlassian.jira.rest.client.api.domain.input.*;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please replace wildcard imports?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this is an automatic IDE feature (in IDEA) replacement, that when > 5 imports, wildcards are automatically incurred. Personally this is still debatable for me:
http://stackoverflow.com/questions/147454/why-is-using-a-wild-card-with-a-java-import-statement-bad
Any good reason in Jenkins case about not using wildcard imports that we should maybe put in the README guidelines?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i just looked through few random classes in jenkins core repo and can't see wildcard imports used. it would be better to add checkstyle plugin to build rather than put it in README

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Iterators;
Expand Down Expand Up @@ -368,4 +365,19 @@ public String getBaseApiPath() {
public Permissions getMyPermissions() throws RestClientException {
return jiraRestClient.getMyPermissionsRestClient().getMyPermissions(null).claim();
}

public boolean updateIssueFieldValue(String issueKey, String fieldName, String fieldValue) {
Copy link
Member

@artkoshelev artkoshelev Aug 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it should be called updateIssue, since we update any field. and can we use this method inside current updateIssue (which is actually updates fix version) and updateIssueLabels methods ?

IssueField field = getIssue(issueKey).getFieldByName(fieldName);
FieldInput fieldInput = new FieldInput(field.getId(), fieldValue);

IssueInput issueInput = IssueInput.createWithFields(fieldInput);

try {
jiraRestClient.getIssueClient().updateIssue(issueKey, issueInput).get(timeout, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
LOGGER.warning("jira rest client update issue field error. cause: " + e.getMessage());
}
return false;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add new line

12 changes: 12 additions & 0 deletions src/main/java/hudson/plugins/jira/JiraSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -410,4 +410,16 @@ public Version addVersion(String version, String projectKey) {
*/
public Permissions getMyPermissions(){ return service.getMyPermissions(); }

/**
* Update any issue field by given value
*
* @param issueKey
* @param fieldName
* @param fieldValue
*/
public boolean updateIssueFieldValue(String issueKey, String fieldName, String fieldValue) {
LOGGER.fine("Updating issue " + issueKey + " with field name: " + fieldName + " and value: " + fieldValue);
return service.updateIssueFieldValue(issueKey, fieldName, fieldValue);
}

}
105 changes: 105 additions & 0 deletions src/main/java/hudson/plugins/jira/pipeline/IssueFieldUpdateStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package hudson.plugins.jira.pipeline;

import com.google.inject.Inject;
import hudson.Extension;
import hudson.Util;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.jira.JiraSite;
import hudson.plugins.jira.Messages;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;
import org.kohsuke.stapler.DataBoundConstructor;

import javax.annotation.Nonnull;
import java.io.IOException;

/**
* Step for updating jira issue with workflow migration
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one is doing something different...

*
* @author aatarasoff
*/
public class IssueFieldUpdateStep extends AbstractStepImpl {
private final String issueKey;
private final String fieldName;
private final String fieldValue;

@DataBoundConstructor
public IssueFieldUpdateStep(@Nonnull String issueKey, @Nonnull String fieldName, @Nonnull String fieldValue) {
this.issueKey = Util.fixEmptyAndTrim(issueKey);
this.fieldName = Util.fixEmptyAndTrim(fieldName);
this.fieldValue = Util.fixEmptyAndTrim(fieldValue);
}

public String getIssueKey() {
return issueKey;
}

public String getFieldName() {
return fieldName;
}

public String getFieldValue() {
return fieldValue;
}

@Extension(optional = true)
public static final class DescriptorImpl extends AbstractStepDescriptorImpl {

public DescriptorImpl() {
super(IssueFieldUpdateStep.StepExecution.class);
}

@Override
public String getFunctionName() {
return "jiraIssueFieldUpdate";
}

@Override
public String getDisplayName() {
return Messages.IssueFieldUpdateStep_Descriptor_DisplayName();
}
}

public static class StepExecution extends AbstractSynchronousNonBlockingStepExecution<Void> {

private static final long serialVersionUID = 1L;

@Inject
private transient IssueFieldUpdateStep step;

@StepContextParameter
private transient TaskListener listener;

@StepContextParameter
private transient Run run;

@Override
protected Void run() throws Exception {
JiraSite site = JiraSite.get(run.getParent());

if (site == null) {
listener.getLogger().println(Messages.Updater_NoJiraSite());
run.setResult(Result.FAILURE);
}

listener.getLogger().println("[JIRA] Update issue with key: " + step.getIssueKey());

try {
if (!site.getSession().updateIssueFieldValue(step.getIssueKey(), step.getFieldName(), step.getFieldValue())) {
listener.getLogger().println(Messages.IssueFieldUpdateStep_Failed());
run.setResult(Result.UNSTABLE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why UNSTABLE here and FAILURE in other cases? I think we should stick to one failure type in this case.

}
} catch (IOException e) {
listener.getLogger().println(Messages.IssueFieldUpdateStep_Failed());
e.printStackTrace(listener.getLogger());
run.setResult(Result.FAILURE);
}

return null;
}
}
}
110 changes: 110 additions & 0 deletions src/main/java/hudson/plugins/jira/pipeline/IssueUpdateStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package hudson.plugins.jira.pipeline;

import com.google.inject.Inject;
import hudson.Extension;
import hudson.Util;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.jira.JiraSite;
import hudson.plugins.jira.Messages;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;
import org.kohsuke.stapler.DataBoundConstructor;

import javax.annotation.Nonnull;
import java.io.IOException;

/**
* Step for updating jira issue with workflow migration
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to re-word, as I don't understand it - maybe let's stick to conform to JIRA terminology about workflow transition :-)
e.g. "Perform workflow transitions on the issues selected by JQL query"?

*
* @author aatarasoff
*/
public class IssueUpdateStep extends AbstractStepImpl {
private final String jqlSearch;
private final String workflowActionName;
private final String comment;

@DataBoundConstructor
public IssueUpdateStep(@Nonnull String jqlSearch, @Nonnull String workflowActionName, String comment) {
this.jqlSearch = Util.fixEmptyAndTrim(jqlSearch);
this.workflowActionName = Util.fixEmptyAndTrim(workflowActionName);
this.comment = Util.fixEmptyAndTrim(comment);
}

public String getJqlSearch() {
return jqlSearch;
}

public String getWorkflowActionName() {
return workflowActionName;
}

public String getComment() {
return comment;
}

@Extension(optional = true)
public static final class DescriptorImpl extends AbstractStepDescriptorImpl {

public DescriptorImpl() {
super(IssueUpdateStep.StepExecution.class);
}

@Override
public String getFunctionName() {
return "jiraIssueUpdate";
}

@Override
public String getDisplayName() {
return Messages.IssueUpdateStep_Descriptor_DisplayName();
}
}

public static class StepExecution extends AbstractSynchronousNonBlockingStepExecution<Void> {

private static final long serialVersionUID = 1L;

@Inject
private transient IssueUpdateStep step;

@StepContextParameter
private transient TaskListener listener;

@StepContextParameter
private transient Run run;

@Override
protected Void run() throws Exception {
JiraSite site = JiraSite.get(run.getParent());

if (site == null) {
listener.getLogger().println(Messages.Updater_NoJiraSite());
run.setResult(Result.FAILURE);
}

if (StringUtils.isNotEmpty(step.getWorkflowActionName())) {
listener.getLogger().println(Messages.JiraIssueUpdateBuilder_UpdatingWithAction(step.getWorkflowActionName()));
}

listener.getLogger().println("[JIRA] JQL: " + step.getJqlSearch());

try {
if (!site.progressMatchingIssues(step.getJqlSearch(), step.workflowActionName, step.getComment(), listener.getLogger())) {
listener.getLogger().println(Messages.JiraIssueUpdateBuilder_SomeIssuesFailed());
run.setResult(Result.UNSTABLE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

}
} catch (IOException e) {
listener.getLogger().println(Messages.JiraIssueUpdateBuilder_Failed());
e.printStackTrace(listener.getLogger());
run.setResult(Result.FAILURE);
}

return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package hudson.plugins.jira.pipeline;

import com.google.inject.Inject;
import hudson.Extension;
import hudson.Util;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.jira.JiraSite;
import hudson.plugins.jira.Messages;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;
import org.kohsuke.stapler.DataBoundConstructor;

import javax.annotation.Nonnull;

/**
* Created by aleksandr on 01.07.16.
*/
public class IssueWorkflowActionStep extends AbstractStepImpl {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add class description

private final String issueKey;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no variable expansion here, I'm wondering what usecase is this going to be, because you'll need to configure "issueKey" as static value in the plugin configuration.
Usually this will not be the case and you'll want to get this from e.g. a variable ?

Maybe you could add variable expansion (see JiraIssueUpdateBuilder for example) - that would give some additional great power to those functionalities, what do you think?

private final String workflowActionName;

@DataBoundConstructor
public IssueWorkflowActionStep(@Nonnull String issueKey, @Nonnull String workflowActionName) {
this.issueKey = Util.fixEmptyAndTrim(issueKey);
this.workflowActionName = Util.fixEmptyAndTrim(workflowActionName);
}

public String getIssueKey() {
return issueKey;
}

public String getWorkflowActionName() {
return workflowActionName;
}

@Extension(optional = true)
public static final class DescriptorImpl extends AbstractStepDescriptorImpl {

public DescriptorImpl() {
super(IssueWorkflowActionStep.StepExecution.class);
}

@Override
public String getFunctionName() {
return "jiraIssueWorkflowStep";
}

@Override
public String getDisplayName() {
return Messages.IssueWorkflowActionStep_Descriptor_DisplayName();
}
}

public static class StepExecution extends AbstractSynchronousNonBlockingStepExecution<Void> {

private static final long serialVersionUID = 1L;

@Inject
private transient IssueWorkflowActionStep step;

@StepContextParameter
private transient TaskListener listener;

@StepContextParameter
private transient Run run;

@Override
protected Void run() throws Exception {
JiraSite site = JiraSite.get(run.getParent());

if (site == null) {
listener.getLogger().println(Messages.Updater_NoJiraSite());
run.setResult(Result.FAILURE);
}

listener.getLogger().println("[JIRA] Migrate issue with key: " + step.getIssueKey() +
" to step: " + step.getWorkflowActionName()
);

try {
long originalStatusId = site.getSession()
.getIssue(step.getIssueKey())
.getStatus()
.getId();

Integer actionId = site.getSession().getActionIdForIssue(step.getIssueKey(), step.getWorkflowActionName());
if (actionId == null) {
throw new RuntimeException("Workflow action does not exists");
}

site.getSession().progressWorkflowAction(step.getIssueKey(), actionId);

long currentStatusId = site.getSession()
.getIssue(step.getIssueKey())
.getStatus()
.getId();

if (originalStatusId == currentStatusId){
listener.getLogger().println(Messages.IssueWorkflowActionStep_Failed());
run.setResult(Result.FAILURE);
}
} catch (Exception e) {
listener.getLogger().println(Messages.IssueWorkflowActionStep_Failed());
e.printStackTrace(listener.getLogger());
run.setResult(Result.FAILURE);
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,9 @@ SearchIssuesStep.Descriptor.DisplayName=JIRA: Search issues
IssueSelectorStep.Descriptor.DisplayName=JIRA: Issue selector
JiraCreateIssueNotifier.DisplayName=JIRA: Create issue
IssueSelector.ExplicitIssueSelector.DisplayName=Explicit selector
IssueSelector.JqlIssueSelector.DisplayName=JQL selector
IssueSelector.JqlIssueSelector.DisplayName=JQL selector
IssueUpdateStep.Descriptor.DisplayName=JIRA: Progress issues by workflow action
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'Move issues through workflow' ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually progress is a good word, hence i suggest "JIRA: Progress issues through workflow"

IssueFieldUpdateStep.Descriptor.DisplayName=JIRA: Update issue field
IssueFieldUpdateStep.Failed=JIRA: Update issue field is failed
IssueWorkflowActionStep.Descriptor.DisplayName=JIRA: Progress single issue by workflow action
IssueWorkflowActionStep.Failed=JIRA: Progress issue is failed
Loading