1
0
-1

Hi,

In our Exalate - Jira configuration we have a syncing rule we have internal syncing data.

We have in our company R&D developers and agents that support customers.

The workflow is: a customer opens a ticket, the agent gets it and he decides if R&D is needed.

If yes, he opens a new ticket to R&D that is synced with the original customer ticket.

The sync allows R&D to see all the history of the customer request.


Although the sync includes attachments and comments, we have a problem with it. To prevent infinite syncing, we were advised to add a prefix "CON_" to the attachment file names. This addition makes a mess in the attachment list of the issue, and the users don't like it too much. Beyond that, renaming the file disrupts the preview of thumbnails in the issue comments. 


Is there another way to prevent infinite syncing without renaming the files? Maye be changing a different property of the attachments?


Thanks

  1. Serhiy Onyshchenko

    Hello, Elyashiv ,  as far as I understand, your sync set up was following this guide: https://docs.idalko.com/exalate/x/xoCKAg
    And according ti this picture,

    the addition of the file prefix happens in the Internal Service Desk issue, right?
    Later on on the same instance you use these prefixes to avoid syncing attachments from Dev A to Dev B and visa-versa, correct?

    If all of my suggestions were right so far, might I ask you to share the outgoing sync scripts that you have on the Internal Service Desk side, please?
    I'll suggest a script that doesn't rely on file prefixes but instead uses Exalate's own sync metadata to make sure it doesn't sync the wrong attachments over.

    Regards, Serhiy.

  2. Elyashiv

    Hi Serhiy Onyshchenko ,

    Your suggestions are correct, this is our case.


    This is the relevant code from the relevant rule  of the outgoing synchronization of attachments:

    And this is the relevant code from the incoming sync rule:


    We have a few internal syncing rules and in each one, we have a different prefix: "CON1_" "CON2_" etc.




  3. Serhiy Onyshchenko

    Hey, Elyashiv ,
    instead of filtering by filename, we could use internal sync link metadata which describes, how attachments and comments relate to one another...
    Please, try replacing (in Outgoing sync) your current :

    replica.attachments = issue.attachments.findAll { attachment ->
        attachment.filename.contains("CON1") ||
    	!attachment.filename.contains("CON")
    }

    with

    if (issue.key == "TEST-1") {
        def syncedAttachmentIds = nodeHelper
                .twinTraceRepository
                .findByLocalIssueKey(issueKey)
                .inject([]) { result, tt ->
                    if (tt.connection.ID == connection.ID) { return result }
                    def localAttachmentIds = tt
                            .fieldTraces
                            .get(com.exalate.api.domain.twintrace.TraceType.ATTACHMENT)
                            .localId
                    result.addAll(localAttachmentIds)
                    result
                }
    
        replica.attachments = issue.attachments.findAll { a ->
            !syncedAttachmentIds.contains(a.id as String)
        }
    } else {
    	replica.attachments = issue.attachments.findAll { attachment ->
        	attachment.filename.contains("CON1") ||
    		!attachment.filename.contains("CON")
    	}
    }
    


    And for Incoming sync, replace:

    if(issue.key == "TEST-1") {
     	issue.attachments += replica.addedAttachments
    } else {
    	issue.attachments += replica.addedAttachments.collect { attachment ->
        	attachment.filename = "CON1"+attachment.filename
    		attachment
    	}
    }



    Note that I used TEST-1 as an example, replace it with an issue key which you'd like to test this out on


    Regards, Serhiy.

  4. Elyashiv

    Hi Serhiy Onyshchenko,

    Thanks for your answer!

    Unfortunately, it didn't work as expected.

    R&D ticket (Dev A) didn't get the attachments at all - from the customer ticket (Internal SD).


  5. Elyashiv

    Hi Serhiy Onyshchenko ,

    Do you have any other solution?


    Thanks

  6. Serhiy Onyshchenko

    Hello, Elyashiv , sorry for being so slow on turn around. Feel free to ping Saskia so that she chases me (smile)
    Let's troubleshoot if the suggested approach can work

    if (issue.key == "TEST-1") {
        def syncedAttachmentIds = nodeHelper
                .twinTraceRepository
                .findByLocalIssueKey(issueKey)
                .inject([]) { result, tt ->
                    if (tt.connection.ID == connection.ID) { return result }
                    def localAttachmentIds = tt
                            .fieldTraces
                            .get(com.exalate.api.domain.twintrace.TraceType.ATTACHMENT)
                            .localId
                    result.addAll(localAttachmentIds)
                    result
                }
        debug.error("issue.attachments.id=${issue.attachments.id} syncedAttachmentIds=$syncedAttachmentIds")
    
        replica.attachments = issue.attachments.findAll { a ->
            !syncedAttachmentIds.contains(a.id as String)
        }
    } else {
    	replica.attachments = issue.attachments.findAll { attachment ->
        	attachment.filename.contains("CON1") ||
    		!attachment.filename.contains("CON")
    	}
    }
  7. Elyashiv

    Hi Serhiy Onyshchenko ,

    The script didn't run until the debug line.

    It stopped with error:


    Script error for issue TMH1-23. Details: Cannot get property 'localId' on null object. 


    referring to the line:


    def localAttachmentIds = tt
       						.fieldTraces
                            .get(com.exalate.api.domain.twintrace.TraceType.ATTACHMENT)
                            .localId





  8. Serhiy Onyshchenko

    Huh, let's try and fix that:

    if (issue.key == "TEST-1") {
        def syncedAttachmentIds = nodeHelper
                .twinTraceRepository
                .findByLocalIssueKey(issueKey)
                .inject([]) { result, tt ->
                    if (tt.connection.ID == connection.ID) { return result }
                    def localAttachmentIds = tt
                            .fieldTraces
                            .get(com.exalate.api.domain.twintrace.TraceType.ATTACHMENT)
                            ?.localId ?: []
                    result.addAll(localAttachmentIds)
                    result
                }
        debug.error("issue.attachments.id=${issue.attachments.id} syncedAttachmentIds=$syncedAttachmentIds")
    
        replica.attachments = issue.attachments.findAll { a ->
            !syncedAttachmentIds.contains(a.id as String)
        }
    } else {
    	replica.attachments = issue.attachments.findAll { attachment ->
        	attachment.filename.contains("CON1") ||
    		!attachment.filename.contains("CON")
    	}
    }

    Please, let me know, how that goes.

  9. Elyashiv

    Hi Serhiy Onyshchenko 

    this is the error now:

    Script error for issue TMH1-23. Details: No such property: debug for class: Script12954. Error line: Script12954.groovy:38
  10. Elyashiv

    Hi Serhiy Onyshchenko ,
    We still have an issue.

    referring to this scheme:


    If we add an attachment to the "Dev A" issue (using a comment or regularly attaching an attachment to the issue) we will see it in all connected issues (Internal Service Desk + Dev A + Dev B).

    We are supposed to see the attachment only on "Dev A" & "Internal Service Desk" issues. Any other "Dev" issues are not supposed to get the attachment.

    I will add that if you comment "Dev A" you will see the comment only on the "Internal Service Desk" issue.


    Can you assist?

  11. Serhiy Onyshchenko

    Hey, Elyashiv , seeing my snail-speed at helping you, let me take another approach and take my time to set it up in my env to give you a video and full description of the set up to get it working. Let me get back to you with an update on this Monday Jan 17th.

  12. Serhiy Onyshchenko

    Hey, Elyashiv , a small update: got the case setup, starting to run the debug.error I suggested earlier to see what I'll find.
    Let me give an update on my findings Tomorrow

  13. Serhiy Onyshchenko

    Hey, Elyashiv 
    So the suggestion with the traces doesn't seem to work, as Exalate creates traces after attaching the attachments. I'll be looking into other places - entity properties. Let me come back to you on Thursday with an update.

  14. Serhiy Onyshchenko

    Hey, Elyashiv , looking into options to store multiple records for the issue so that Exalate knows that a certain attachment comes from a certain connection.

    Currently exploring this option:
    https://docs.atlassian.com/software/jira/docs/api/8.13.16/com/atlassian/jira/entity/property/EntityPropertyService.html#setProperty-com.atlassian.jira.user.ApplicationUser-com.atlassian.jira.entity.property.EntityPropertyService.SetPropertyValidationResult-

    Regards, Serhiy.

  15. Serhiy Onyshchenko

    Hello, Elyashiv , let me give you an update on Wed 26ths re the entity property option

  16. Serhiy Onyshchenko

    Hey, Elyashiv , apologies for delayed update, the property service approach bears fruit - already able to note stuff to the issue property before the attachments are actually attached to the issue. Next update on Tue Feb 1st.

  17. Elyashiv

    Hi Serhiy Onyshchenko ,

    I will wait.

    Thanks in meantime.

