It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

How to move an issue programatically to a different project

I am trying to create a script in a post-function that will move the issue to a different project. I first tried to just set the project object on the issue to the new project, but that just changed it's project, but did not change the Issue Key to match the Project Key.

Can anyone help me get an issue moved to a new project?

Here is the meat of the code that i have so far which moves the issue and sets the correct workflow and status. I will build more validation into it later for our uses, but this is just to get the bulk of it working first:

Map targetProjectMap = (Map)issue.getCustomFieldValue(targetProjectCF)
log.debug("targetProject: " + targetProjectMap)
log.debug("Keys: " + targetProjectMap.keySet())
log.debug("values: " + targetProjectMap.values())
String targetProjectName = (String)targetProjectMap.get("name")
String targetProjectKey = (String)targetProjectMap.get("key")
log.debug("targetProjectName: " + targetProjectName)
Project targetProject = projectManager.getProjectObjByKey(targetProjectKey)
Project sourceProj = issue.projectObject
if (targetProject.name != sourceProj.name){
	//Project destProj = projectManager.getProjectObjByName(targetProject)
	log.debug("Project: " + targetProject.name)
	mutableIssue.setProjectObject(targetProject)
	//Do i need to set workflow?
	IssueType issueType = issue.issueTypeObject
	log.debug("IssueType: " + issueType.name + " : " + issueType.id)
	String WFname = workflowSchemeManager.getWorkflowName(targetProject,issueType.id)
	log.debug("Workflow Name: " + WFname)
	workflowManager.migrateIssueToWorkflow(mutableIssue,workflowManager.getWorkflow(WFname),statusManager.getStatus("1"))
}

15 answers

you can't move issues to another project using programatically, there is no api

check the following issue

https://jira.atlassian.com/browse/JRA-16494

It's not true that there's no API, otherwise you wouldn't be able to it in jira via the web UI. There may be no @PublicApi, but that's not the same.

I tried this together a while ago and came unstuck somewhere. Actually I can't even remember if I came unstuck but I presume I did otherwise I would have probably made it into a built-in script.

I normally hack some code together to prove the concept, and that's as far as I got. Perhaps it will help you. BTW the ctor arguments appear to be out of date for the latest version. The fact that this is not available in any @PublicApi should probably be enough to put you off, unless you have a taste for adventure.

https://gist.github.com/jamieechlin/5213550

I'll look into this to see if i can figure out how to do this in a workflow post-function. It is a very specific use case for us, so i should be able to limit the possible problems since i will be controlling the specific projects, issue types, etc, that this script will activate on. I'll post more when i figure out more.

I tried your code as a script-runner post-function, Jamie. But I am getting a NullPointerExeption inside MoveIssueConfirm.doExecute():

java.lang.NullPointerException
        at com.atlassian.jira.web.action.issue.MoveIssueConfirm.moveIssueDetails(MoveIssueConfirm.java:361)
        at com.atlassian.jira.web.action.issue.MoveIssueConfirm.moveIssueInTxn(MoveIssueConfirm.java:301)
        at com.atlassian.jira.web.action.issue.MoveIssueConfirm.doExecute(MoveIssueConfirm.java:273)

This is JIRA 4.4.4.

For me it looks like the issue-type of the target issue is not set. But trying to set it using

moveIssueConfirm.getFieldValuesHolder().put(IssueFieldConstants.ISSUE_TYPE, targetIssueType)

before calling doExecute() does not help.

Any hints?

Greetings
Hermann

Hello

