Service Desk Customer issues and comments do not sync - no permission to create

Originally asked by Jiri Kanicky on 04 August 2020 (original question)


Environment:

2 Jira Servers

  • Jira Service Desk Source Project (CD)
  • Jira Service Desk Destination Project (GCD)

Configuration:

Both Projects are identical in terms of permissions setup. Both projects use Service Desk Customers Role for Customers (users without Jira License).

Customer users are same on both Jira. Password is different. (not user if password needs to be same)

Incoming sync rule:

issue.comments = commentHelper.mergeComments(issue, replica){ it.executor = nodeHelper.getUserByEmail(it.author?.email) }

Problem:

Issues opened by customer in CD are unable to sync to GCD.

There are two sub-issues:

  1. Exalate canont create ticket with the following error:

Error 1:

Exalate has problems while trying to create an issue in this Jira. Details: [InvalidInputException: [Error map: [{}]] [Error list: [[User ‘customer@customer.com’ doesn’t have the ‘Create Issues’ permission]]]

2. If we add the customer to Create Issues permission scheme, the issue is created, but get comment error.

Error 2:

It was not possible to create service desk comment from createParams `BasicCommentParameters{executor=customer@customer.com (JIRAUSER39169), commentId=null, created=2020-08-04 09:06:12.653, body=’ [^TemenosBNKReleaseDocSSOWCF3rdAug2020.docx] _(420 kB)_ [^TemenosBNKReleaseDocStewie3rdAug2020.docx] _(420 kB)_', groupName=‘null’, roleLevelId=null, issue=GCD-762, commentProperties={exalate.comment.creator={“byExalate”:true}}, createNotification=false}` The Service Desk Error: `null`, message `You don’t have permission to access this Service Desk.`


Answer by Jiri Kanicky on 04 August 2020

Hi.

Incoming sync quite long (I had to reduct it a bit). It all works, just the permission is the issue.

if(firstSync){
   // issue.projectKey   = "GCD"
   // Set type name from source issue, if not found set a default
   // issue.typeName     = nodeHelper.getIssueType(replica.typeName)?.name ?: "Task"
   if(replica.type.name == "Admin Reporting") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "COB") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Data Upload") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Decommission Server") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Delivery Admin") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Dev/Test Setup") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Documentation") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "DR Setup") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "ETL") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "File Request") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Go-Live Checklist") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Incident") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Integration") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "License Update") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }   
   else if (replica.type.name == "Network Request") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Non-Prod Setup") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Presales") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Prod Setup") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Restart Services") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Risk") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Security Task") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Server Spec") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Task") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Tuning") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Upgrades / Monthly Builds") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Task"
   }
   else if (replica.type.name == "Environment Refresh") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Environment Refresh"
   }
   else if (replica.type.name == "Backup – Appl. & DB") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Backup - Appl. & DB Data"
   }
   else if (replica.type.name == "Release") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Release"
   }
   else if (replica.type.name == "Sub-task") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Sub-task"
   }
   else if (replica.type.name == "User Access") {
       issue.projectKey   = "GCD"
       issue.typeName     = "User Access"
   }
   else if (replica.type.name == "Change Request") {
       issue.projectKey   = "GCD"
       issue.typeName     = "Change Request"
   }
  
  // set date when the issue is created during sync
  // issue.customFields."Migration Date".value = "Date"
}


if(firstSync && replica.parentId){
    issue.typeName     = "Sub-task" //Make sure to use the right subtask type here.
    def localParent = nodeHelper.getLocalIssueFromRemoteId(replica.parentId.toLong())
    if(localParent){
        issue.parentId = localParent.id
    } else {
       throw new com.exalate.api.exception.IssueTrackerException("Subtask cannot be created: parent issue with remote id " + replica.parentId + " was not found. Please make sure the parent issue is synchronized before resolving this error" )
    }
}


// Organizations sync