CommentAdd your comment...

1 answer

  1.  
    1
    0
    -1

    Hello, Elyashiv 
    Here's a solution (smile)


    SD_to_DEV1SD_to_DEV2
    ​Out
    ...
    replica.resolution     = issue.resolution
    replica.status         = issue.status
    replica.parentId       = issue.parentId
    replica.priority       = issue.priority
    
    if (issue.project.key == "SDT") {
        def _IssuePropertyService = com.atlassian.jira.component.ComponentAccessor.getClassLoader().loadClass("com.atlassian.jira.bc.issue.properties.IssuePropertyService")
        // com.atlassian.jira.bc.issue.properties.IssuePropertyService ips = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.bc.issue.properties.IssuePropertyService.class)
        def ips = com.atlassian.jira.component.ComponentAccessor.getComponent(_IssuePropertyService)
        def nserv = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class)
        def errorCollectionToStr = { ec ->
            "${ec.errorMessages.join(", ")} ; ${ec.errors.collect { k, v -> "$k:$v" }.join(", ")}"
        }
        def proxyAppUser = nserv.getProxyUser()
        
        def propsResult = ips.getProperties(
            proxyAppUser,
            issue.id as Long
        )
        def js = new groovy.json.JsonSlurper()
        def addedAttachments = propsResult
                .collect { prop ->
                    if (prop.key.startsWith("exalate-attachment-added")) js.parseText(prop.value)
                    else null
                }
                .findAll()
                .flatten()
        //debug.error("addedAttachments=$addedAttachments")
        replica.attachments = issue.attachments.findAll { a ->
            !addedAttachments.any { addedAttachment ->
                addedAttachment.filename.equals(a.filename) && addedAttachment.filesize == a.filesize
            }
        }
    
        replica.comments = issue.comments.findAll { comment ->
                            comment.author.username == "SyncCon1" ||
                            !comment.author.username.contains("SyncCon")
                         } 
    } else {
        replica.comments = issue.comments
        replica.attachments    = issue.attachments
    }
    ...

    ...
    
    if (issue.project.key == "SDT") {
        replica.comments = issue.comments.findAll { comment ->
            comment.author.username == "SyncCon2" ||
            !comment.author.username.contains("SyncCon")
        }
                         
        def _IssuePropertyService = com.atlassian.jira.component.ComponentAccessor.getClassLoader().loadClass("com.atlassian.jira.bc.issue.properties.IssuePropertyService")
        // com.atlassian.jira.bc.issue.properties.IssuePropertyService ips = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.bc.issue.properties.IssuePropertyService.class)
        def ips = com.atlassian.jira.component.ComponentAccessor.getComponent(_IssuePropertyService)
        def nserv = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class)
        def errorCollectionToStr = { ec ->
            "${ec.errorMessages.join(", ")} ; ${ec.errors.collect { k, v -> "$k:$v" }.join(", ")}"
        }
        def proxyAppUser = nserv.getProxyUser()
        
        def propsResult = ips.getProperties(
            proxyAppUser,
            issue.id as Long
        )
        def js = new groovy.json.JsonSlurper()
        def addedAttachments = propsResult
                .collect { prop ->
                    if (prop.key.startsWith("exalate-attachment-added")) js.parseText(prop.value)
                    else null
                }
                .findAll()
                .flatten()
        //debug.error("addedAttachments=$addedAttachments")
        replica.attachments = issue.attachments.findAll { a ->
            !addedAttachments.any { addedAttachment ->
                addedAttachment.filename.equals(a.filename) && addedAttachment.filesize == a.filesize
            }
        }
    
        
    } else {
        replica.comments = issue.comments
        replica.attachments    = issue.attachments
    }
    ...

    In
    ...
    if (replica.project.key == "AAA") {
        issue.comments     = commentHelper.mergeComments(issue, replica, {
                                comment ->
                                comment.roleLevel = "Team"
                                comment.executor = nodeHelper.getUserByUsername("SyncCon1")
                                comment
            })
    
    
    
        def _IssuePropertyService = com.atlassian.jira.component.ComponentAccessor.getClassLoader().loadClass("com.atlassian.jira.bc.issue.properties.IssuePropertyService")
        //com.atlassian.jira.bc.issue.properties.IssuePropertyService ips = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.bc.issue.properties.IssuePropertyService.class)
        def ips = com.atlassian.jira.component.ComponentAccessor.getComponent(_IssuePropertyService)
        def nserv = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class)
        def errorCollectionToStr = { ec ->
            "${ec.errorMessages.join(", ")} ; ${ec.errors.collect { k, v -> "$k:$v" }.join(", ")}"
        }
        def proxyAppUser = nserv.getProxyUser()
        def nowMilli = java.time.Instant.now().toEpochMilli()
        
        def json = ({
            def attachmentJsons = replica
                .addedAttachments
                .inject([]) { result, attachment ->
                    result += ["filename":attachment.filename,"filesize":attachment.filesize]
                    result
                }
            if (attachmentJsons.empty) return null
            else groovy.json.JsonOutput.toJson(attachmentJsons)
        })()
        if (json) {
            def setPropValResult = ips.validateSetProperty(
                    proxyAppUser,
                    issue.id as Long,
                    new com.atlassian.jira.entity.property.EntityPropertyService.PropertyInput(json, "exalate-attachment-added-$nowMilli")
            )
            if (setPropValResult.valid) {
                ips.setProperty(proxyAppUser, setPropValResult)
            } else {
                debug.error("Failed to set a property `${"exalate-attachment-added-${connection.name}-$nowMilli"}` to issue ${issue.key} (${issue.id}): ${errorCollectionToStr(setPropValResult.errorCollection)}")
            }
        }
    
    
        //debug.error("blocking sync to try reading / writing via properties API")
    }
    issue.attachments  = attachmentHelper.mergeAttachments(issue, replica)
    ...
    
    
    ...
    if (replica.project.key == "BBB") {
        issue.comments     = commentHelper.mergeComments(issue, replica, {
                                comment ->
                                comment.roleLevel = "Team"
                                comment.executor = nodeHelper.getUserByUsername("SyncCon2")
                                comment
            })
    
    
    
        def _IssuePropertyService = com.atlassian.jira.component.ComponentAccessor.getClassLoader().loadClass("com.atlassian.jira.bc.issue.properties.IssuePropertyService")
        //com.atlassian.jira.bc.issue.properties.IssuePropertyService ips = com.atlassian.jira.component.ComponentAccessor.getComponent(com.atlassian.jira.bc.issue.properties.IssuePropertyService.class)
        def ips = com.atlassian.jira.component.ComponentAccessor.getComponent(_IssuePropertyService)
        def nserv = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class)
        def errorCollectionToStr = { ec ->
            "${ec.errorMessages.join(", ")} ; ${ec.errors.collect { k, v -> "$k:$v" }.join(", ")}"
        }
        def proxyAppUser = nserv.getProxyUser()
        def nowMilli = java.time.Instant.now().toEpochMilli()
        
        def json = ({
            def attachmentJsons = replica
                .addedAttachments
                .inject([]) { result, attachment ->
                    result += ["filename":attachment.filename,"filesize":attachment.filesize]
                    result
                }
            if (attachmentJsons.empty) return null
            else groovy.json.JsonOutput.toJson(attachmentJsons)
        })()
        if (json) {
            def setPropValResult = ips.validateSetProperty(
                    proxyAppUser,
                    issue.id as Long,
                    new com.atlassian.jira.entity.property.EntityPropertyService.PropertyInput(json, "exalate-attachment-added-$nowMilli")
            )
            if (setPropValResult.valid) {
                ips.setProperty(proxyAppUser, setPropValResult)
            } else {
                debug.error("Failed to set a property `${"exalate-attachment-added-${connection.name}-$nowMilli"}` to issue ${issue.key} (${issue.id}): ${errorCollectionToStr(setPropValResult.errorCollection)}")
            }
        }
    
        issue.attachments  = attachmentHelper.mergeAttachments(issue, replica)
        //debug.error("blocking sync to try reading / writing via properties API")
    } else {
        issue.comments     = commentHelper.mergeComments(issue, replica)
        issue.attachments  = attachmentHelper.mergeAttachments(issue, replica)
    }
    ...
    
    


    Happy Exalating!

      CommentAdd your comment...