Using the code of Jamie (see https://answers.atlassian.com/questions/150179/how-to-move-an-issue-programatically-to-a-different-project/150547) I was able to move an issue programmatically!

But unfortunately not in a post-function or a listener. It only works if I am using a custom webwork plugin to add an action to the issue view. Everything happens inside the doExecute() function of the class I associate with that action:

/**
  * The current issue Id is passed to us because we defined
  * the <link> of out <web-item> as
  *  <link linkId="...">/secure/MoveToSPAM.jspa?id=${issueId}</link>
  * in the atlassian-plugin.xml
  */
private String issueId = null;

/**
  * This method is automatically discovered and called by JSP and Webwork
  * if the name matches the id of a parameter passed in an HTML form.
  */
public void setId(String value) {
  this.issueId = value;
}

public String getId() {
  return this.issueId;
}

protected String doExecute() throws Exception {
  Long issueId = 0L;
  try {
     String id = getId();
     if (id != null) {
        issueId = Long.parseLong(id, 10);
     }
     else {
        return "Issue ID is null";
     }
  } catch (Exception e) {
     log.warn("Issue ID is not a Long: "+ e);
     return "Issue ID is not a Long";
  }

  // This is Jamies code with some small changes
  // Not necessary to make things work:
  //CoreTransactionUtil.begin();
  log.debug("Starting to move Issue");

  ComponentManager componentManager = ComponentManager.getInstance();

  ProjectManager projectManager = componentManager.getProjectManager();
  // I want to move it to a designated "SPAM"-Queue
  Project targetProject = projectManager.getProjectObjByKey("SPAM");
  
  IssueManager issueManager = componentManager.getIssueManager();
  MutableIssue targetIssue = issueManager.getIssueObject(issueId);

  MoveIssueBean moveIssueBean = new MoveIssueBean(componentManager.getConstantsManager(), projectManager);
  moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.PROJECT, targetProject.getId());
  moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.ISSUE_TYPE, targetIssue.getIssueTypeObject().getId());
  moveIssueBean.setIssueId(targetIssue.getId());
  moveIssueBean.setTargetStatusId(targetIssue.getStatusObject().getId());

  ActionContext.getSession().put(SessionKeys.MOVEISSUEBEAN, moveIssueBean);
  MoveIssueUpdateFields moveIssueUpdateFields = new MoveIssueUpdateFields(
          componentManager.getIssueLinkManager(),
          componentManager.getSubTaskManager(),
          componentManager.getConstantsManager(),
          componentManager.getWorkflowManager(),
          componentManager.getFieldManager(),
          componentManager.getFieldLayoutManager(),
          componentManager.getIssueFactory(),
          componentManager.getFieldScreenRendererFactory(),
          ComponentManager.getComponentInstanceOfType(CommentService.class),
          ComponentManager.getComponentInstanceOfType(IssueSecurityHelper.class)
  );

  MoveIssueConfirm moveIssueConfirm = new MoveIssueConfirm(
          componentManager.getIssueLinkManager(),
          componentManager.getSubTaskManager(),
          ComponentManager.getComponentInstanceOfType(AttachmentManager.class),
          componentManager.getConstantsManager(),
          componentManager.getWorkflowManager(),
          componentManager.getFieldManager(),
          componentManager.getFieldLayoutManager(),
          componentManager.getIssueFactory(),
          componentManager.getFieldScreenRendererFactory(),
          ComponentManager.getComponentInstanceOfType(CommentService.class),
          ComponentManager.getComponentInstanceOfType(IssueSecurityHelper.class),
          issueManager,
          componentManager.getAttachmentPathManager()
  );

  moveIssueUpdateFields.setId(targetIssue.getId());
  moveIssueUpdateFields.doExecute();

  moveIssueConfirm.setId(targetIssue.getId());
  return moveIssueConfirm.doExecute();
}

So this answers your (and my) question only partially. Or it raises another question: Why does the same code not work in a post-function or a listener of a workflow step? As I said before the problem there is that the move seemsto work but gets reverted/rolled back/undone somehow somewhere later in the process of doing that workflow step!

Should I post a new question regarding this or can we discuss this here?

Greetings

Hermann

I made a small class to reuse this as often as I need.

Make the usage of Moving issue from a project a another project pretty easy. 

Thanks to all the other suggestion on the top. You helped a lot making it possible. 

Works on JIRA 6.2.7 script console, but it doesn't work for Listeners, Post FUnction or Service. That is weird. 
as a service and listeners, it throw a sun reflect error. 

 