//add organization names into the Text Field
issue.customFields."Migration Details".value = issue.customFields."Migration Details".value +"\n"+ "Organizations: " + replica.customKeys."Organization Names"?.join(",")

// Assing Organization to variable that it can be processed in if else list later. Assuming that only 1 organization exists per ticket
def orgName = replica.customKeys."Organization Names"?.join(",")

if (orgName == "BNE"){
    issue.customFields.Organizations.value = "GCD-Alex Technologies Pty Ltd"
}



//CRM Company assign based on Customer (Cloud Imp)
if (orgName.isEmpty()) {
    
    def customerCloud1 = replica.customFields."Customer (Cloud Imp)".value.value
    
    def customerCloud = customerCloud1?.join("")
    
    issue.customFields."Migration Details".value = issue.customFields."Migration Details".value +"\n" + "orgName is emty"
    issue.customFields."Migration Details".value = issue.customFields."Migration Details".value +"\n" + "Customer (Cloud Imp): " + customerCloud?.join("")
    
    if (customerCloud == "BNE"){
        issue.customFields."Migration Details".value = issue.customFields."Migration Details".value +"\n" + "if condition for BNE executed"
        issue.customFields."CRM Company".value = "XXX"
    }
    
}


// Customer Request type
//if (replica.key == "CD-9160"){
    // def customerRequestType = replica.customFields."Customer Request Type".value
//}

def issueType = replica.type.name

if (issueType == "Release"){
    issue.customFields."Customer Request Type".value = "Release/Change"
}
else if (issueType == "Documentation"){
    issue.customFields."Customer Request Type".value = "Documentation"
}
else if (issueType == "Incident"){
    issue.customFields."Customer Request Type".value = "Incident"
}
else if (issueType == "User Access"){
    issue.customFields."Customer Request Type".value = "User Access"
}
else if (issueType == "Restart Services"){
    issue.customFields."Customer Request Type".value = "Restart Services"
}
else if (issueType == "Integration"){
    issue.customFields."Customer Request Type".value = "Integration"
}
else if (issueType == "File Request"){
    issue.customFields."Customer Request Type".value = "Data Extract - Files / Logs"
}
else if (issueType == "Data Upload"){
    issue.customFields."Customer Request Type".value = "Data Upload"
}
else if (issueType == "Environment Refresh"){
    issue.customFields."Customer Request Type".value = "Environment Refresh"
}
else if (issueType == "Backup - Appl. & DB Data"){
    issue.customFields."Customer Request Type".value = "Backup - Appl. & DB Data"
}
else if (issueType == "COB"){
    issue.customFields."Customer Request Type".value = "COB"
}
else if (issueType == "ETL"){
    issue.customFields."Customer Request Type".value = "ETL"
}
else if (issueType == "Network Reqeust"){
    issue.customFields."Customer Request Type".value = "Network Changes"
}
else if (issueType == "Change Request"){
    issue.customFields."Customer Request Type".value = "Change Request (CR)"
}
else if (issueType == "Tuning"){
    issue.customFields."Customer Request Type".value = "Tuning"
}
else if (issueType == "Decomission Server"){
    issue.customFields."Customer Request Type".value = "Decomission Server"
}
else if (issueType == "Admin Reporting"){
    issue.customFields."Customer Request Type".value = "Delivery Reporting"
}
else if (issueType == "Risk"){
    issue.customFields."Customer Request Type".value = "Risk"
}
else if (issueType == "Dev/Test Setup"){
    issue.customFields."Customer Request Type".value = "Dev Test Setup"
}
else if (issueType == "Task"){
    issue.customFields."Customer Request Type".value = "Miscellaneous Request"
}
else if (issueType == "Prod Setup"){
    issue.customFields."Customer Request Type".value = "Prod Setup"
}
else if (issueType == "DR Setup"){
    issue.customFields."Customer Request Type".value = "DR Setup"
}
else if (issueType == "Delivery Admin"){
    issue.customFields."Customer Request Type".value = "Delivery Admin Setup"
}
else if (issueType == "Non-Prod Setup"){
    issue.customFields."Customer Request Type".value = "Non-Prod Setup"
}
else if (issueType == "License Update"){
    issue.customFields."Customer Request Type".value = "License Update"
}
else if (issueType == "Upgrades / Monthly Builds"){
    issue.customFields."Customer Request Type".value = "Upgrades / Monthly Builds"
}
else if (issueType == "Presales"){
    issue.customFields."Customer Request Type".value = "Presales"
}
else if (issueType == "Go-Live Checklist"){
    issue.customFields."Customer Request Type".value = "Go-Live Checklist"
}

