Copy attachments from one issue to another linked issue

Mihai Mihai
Contributor
October 3, 2019

Hello,

We are trying to copy attachments from one issue to another. The issues should be linked with the "Parent/Child" issue link, and we would probably keep 2 script listeners to always keep the issues in sync between 2 projects.

What we have: 

import com.atlassian.jira.event.issue.IssueEvent
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.attachment.Attachment
import com.atlassian.jira.issue.AttachmentManager
import com.atlassian.jira.ComponentAccessor
import com.atlassian.jira.issue.comments.Comment
import com.atlassian.jira.issue.comments.CommentManager
import com.atlassian.jira.issue.link.IssueLinkManager
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.user.ApplicationUser
import org.ofbiz.core.entity.GenericValue
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue;

def linker = ComponentAccessor.getIssueLinkManager()
def commentMgr = ComponentAccessor.getCommentManager()
def issue = event.issue as MutableIssue
def project = issue.getProjectObject().getKey()
def attachmentManager = ComponentAccessor.getAttachmentManager()


def lastAttachment = attachmentManager.getAttachments(issue).last()

def issueLinks = linker.getOutwardLinks(issue.getId())

def subElements = issueLinks.findAll { it.issueLinkType.name.contains('Parent/Child') && it.destinationObject.getProjectObject().getKey() == 'Test'}

def newComment = event.getComment()
def originalAuthor = newComment.getAuthorApplicationUser()
def commentBody = newComment.getBody()

subElements.each {
if( commentBody != "")
{
commentMgr.create(it.getDestinationObject(), originalAuthor, commentBody , false)
}
}

subElements.each {
if(lastAttachment)
{
attachmentManager.copyAttachment(lastAttachment, originalAuthor, it.getDestinationObject().key)
}
}

 

You can ignore the comments part, but since there isn't an issue attachment added event, I'm not sure how else to do this.

 

The script does not give any errors, but attachments are not copied at all.

 

Thank you in advance!

5 answers

1 accepted

2 votes
Answer accepted
Ravi Sagar _Sparxsys_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 3, 2019

Hi @Mihai Mihai 

As @fran garcia gomera mentioned use the Issue Commented event.

I tried trimmed version of your code and it works perfectly with slight change.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.issue.comments.Comment
import com.atlassian.jira.issue.comments.CommentManager

def issue = event.issue
def attachmentManager = ComponentAccessor.getAttachmentManager()
def lastAttachment = attachmentManager.getAttachments(issue).last()
def links = ComponentAccessor.getIssueLinkManager().getOutwardLinks(issue.getId())
def subElements = links.findAll { it.issueLinkType.name.contains('Parent/Child') }

def newComment = event.getComment()
def originalAuthor = newComment.getAuthorApplicationUser()

//return subElements*.destinationObject

subElements.each { it ->
if (lastAttachment) {

attachmentManager.copyAttachment(lastAttachment, originalAuthor, it.getDestinationObject().key )
}

}

I hope it helps.

Ravi 

Mihai Mihai
Contributor
October 3, 2019

Hello,

 

Thank you for your reply!

 

I used the 'issue commented' event and the script that you added which is much cleaner.

It does not give any errors, but attachments do not get copied to the linked issue. I tried adding an image inside a comment or editing an issue and adding both a comment and a file attachment during that edit.

Do I have to attach the file in a specific way?

Thank you!

Ravi Sagar _Sparxsys_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 3, 2019

Is the name of your Issue Link exactly Parent/Child?

Ravi Sagar _Sparxsys_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 3, 2019

Also check whether the linked issues are outward links or inward links. The code is only checking for outward links.

Mihai Mihai
Contributor
October 3, 2019

Hi @Ravi Sagar _Sparxsys_ 

Thank you for your replies. Yes, the issue link is "Parent/Child" and I am testing it on an outward link.

 

There aren't any errors, but nothing is added. Is there any other test that I can do?

Thank you!

Mihai Mihai
Contributor
October 3, 2019

Hi @Ravi Sagar _Sparxsys_ 

 

I found one issue that somehow did not allow me to add attachments on the server: 