import com.atlassian.core.ofbiz.util.CoreTransactionUtil
import com.atlassian.jira.bc.issue.comment.CommentService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.ConstantsManager
import com.atlassian.jira.config.StatusManager
import com.atlassian.jira.config.properties.JiraSystemProperties
import com.atlassian.jira.issue.AttachmentManager
import com.atlassian.jira.issue.IssueFieldConstants
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.issuetype.IssueType
import com.atlassian.jira.issue.security.IssueSecurityHelper
import com.atlassian.jira.project.Project
import com.atlassian.jira.project.ProjectManager
import com.atlassian.jira.web.SessionKeys
import com.atlassian.jira.web.action.issue.MoveIssueConfirm
import com.atlassian.jira.web.action.issue.MoveIssueUpdateFields
import com.atlassian.jira.web.bean.MoveIssueBean
import webwork.action.ActionContext
import java.lang.reflect.Method
import org.apache.log4j.Category
class ProjectIssueMove {
    Category log = Category.getInstance(ProjectIssueMove.class)
    public void moveIssueToAnotherProject(String newProjectKey, String newIssueType, String newStatusName, MutableIssue targetIssue) {
        try {
            MoveIssueBean moveIssueBean = new MoveIssueBean(ComponentAccessor.getConstantsManager(), ComponentAccessor.getProjectManager())
            moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.PROJECT, getProjectFromKey(newProjectKey).id)
            moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.ISSUE_TYPE, getTargetIssueType(newIssueType).id)
            moveIssueBean.setIssueId(targetIssue.getId())
            moveIssueBean.setTargetStatusId(getStatusToSet(newStatusName).id)
            moveIssueBean.setSourceIssueKey(targetIssue.getKey())
            moveIssueBean.setUpdatedIssue(targetIssue)
            ActionContext.getSession().put(SessionKeys.MOVEISSUEBEAN, moveIssueBean)
            MoveIssueUpdateFields moveIssueUpdateFields = new MoveIssueUpdateFields(
                    ComponentAccessor.getSubTaskManager(),
                    ComponentAccessor.getConstantsManager(),
                    ComponentAccessor.getWorkflowManager(),
                    ComponentAccessor.getFieldManager(),
                    ComponentAccessor.getFieldLayoutManager(),
                    ComponentAccessor.getIssueFactory(),
                    ComponentAccessor.getFieldScreenRendererFactory(),
                    ComponentAccessor.getComponentOfType(CommentService.class),
                    ComponentAccessor.getComponentOfType(IssueSecurityHelper.class),
                    ComponentAccessor.getUserUtil()
            )
            MoveIssueConfirm moveIssueConfirm = new MoveIssueConfirm(
                    ComponentAccessor.getSubTaskManager(),
                    ComponentAccessor.getComponentOfType(AttachmentManager.class),
                    ComponentAccessor.getConstantsManager(),
                    ComponentAccessor.getWorkflowManager(),
                    ComponentAccessor.getFieldManager(),
                    ComponentAccessor.getFieldLayoutManager(),
                    ComponentAccessor.getIssueFactory(),
                    ComponentAccessor.getFieldScreenRendererFactory(),
                    ComponentAccessor.getComponentOfType(CommentService.class),
                    ComponentAccessor.getComponentOfType(IssueSecurityHelper.class),
                    ComponentAccessor.getIssueManager(),
                    ComponentAccessor.getUserUtil()
            )
            
            JiraSystemProperties.getInstance()
            moveIssueUpdateFields.setId(targetIssue.getId())
            Method privateUpdateFieldsDoExecute = MoveIssueUpdateFields.class.getDeclaredMethod("doExecute")
            privateUpdateFieldsDoExecute.setAccessible(true)
            privateUpdateFieldsDoExecute.invoke(moveIssueUpdateFields)
            moveIssueConfirm.setId(targetIssue.getId())
            Method privateIssueConfirmDoExecute = MoveIssueConfirm.class.getDeclaredMethod("doExecute")
            privateIssueConfirmDoExecute.setAccessible(true)
            privateIssueConfirmDoExecute.invoke(moveIssueConfirm)
            CoreTransactionUtil.commit(true)
        }
        catch (Exception e) {
            log.error("move issue failed " + e.message)
        }
    }
    private Project getProjectFromKey(String projectKey) { //get the projectObject from a String key
        ProjectManager projectManager = ComponentAccessor.getProjectManager()
        return projectManager.getProjectObjByKeyIgnoreCase(projectKey)
    }
    def getStatusToSet(String statusName) { //find the status object from a status name
        StatusManager statusManager = ComponentAccessor.getComponentOfType(StatusManager.class)
        def statuses = statusManager.getStatuses()
        def iterator = statuses.iterator()
        while (iterator.hasNext()) {
            def status = iterator.next()
            if (status.getName() == statusName) {
                return status
            }
        }
        return null
    }
    def IssueType getTargetIssueType(String issueTypeName) { //Find the issue type object from a issue type name
        ConstantsManager constantsManager = ComponentAccessor.getConstantsManager()
        def issueTypes = constantsManager.getAllIssueTypeObjects()
        def issuetypeiterator = issueTypes.iterator()
        while (issuetypeiterator.hasNext()) {
            def issueType = issuetypeiterator.next()
            if (issueType.name.toLowerCase() == issueTypeName.toLowerCase()) {
                return issueType
            }
        }
        return null
    }
}