//issue.customFields."Migration Details".value = issue.customFields."Migration Details".value +"\n"+ "Customer Request Type: " + customerRequestType
issue.customFields."Migration Details".value = issue.customFields."Migration Details".value +"\n"+ "Issue Type: " + issueType

//issue.customFields."Customer Request Type".value =  replica.customFields."Customer Request Type"


// System fields
issue.summary      = replica.summary
issue.description  = replica.description
issue.labels       = replica.labels
issue.created      = replica.created
issue.resolutiondate    = replica.resolutiondate
issue.attachments  = attachmentHelper.mergeAttachments(issue, replica)

// User Synchronization (Assignee/Reporter)

// Set a Reporter/Assignee from the source side, if the user can't be found set a default user
// You can use this approach for custom fields of type User
//def defaultUser = nodeHelper.getUserByEmail("jirabot_service@temenos.com")
issue.creator = nodeHelper.getUserByUsername(replica.creator?.username)
issue.assignee = nodeHelper.getUserByUsername(replica.assignee?.username)
issue.reporter = nodeHelper.getUserByUsername(replica.reporter?.username)

// Requested Participants
issue.customFields."Request participants"?.value = replica.customFields."Request participants"?.value?.collect { it ->
    nodeHelper.getUserByEmail(it?.email) 
}

 
/*
Comment Synchronization

Sync comments with the original author if the user exists in the local instance
Remove original Comments sync line if you are using this approach
*/
issue.comments = commentHelper.mergeComments(issue, replica){ it.executor = nodeHelper.getUserByEmail(it.author?.email) }
//issue.comments     = commentHelper.mergeComments(issue, replica)
 
/*
Status Synchronization

Sync status according to the mapping [remote issue status: local issue status]
If statuses are the same on both sides don't include them in the mapping
def statusMapping = ["Open":"New", "To Do":"Backlog"]
def remoteStatusName = replica.status.name
issue.setStatus(statusMapping[remoteStatusName] ?: remoteStatusName)
*/
def statusMap = [
 
       // "remote status name": "local status name"
         "Closed" : "Closed",
         "Paused" : "Paused",
         "Triage" : "Triage",
         "Waiting for Customer" : "Waiting for Customer",
         "In Progress"  :   "In Progress",
         "Waiting for Internal 3rd Party"   : "Waiting for Internal 3rd Party",
         "Pending"  :   "Pending",
         "Awaiting approval"    :   "Awaiting Approval",
         "Estimation"   :   "Estimate",
         "Remediating"  :   "In Progress",
         "Scoping"  :   "Scoping"
   ]
def remoteStatusName = replica.status.name
issue.setStatus(statusMap[remoteStatusName] ?: remoteStatusName)

// Priority Mapping
def priorityMapping = [
        // remote side priority <-> local side priority            
          "Blocker" : "Blocker",
          "Critical" : "Critical",
          "Major" : "Medium",
          "Minor" : "Low",
          "Trivial" : "Minor"
    ]
def priorityName = priorityMapping[replica.priority?.name] ?: "Low" // set default priority in case the proper urgency could not be found
issue.priority = nodeHelper.getPriority(priorityName)