2019-10-04 08:22:47,504 file-attachment-store:thread-41 WARN uidq5816 502x8986x1 1fi5fb7 10.208.247.63 /rest/internal/2/AttachTemporaryFile [c.a.j.issue.managers.DefaultAttachmentManager] Unable to copy attachment to issue with key TEST-10407. Exception: Unable to create target directory /opt/ea/atlassian/jira-home/data/attachments/TEST/20000/TEST-10407

 

and I corrected that. Now images added inside comments get copied.

However, by attachments, I also think of txt files and zip files added to an issue. Those do not get copied to the other issue. Is there any way to bring those items over as well?

 

Thank you very much!

Mihai Mihai
Contributor
October 3, 2019

Just to be sure you understand what I meant, we would need a listener for this type of attachments, not just the ones added through comments:

 

Screenshot_3.png

1 vote
fran garcia gomera
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 3, 2019

Don't understand exactly the problem, if you choose custom listener there is an issue commented event that will launch your script.

listener.png

0 votes
Amit Shaw September 28, 2023

Hi Ravi, 

I am using your above formula bet still getting the error. 

Capture.PNG

I am using the script in automation and want that when ever any comments has been added in the ticket it will check if that attachment has been already there or not. if not then only it will attach it in the linked issues ticket. 

 

Please help me resolve this. 

0 votes
Mihai Mihai
Contributor
October 4, 2019

Hello,

Actually, it was never working. 

The one that was adding the comments with the images was the previous one that I initially added in the description and it was not disabled.

Mihai Mihai
Contributor
October 4, 2019

Hello @Ravi Sagar _Sparxsys_ 

Correction - it does work, but only with images, not with any other file type.

Like Ravi Sagar _Sparxsys_ likes this
Ravi Sagar _Sparxsys_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 4, 2019

Not sure why it is not working for other files. I tried the same code with txt files. I will check.

Like Mihai Mihai likes this
Mihai Mihai
Contributor
October 4, 2019

Hi @Ravi Sagar _Sparxsys_ 

I think I found something useful.

The problem might be coming from the "last" function, which does not seem to actually look for the last attachment.

For example, if an issue already has multiple attachments, it will only work with images for me.

If on an issue with 0 attachments I add any kind of file, then that one is copied to the linked issue.

Doing some more research I found on a thread that last does not work because attachments are sorted alphabetically.

 

Do you know any work-around for this?

 

Thank you!

Mihai Mihai
Contributor
October 4, 2019

Reproduction steps:

 

Issue a > parent of > Issue b

both issue a and issue b have 0 attachments.

add to issue b an attachment named:  "abc" >>> it gets copied to issue a

add another attachment to issue b, named: "xyz" >>> it gets copied to issue a