You can make that a bit simpler with: import webwork.util.injection.ObjectFactory def bulkMoveOperation = ObjectFactory.instantiate(MoveIssueUpdateFields.class) as MoveIssueUpdateFields Same for other one. Still quite liable to break between different instances though.

Sorry, I meant versions not instances.

Also looks like it would break if someone is using the rest API as there is no session.

You are right for the API. And Also I have a problem with the fact that running it in a PostFunction or a listener, it freeze the issue (turn around endlessly). A refresh of the issue is needed and the move is complete. can't make this working : def bulkMoveOperation = ObjectFactory.instantiate(MoveIssueUpdateFields.class) as MoveIssueUpdateFields Dunno what I am doing wrong. Should it look like this : def bulkMoveOperation = ObjectFactory.instantiate(MoveIssueUpdateFields.class) as MoveIssueUpdateFields bulkMoveOperation.doExecute() def bulkMoveConfirm = ObjectFactory.instantiate(MoveIssueConfirm.class) as MoveIssueConfirm bulkMoveConfirm.doExecute() I tried to put it in a thread, but since it's a webAction, it doesn't work in a different thread. Thats a shame. It would have been so perfect. If you have any ideas.. It would be really great. Thanks a lot for your time Jamie Michel

I know it's a pretty old one, but it can't hurt to try :)

I use a similar code for the move issue post function and it works good but the issue screen is getting darker and the user need to refresh the page in order to see the updated issue (same as Michel mention)

Did anyone has an idea how it can be resolve?

thanks

Dar

I would take a look at com.atlassian.jira.web.action.issue.MoveIssueUpdateFields and com.atlassian.jira.web.action.issue.MoveIssueConfirm. Agree with Anton's comment though in https://jira.atlassian.com/browse/JRA-16494

True @Jamie Echlin.
I am too working on the same function. just now i am able to see the com.atlassian.jira.web.action.issue.MoveIssue.