// Resolution Mapping
if (replica.resolution == null && issue.resolution != null) {
   // if the remote issue is not resolved, but the local issue is set, then clear the local issue resolution
  
   issue.resolution = null
}
  
if (replica.resolution != null) {
   // the remote issue is resolved, but the local isn't - look up the correct local resolution object.
 
 
   def resolutionMap = [
        "Cannot Reproduce" : "Cannot Reproduce",
        "Current Production Issue" : "Known Error",
        "Data Integrity" :  "Code Fix",
        "Duplicate" : "Duplicate",
        "Fixed" : "Resolved",
        "Incomplete" : "Incomplete",
        "Infrastructure/Environmental" : "Cannot Reproduce",
        "Knowledge/Training" : "Working as Expected",
        "Out of Scope Requirement" : "Feature Request",
        "Raised in Error" : "Declined",
        "Request Completed" : "Resolved",
        "Will not be actioned" : "Won't Do",
        "Done" : "Done",
        "Won't Do" : "Won't Do",
        "Declined" : "Declined"
   ]
  
   // use 'done' as resolution if the remote resolution is not found
   def targetResolutionName = resolutionMap[replica.resolution.name] ?: "Done"
    
   // nodeHelper.getResolution looks up the local resolution object based on the provided name
   issue.resolution = nodeHelper.getResolution(targetResolutionName)
}


/*
Custom Fields

This line will sync Text, Option(s), Number, Date, Organization, and Labels CFs
For other types of CF check documentation
issue.customFields."CF Name".value = replica.customFields."CF Name".value
*/
issue.customFields."Migration Ticket".value = replica.key
issue.customFields."Migration Ticket URL".value = "https://client.rubik.com.au/support/browse/" + replica.key
issue.customFields."Migration Instance".value = "Rubik"

// single selects
if(replica.customFields."Request Type"?.value){
  issue.customFields."User Access Request Type".value = replica.customFields."Request Type".value.value
}
if(replica.customFields."Team Responsible"?.value){
  issue.customFields."Team Responsible".value = replica.customFields."Team Responsible".value.value
}
if(replica.customFields."Cloud Infrastructure"?.value){
  issue.customFields."Cloud Infrastructure".value = replica.customFields."Cloud Infrastructure".value.value
}
if(replica.customFields."User Access Type"?.value){
  issue.customFields."User Access Type".value = replica.customFields."User Access Type".value.value
}
if(replica.customFields."Hosting Solution"?.value){
  issue.customFields."Hosting Solution".value = replica.customFields."Hosting Solution".value.value
}
if(replica.customFields."CR Classification"?.value){
    issue.customFields."CR Classification".value = replica.customFields."CR Classification".value.value
}
if(replica.customFields."CR Type"?.value){
    issue.customFields."CR Type" = replica.customFields."CR Type"
}

// radio
if(replica.customFields."Cloud Security Policy Acknowledgement"?.value){
    issue.customFields."Cloud Security Policy Acknowledgement".value = replica.customFields."Cloud Security Policy Acknowledgement".value.value
}
if(replica.customFields."CR Outcome"?.value){
    issue.customFields."CR Outcome" = replica.customFields."CR Outcome"
}


//dates
issue.due = replica.due
issue.customFields."Rollback Date".value = replica.customFields."Rollback Date".value


// Checkboxes
def checkboxCollection1 = replica.customFields."Env Deployment Checklist".
                                value?.
                                collect{
                                        a->
                                        nodeHelper.getOption (issue, "Env Deployment Checklist", a.value)
                                        }
issue.customFields."Env Deployment Checklist".value = checkboxCollection1

def checkboxCollection2 = replica.customFields."Env Refresh Checklist".
                                value?.
                                collect{
                                        a->
                                        nodeHelper.getOption (issue, "Env Refresh Checklist", a.value)
                                        }
issue.customFields."Env Refresh Checklist".value = checkboxCollection2