add any other attachment to issue B, that has the name starting with any letter of the alphabet < x . ("lmn" >>> new attachment does not get copied anymore, xyz still does because it is last.

Mihai Mihai
Contributor
October 8, 2019

Hi @Ravi Sagar _Sparxsys_ 

 

Please let me know if you know any possible solution for this situation.

 

Thank you!

Like Ravi Sagar _Sparxsys_ likes this
Ravi Sagar _Sparxsys_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 8, 2019

Let me try to reproduce your last scenario.

Ravi Sagar _Sparxsys_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 8, 2019

Hi @Mihai Mihai 

I was able to reproduce. The attachments are listed using their file names but we can sort them using their created date before fetching the last one.

Try this code.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.issue.comments.Comment
import com.atlassian.jira.issue.comments.CommentManager

def issue = event.issue
def attachmentManager = ComponentAccessor.getAttachmentManager()
def sortAttachment = attachmentManager.getAttachments(issue).sort { it.created }
def lastAttachment = sortAttachment.last()
def links = ComponentAccessor.getIssueLinkManager().getOutwardLinks(issue.getId())
def subElements = links.findAll { it.issueLinkType.name.contains('Parent/Child') }

def newComment = event.getComment()
def originalAuthor = newComment.getAuthorApplicationUser()

//return subElements*.destinationObject

subElements.each { it ->
if (lastAttachment) {

attachmentManager.copyAttachment(lastAttachment, originalAuthor, it.getDestinationObject().key )
}

}

Please note this code will copy attachment based on outward links which is in this case parent to child.

The main change is the following piece of code.

def sortAttachment = attachmentManager.getAttachments(issue).sort { it.created }
def lastAttachment = sortAttachment.last()

I hope it helps.

Let me know.

Ravi 

Like Mihai Mihai likes this
Mihai Mihai
Contributor
October 8, 2019

Hi @Ravi Sagar _Sparxsys_ 

This works indeed!

It copies the attachment, however, on each new comment, added to the linked issue, and it would be great if it could check if the attachment was either already copied or if it already exists on the other issue.

I think the filename should be a good enough indicator, probably together with the file type.

Please let me know if that is possible.

 

Thank you!

Ravi Sagar _Sparxsys_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 9, 2019

Hi @Mihai Mihai 

Yes it can be done, before copying the attachment fetch the list of attachments of the linked issue, you will get a list. So before copying search for the file name in that list using list.contains("filename.txt"). If the attachment is found then don't copy.

Trying writing the code.

Ravi

Mihai Mihai
Contributor
October 9, 2019

Hi @Ravi Sagar _Sparxsys_ 

Thank you for your reply.

I am sure I am missing something here, because it still always adds the attachment on each comment:

 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.issue.comments.Comment
import com.atlassian.jira.issue.comments.CommentManager

def issue = event.issue
def attachmentManager = ComponentAccessor.getAttachmentManager()
def sortAttachment = attachmentManager.getAttachments(issue).sort { it.created }
def lastAttachment = sortAttachment.last()
def listOfCurrentIssueAttachments = attachmentManager.getAttachments(issue)
def links = ComponentAccessor.getIssueLinkManager().getOutwardLinks(issue.getId())
def subElements = links.findAll { it.issueLinkType.name.contains('Parent/Child') && it.destinationObject.getProjectObject().getKey() == 'TEST'}

def newComment = event.getComment()
def originalAuthor = newComment.getAuthorApplicationUser()

//return subElements*.destinationObject

subElements.each { it ->
if (!listOfCurrentIssueAttachments.contains(lastAttachment.filename)) {

attachmentManager.copyAttachment(lastAttachment, originalAuthor, it.getDestinationObject().key )
}

}

 

What I added is:

 

def listOfCurrentIssueAttachments = attachmentManager.getAttachments(issue)

and the change in the if statement.

 

Thank you.

Mihai Mihai
Contributor
October 13, 2019

Hi @Ravi Sagar _Sparxsys_ 

Please help with the solution for copying attachments only if they don't already exist on the linked issue.

 

Thank you!

Mihai Mihai
Contributor
October 14, 2019

Hi @Ravi Sagar _Sparxsys_ 

 

I also tried with:

 


def issue = event.issue
def attachmentManager = ComponentAccessor.getAttachmentManager()
def sortAttachment = attachmentManager.getAttachments(issue).sort { it.created }
def lastAttachment = sortAttachment.last()
def listOfCurrentIssueAttachments = attachmentManager.getAttachments(issue)
def links = ComponentAccessor.getIssueLinkManager().getOutwardLinks(issue.getId())
def subElements = links.findAll { it.issueLinkType.name.contains('Parent/Child') && it.destinationObject.getProjectObject().getKey() == 'TEST'}

def newComment = event.getComment()
def originalAuthor = newComment.getAuthorApplicationUser()

if (listOfCurrentIssueAttachments) {

listOfCurrentIssueAttachments.collect { it.filename }
subElements.each { it ->
if (!listOfCurrentIssueAttachments.contains(lastAttachment.filename)) {

attachmentManager.copyAttachment(lastAttachment, originalAuthor, it.getDestinationObject().key )
}
}
}

 

but it still copies the attachments. I assume I am not getting the list of attachment names in the right way, but I'm not sure how to do it in a different way.

Any help is appreciated, thank you!

Ravi Sagar _Sparxsys_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 14, 2019

Hi @Mihai Mihai 

How are you fetching the attachments of the linked issues in your code?

Ravi

Mihai Mihai
Contributor
October 14, 2019

Hi @Ravi Sagar _Sparxsys_ 

subElements.each { it ->
if (!listOfCurrentIssueAttachments.contains(lastAttachment.filename)

 

I was thinking that "lastAttachment.filename" in the above section refers to the attachments of the issues that are linked to the current one. 

Ravi Sagar _Sparxsys_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 14, 2019

No lastAttachment is the last attachment of the current issue not the linked issue. Use the same method you used for fetching the attachments but instead of the current issue pass the linked issue object.

Ravi

Mihai Mihai
Contributor
October 14, 2019

Hi @Ravi Sagar _Sparxsys_ 

I believe now I got the linked issue object, but it is still copying the attachment even if it exists:

 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.issue.comments.Comment
import com.atlassian.jira.issue.comments.CommentManager

def issue = event.issue
def attachmentManager = ComponentAccessor.getAttachmentManager()
def sortAttachment = attachmentManager.getAttachments(issue).sort { it.created }
def lastAttachment = sortAttachment.last()
def listOfCurrentIssueAttachments = attachmentManager.getAttachments(issue)
def emptyList = []
def links = ComponentAccessor.getIssueLinkManager().getOutwardLinks(issue.getId())
def subElements = links.findAll { it.issueLinkType.name.contains('Parent/Child') && it.destinationObject.getProjectObject().getKey() == 'TEST'}
def newComment = event.getComment()
def originalAuthor = newComment.getAuthorApplicationUser()

if (listOfCurrentIssueAttachments) {

listOfCurrentIssueAttachments.collect { it.filename }
subElements.each { it ->
def linkedIssue = it.destinationObject
def attachmentsOfLinkedIssues = attachmentManager.getAttachments(linkedIssue).sort { it.created }
def LastAttachmentOfLinkedIssues = attachmentsOfLinkedIssues.last()
if (!listOfCurrentIssueAttachments.contains(LastAttachmentOfLinkedIssues.filename)) {

attachmentManager.copyAttachment(lastAttachment, originalAuthor, it.getDestinationObject().key )
}
}
}

 

Thank you!

Ravi Sagar _Sparxsys_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 15, 2019

Hi @Mihai Mihai 

Here is the complete code that will check whether the filename is already there in the child task (linked) for not.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.issue.comments.Comment
import com.atlassian.jira.issue.comments.CommentManager
import org.apache.log4j.Logger
import org.apache.log4j.Level

def log = Logger.getLogger("com.acme.listener")
log.setLevel(Level.DEBUG)


def issue = event.issue
def attachmentManager = ComponentAccessor.getAttachmentManager()
def sortAttachment = attachmentManager.getAttachments(issue).sort { it.created }
def lastAttachment = sortAttachment.last()
def links = ComponentAccessor.getIssueLinkManager().getOutwardLinks(issue.getId())
def subElements = links.findAll { it.issueLinkType.name.contains('Parent/Child') }

def newComment = event.getComment()
def originalAuthor = newComment.getAuthorApplicationUser()

//return subElements*.destinationObject

subElements.each { it ->

def subAttachmentManager = ComponentAccessor.getAttachmentManager()
def subAttachments = attachmentManager.getAttachments(it.getDestinationObject())

log.debug("Last Attachment from parent: " + lastAttachment.filename)
log.debug("Sub Attachments: " + subAttachments*.filename)
log.debug("Last Attachment already there before copy?: " + subAttachments*.filename.contains(lastAttachment.filename))

if (lastAttachment && !subAttachments*.filename.contains(lastAttachment.filename)) {

attachmentManager.copyAttachment(lastAttachment, originalAuthor, it.getDestinationObject().key )
}




}

You were quite close in your code but you were looking at the last attachment in your linked issue whereas you have to look into all the attachments. Also you have to search based on the file name using subAttachments*.filename.

I hope it is clear now.

Have a nice day.

Ravi

Like # people like this
Mihai Mihai
Contributor
October 15, 2019

Thank you very much @Ravi Sagar _Sparxsys_  !

Suggest an answer

Log in or Sign up to answer