but one issue in it is it is not getting loaded into the Idea IDE :( please can you find some way

Add all the jars and classes that ship with jira...

i tried that too. i have the latest verstion of installation. is it working for you?

I am able to import those classes into my script via Idea. However, don't those need to be used differently from the normal classes since they are web actions? I've never used one of those before that i know of, does anyone have any examples of using other web actions that i can reference to help determine how to use these? Thanks.

yes exactly @Adam those classes are used differently through the web UI. i also failde in attempting the above class using a Listener plugin approach.. Any one have any other idea. I feel that i can be achieve with the aproach @jamie have tried.

Map targetProjectMap = (Map)issue.getCustomFieldValue(targetProjectCF)
log.debug("targetProject: " + targetProjectMap)
log.debug("Keys: " + targetProjectMap.keySet())
log.debug("values: " + targetProjectMap.values())
String targetProjectName = (String)targetProjectMap.get("name")
String targetProjectKey = (String)targetProjectMap.get("key")
log.debug("targetProjectName: " + targetProjectName)
Project targetProject = projectManager.getProjectObjByKey(targetProjectKey)
Project sourceProj = issue.projectObject
if (targetProject.name != sourceProj.name){
    //Project destProj = projectManager.getProjectObjByName(targetProject)
    log.debug("Project: " + targetProject.name)
    mutableIssue.setProjectObject(targetProject)
    //Do i need to set workflow?
    IssueType issueType = issue.issueTypeObject
    log.debug("IssueType: " + issueType.name + " : " + issueType.id)
    String WFname = workflowSchemeManager.getWorkflowName(targetProject,issueType.id)
    log.debug("Workflow Name: " + WFname)
    workflowManager.migrateIssueToWorkflow(mutableIssue,workflowManager.getWorkflow(WFname),statusManager.getStatus("1"))

Thank you for that answer (Herman).

It helps me to do it, but for Jira 6.1.3 I had to modify a few things.

Here is my code for changing issue type and Status Id (keeping the same project) :

String EnAttenteStatusId = "1";

		MoveIssueBean moveIssueBean = new MoveIssueBean(ComponentAccessor.getConstantsManager(), ComponentAccessor.getProjectManager());
		moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.PROJECT, _targetIssue.getProjectId());
		moveIssueBean.getFieldValuesHolder().put(IssueFieldConstants.ISSUE_TYPE, _issueTypeId);
		moveIssueBean.setIssueId(_targetIssue.getId());
		moveIssueBean.setTargetStatusId(EnAttenteStatusId);
		moveIssueBean.setSourceIssueKey(_targetIssue.getKey());
		moveIssueBean.setUpdatedIssue(_targetIssue);

		ActionContext.getSession().put(SessionKeys.MOVEISSUEBEAN, moveIssueBean);
		MoveIssueUpdateFields moveIssueUpdateFields = new MoveIssueUpdateFields(
				ComponentAccessor.getSubTaskManager(),
				ComponentAccessor.getConstantsManager(),
				ComponentAccessor.getWorkflowManager(),
				ComponentAccessor.getFieldManager(),
				ComponentAccessor.getFieldLayoutManager(),
				ComponentAccessor.getIssueFactory(),
				ComponentAccessor.getFieldScreenRendererFactory(),
				ComponentAccessor.getComponentOfType(CommentService.class),
				ComponentAccessor.getComponentOfType(IssueSecurityHelper.class), 
				ComponentAccessor.getUserUtil()
				);


		MoveIssueConfirm moveIssueConfirm = new MoveIssueConfirm(
				ComponentAccessor.getSubTaskManager(),
				ComponentAccessor.getComponentOfType(AttachmentManager.class),
				ComponentAccessor.getConstantsManager(),
				ComponentAccessor.getWorkflowManager(),
				ComponentAccessor.getFieldManager(),
				ComponentAccessor.getFieldLayoutManager(),
				ComponentAccessor.getIssueFactory(),
				ComponentAccessor.getFieldScreenRendererFactory(),
				ComponentAccessor.getComponentOfType(CommentService.class),
				ComponentAccessor.getComponentOfType(IssueSecurityHelper.class),
				ComponentAccessor.getIssueManager(),
				ComponentAccessor.getUserUtil()		
				);

		JiraSystemProperties.getInstance();
		moveIssueUpdateFields.setId(_targetIssue.getId());
		String msg = moveIssueUpdateFields.doExecute();
		log.debug("moveIssueUpdateFields.doExecute() result = "+msg);

		moveIssueConfirm.setId(_targetIssue.getId());
		msg = moveIssueConfirm.doExecute();
		log.debug("moveIssueConfirm.doExecute() result = "+msg);
		return msg;

Assuming that in your case there are no new required fields for the new issue type, right?

Right. If you have new required fields I think you need to use "moveIssueBean.setFieldValues(fieldValues)" but I have never tried it.

I upgraded to Jira 6.2.7 and the doExecute() are now protected.

How are we suppose to do it now ?

Well I found a different solution (that works inside a post-function or listener) that does not use MoveIssueConfirm et al. but GenericValue and GenericDelegator.

Maybe this works with 6.2.7 as well?

As we have a rather simple setup (one single workflow for all projects, just one issue-type asf), my solution may be of limited use...

Send me a personal mail to hermann.schwaerzler arobas uibk.ac.at if you want me to send you the code I am using.

Finally I did it using java reflection to call the protected methods doExecute().

Hermann Schwärzler, can you share your code, please?

Yes :

replace

String msg = moveIssueUpdateFields.doExecute(); 

and 

msg = moveIssueConfirm.doExecute();

with

// use of java reflection to access protected method since Jira 6.2.7
Method privateUpdateFieldsDoExecute = MoveIssueUpdateFields.class.getDeclaredMethod("doExecute");
privateUpdateFieldsDoExecute.setAccessible(true);
String msg = (String) privateUpdateFieldsDoExecute.invoke(moveIssueUpdateFields);
		

// same thing for moveIssueConfirm
Method privateIssueConfirmDoExecute = MoveIssueConfirm.class.getDeclaredMethod("doExecute");
privateIssueConfirmDoExecute.setAccessible(true);
msg = (String) privateIssueConfirmDoExecute.invoke(moveIssueConfirm);

Suggest an answer

Log in or Sign up to answer
This widget could not be displayed.
This widget could not be displayed.
Community showcase
Posted yesterday in Off-topic

Miscellaneous Monday - Where it all started.

Happy Monday everyone! If you're anything like me, you tend to think a lot about what the future looks like. What are my kids up to in 15 years? Have we solved for climate change? How could TVs poss...

117 views 12 4
Join discussion

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you