def checkboxCollection3 = replica.customFields."Env Verification Checklist".
                                value?.
                                collect{
                                        a->
                                        nodeHelper.getOption (issue, "Env Verification Checklist", a.value)
                                        }
issue.customFields."Env Verification Checklist".value = checkboxCollection3


def checkboxCollection4 = replica.customFields."Environment".
                                value?.
                                collect{
                                        a->
                                        nodeHelper.getOption (issue, "Cloud Environment", a.value)
                                        }
issue.customFields."Cloud Environment".value = checkboxCollection4
// issue.customFields."Migration Details".value = issue.customFields."Migration Details".value +"\n" + checkboxCollection4?.join(",")


def checkboxCollection5 = replica.customFields."Product/s".
                                value?.
                                collect{
                                        a->
                                        nodeHelper.getOption (issue, "Migration Products", a.value)
                                        }
issue.customFields."Migration Products".value = checkboxCollection5




//Text single (ensure that field names are correct. Its case sensitive.)
if(replica.customFields."Contact Email"?.value){
    issue.customFields."Contact Email".value = replica.customFields."Contact Email".value
}
if(replica.customFields."Contact Person"?.value){
    issue.customFields."Contact Person".value = replica.customFields."Contact Person".value
}
if(replica.customFields."Job Title"?.value){
    issue.customFields."Job Title".value = replica.customFields."Job Title".value
}
if(replica.customFields."Pack Location"?.value){
    issue.customFields."Pack Location".value = replica.customFields."Pack Location".value
}
if(replica.customFields."Refresh From"?.value){
    issue.customFields."Refresh From".value = replica.customFields."Refresh From".value
}
if(replica.customFields."Refresh To"?.value){
    issue.customFields."Refresh To".value = replica.customFields."Refresh To".value
}
if(replica.customFields."Team Name"?.value){
    issue.customFields."Team Name".value = replica.customFields."Team Name".value
}



//Text field to Number field (text field must have a digit)
if(replica.customFields."Estimate - High Level (days)"?.value){
    def valueStr = replica.customFields."Estimate - High Level (days)".value
    double valueNum = Double.valueOf(valueStr)
    issue.customFields."Est. (Days)".value = valueNum
}


//Test multiline
if(replica.customFields."Server IPs"?.value){
    issue.customFields."Server IPs".value = replica.customFields."Server IPs".value
}


// Radion to Cascading field
if(replica.customFields."Release Window"?.value){
    def radioValue = replica.customFields."Release Window".value.value
    
    issue.customFields."Release Window".value = nodeHelper.getCascadingSelect(
       nodeHelper.getOption(issue, "Release Window", "APAC"),  // hardcoded value
       nodeHelper.getOption(issue, "Release Window", radioValue )
       )
}




//other
// issue.customFields."Approvers" = replica.customFields."Approvers"



Comments:

Francis Martens (Exalate) commented on 04 August 2020

It is indeed long. You might have a look at the switch statement in groovy which would help compress the incoming sync.

Could you please remove the line


issue.creator = nodeHelper.getUserByUsername(replica.creator?.username)


From the incoming sync script and try again.

Also as a suggestion you can also ensure that the reporter and assignee are always set with following code

def defaultUser = nodeHelper.getUserByEmail("jirabot_service@temenos.com")
issue.assignee = nodeHelper.getUserByUsername(replica.assignee?.username) ?: defaultUser
issue.reporter = nodeHelper.getUserByUsername(replica.reporter?.username) ?: defaultUser

Answer by Francis Martens (Exalate) on 04 August 2020

Hi Jiri Kanicky

Thank you for raising this question here - much obliged.
I tried to reproduce the issue but failed (ie it works here)

What I did is in the incoming sync on my ‘GCD’, I added following line of code

if (firstSync) {
   issue.reporter = nodeHelper.getUserByEmail("customer@customer.com")

}

The permission helper confirmed that this user has no create permissions

Still the ticket gets created

What is your current ‘incoming sync processor’?