Tags and Status to automatic update from both sides -Jira and DevOps-

Hello,

Im trying to get two things

1 - Jira Status to update the Tag on the DevOps ticket
when Jira status is Dev In Progress then DevOps tag should be Ready for Sprint

2- DevOps Tag to update Jira status
when Tag in Devops is Ready for UAT Testing then Jira Status to change to UAT Testing
And
when Tag in DevOps is Deployed to PRD then Jira status to change to Deployed in Prod

My code is as follows:

Outgoing Jira:

// let DevOps know which JIRA key this came from
replica.key = issue.key

// basic fields
replica.summary = issue.summary
replica.priority = issue.priority
//This line below, transforms description from Markdown to HTML to transfer over
replica.description = nodeHelper.getHtmlField(issue, “description”)
replica.attachments = issue.attachments
replica.status = issue.status

// custom-field mappings (make sure the names match exactly what you use in Azure incoming)
replica.customFields.“DevOpsProject” = issue.customFields.“DevOps Project”
replica.customFields.“CustomerRequestType” = issue.customFields.“Customer Request Type”
replica.customFields.“Estimate” = issue.customFields.“Estimated Effort”


Incoming Jira

if (firstSync) {
issue.projectKey = “ITS”
issue.typeName = “CI”
}

if(!firstSync && syncRequest.remoteSyncEventNumber==1){
issue.customFields.“Remote URL”.value = issueUrl
}

// Map DevOps tags to Jira statuses
// If tag in DevOps is ‘Ready for UAT Testing’ then Jira status should change to ‘UAT Testing’
// If tag in DevOps is ‘Deployed to PRD’ then Jira status should change to ‘Deployed to Prod’
def tagToStatusMap = [
‘Ready for UAT Testing’: ‘UAT Testing’,
‘Deployed to PRD’ : ‘Deployed to Prod’
]

def devopsTags = replica.tags ?:
// Find the first tag that matches our mapping
for (tag in devopsTags) {
if (tagToStatusMap.containsKey(tag)) {
issue.setStatus(tagToStatusMap[tag])
break
}
}


Outgoing DevOps

// Outgoing script (Azure DevOps → Jira)

replica.key = workItem.key
replica.summary = workItem.summary
replica.description = workItem.description
replica.tags = issue.labels


Incoming DevOps

if (firstSync) {
workItem.projectKey = replica.customFields.“DevOpsProject”?.value?.value ?: “DEFAULT_PROJECT”
workItem.typeName = “User Story”
workItem.summary = replica.summary

syncHelper.syncBackAfterProcessing()
}

workItem.description = replica.description
workItem.attachments = replica.attachments
workItem.priority = replica.priority

if (firstSync) {
store()
workItem.customFields.“JIRA”.value = issueUrl
workItem.customFields.“Estimate”.value = replica.Estimate
} else {
workItem.customFields.“JIRA”.value = issueUrl
}

// Add tag ‘Ready for Sprint’ if Jira status is ‘Dev In Progress’.
// Only set the tags field if the status is exactly ‘Dev In Progress’.
if (replica.status?.name == “Dev In Progress”) {
workItem.tags = [“Ready for Sprint”]
}

Any ideas? (This code was suggested by the AI in the Exalate screens)

Thanks

hi @Lautaro

Thank you for connecting with Exalate community and happy to work with you on it.

May I know if you tested that code after getting it by the Exalate AI?

Hello, Ive tried the code in my exalate with my test tickets, Tags dont get created and Status dont get updated, i tried troubleshooting the issue with the exalate AI but even after giving me a few other options, tags and status dont change or get creted. Thank you

Just as extra info, the only part of the code that does not work is the TAG, Status part. the rest works fine

Appreciate the update. I have to check and then test it, once I have the information, will be sharing it with you.

Hi @Lautaro

Thank you for your patience. Please try the following snippet (which I still need to try) and see the outcome:

Jira Outgoing:

// Add tag ‘Ready for Sprint’ if Jira status is ‘Dev In Progress’.
// Only set the tags field if the status is exactly ‘Dev In Progress’.
if (replica.status?.name == “Dev In Progress”) {
// Ensure the existing tags are preserved, and append ‘Ready for Sprint’ if Jira status is ‘Dev In Progress’.
workItem.tags = (workItem.tags ?: ) + [“Ready for Sprint”]
}

This code checks if the Jira status is "Dev In Progress". If it is, it adds the tag "Ready for Sprint" to the list of existing tags in Azure DevOps without removing any existing tags.

Incoming Jira

// Map DevOps tags to Jira statuses
// If tag in DevOps is ‘Ready for UAT Testing’ then Jira status should change to ‘UAT Testing’
// If tag in DevOps is ‘Deployed to PRD’ then Jira status should change to ‘Deployed to Prod’
def tagToStatusMap = [
‘Ready for UAT Testing’: ‘UAT Testing’,
‘Deployed to PRD’ : ‘Deployed to Prod’
]

def devopsTags = replica.tags ?:

// Find the first tag that matches our mapping
for (tag in devopsTags) {
if (tagToStatusMap.containsKey(tag)) {
def mappedStatus = tagToStatusMap[tag]

    // Transition Jira status to the mapped value
    // Assuming the 'mappedStatus' corresponds to a valid Jira status name
    issue.transition(mappedStatus)
    break
}

}

This code maps tags from Azure DevOps (e.g., "Ready for UAT Testing", "Deployed to PRD") to corresponding Jira statuses (e.g., "UAT Testing", "Deployed to Prod"). When a matching tag is found, the Jira status is updated using issue.transition(mappedStatus).

Outgoing Azure DevOps:

// Check Jira status and add appropriate tags to DevOps work items
if (replica.status?.name == “Dev In Progress”) {
// Ensure the existing tags are preserved, and append ‘Ready for Sprint’ if Jira status is ‘Dev In Progress’
workItem.tags = (workItem.tags ?: ) + [“Ready for Sprint”]
}

This is similar to the outgoing Jira sync, but here we are updating the tags in Azure DevOps based on the Jira status.

Incoming Azure DevOps:

// Map DevOps tags to Jira statuses
// If tag in DevOps is ‘Ready for UAT Testing’ then Jira status should change to ‘UAT Testing’
// If tag in DevOps is ‘Deployed to PRD’ then Jira status should change to ‘Deployed to Prod’
def tagToStatusMap = [
‘Ready for UAT Testing’: ‘UAT Testing’,
‘Deployed to PRD’ : ‘Deployed to Prod’
]

def devopsTags = replica.tags ?:

// Find the first tag that matches our mapping
for (tag in devopsTags) {
if (tagToStatusMap.containsKey(tag)) {
def mappedStatus = tagToStatusMap[tag]

    // Transition Jira status to the mapped value
    // Assuming the 'mappedStatus' corresponds to a valid Jira status name
    issue.transition(mappedStatus)
    break
}

}

This code checks tags from Azure DevOps (such as "Ready for UAT Testing" and "Deployed to PRD") and maps them to Jira statuses (like "UAT Testing" and "Deployed to Prod"). If a matching tag is found, it uses issue.transition(mappedStatus) to transition the Jira issue status.

Please check and let me know the outcome.

hi @Lautaro

May I know if you had a chance to review my last comment?

Hello Jilian,

I hope you are well. We have been a bit busy and couldnt work around this.

We are a getting errors with exalate everyday (the Bad response when Updating work item - 400 - response body: VS403354: Index out of range for path /relations/24)

This error is becoming quite annoying, it would be wonderful if we could get together on a call and try to solve it, since we have 59 connections in error due to this.

Also, we are not getting anywhere trying to get Tags in DevOps to automatically update Status in Jira, it would be wonderful if we could fix that as well.

Let me know if we should get together on a call to resolve these issues.

Thank you

Hi @Lautaro

No issues at all and pertaining to the error, could I please ask you to open a support ticket as this needs to be checked thoroughly and we will be looking into it there.

BR,

Jillani

HI @Lautaro

May I know if you raised a support ticket as I cant seem to find one. Looking forward to hearing from you.

Hello Jillani,

I think I did, title : Error Bad response when Updating work item - 400 - response body: VS403354 on over 75 tickets

EASE-45890

Thank you

hi @Lautaro

Thank you for opening up the ticket. I am working on it and will be addressing it there.

Hi @Lautaro

I am glad that the ticket raised from your end EASE-45890 pertaining to the error:

Error Bad response when Updating work item - 400 - response body: VS403354 on over 75 tickets

This error stack trace indicates a specific failure during an attempt to update a work item in a system that is likely using Azure DevOps or a similar platform (implied by the VS403354 error code and context like work item).

Upon further checks, I found that it is likely related to the attachments, which is missing in the replica; hence the error.

I asked to replace the current attachment related snippet:

workitems.attachments = replica.addedAttachments

With the default one:

workItem.attachments = attachmentHelper.mergeAttachments(workItem, replica)

This line ensures that attachments are kept in sync between Azure DevOps and the other side of the connection (e.g., Jira Cloud), reflecting any additions or removals.

You applied and confirmed that the error was no longer appearing.

Pertaining to the original query as asked in the description which was to “Tags and Status to automatic update from both sides -Jira Cloud and DevOps”, you were able to sync the Tags from Azure Devops to Status in Jira Cloud side, I tested it and it worked.

I also tested further and was able to sync the Statuses in Jira Cloud to the Tags in Azure DevOps.

Here is a tried and tested script. The first part is the one shared from your side, I only changed the statuses to match the workflow in my test instance:

Sync Tags to Status (ADO ----> JC)

ADO Outgoing:

// Send Azure DevOps tags to Jira as labels
replica.labels = workItem.labels

JC Incoming:

// Map DevOps tags to Jira statuses and transition the Jira issue

if (!firstSync) {

 def tagToStatusMap = [ 
'To Do': 'To Do', 
'IN PROGRESS': 'IN PROGRESS', 
'DONE' : 'DONE' 
]  


// 2) Read tags from replica.labels (Azure DevOps → Jira)
def devopsTags = []
if (replica.labels instanceof Collection) {
devopsTags = replica.labels*.label
.collect { it?.toString()?.trim() }
.findAll { it } // remove null/empty
}

// 3) Find matching Jira status from the map
def targetStatusName = null
devopsTags.each { tag ->
if (tagToStatusMap.containsKey(tag)) {
targetStatusName = tagToStatusMap[tag]
}
}

// 4) If a mapped status was found, set the issue status directly
if (targetStatusName && issue.status != targetStatusName) {
issue.status = targetStatusName
}
}

Sync Status to Tags (JC -----> ADO):

JC Outgoing:

replica.status = issue.status

ADO Incoming:

// Map Jira statuses to Azure DevOps tags
if (!firstSync) {
// 1) Map Jira status names to DevOps tags (exact case from Jira)
def statusToTagMap = [
'To Do': 'To Do',
'In Progress': 'IN PROGRESS', // Jira sends "In Progress" (with space)
'Done': 'DONE' // Jira sends "Done"
]

// 2) Get the Jira status from replica
def jiraStatus = replica.status?.name?.toString()?.trim()

// 3) Get existing tags on the work item
def existingTags = workItem.labels?.collect { it.label } ?: []

// 4) Remove any status-related tags (clean up old status tags)
def statusTags = statusToTagMap.values() as Set
def nonStatusTags = existingTags.findAll { tag ->
!statusTags.contains(tag)
}

// 5) Add the new status tag if mapped
if (jiraStatus && statusToTagMap.containsKey(jiraStatus)) {
def newTag = statusToTagMap[jiraStatus]
nonStatusTags.add(newTag)
}

// 6) Update the work item tags
workItem.labels = nonStatusTags.collect { tag ->
nodeHelper.getLabel(tag)
}
}

Here is another one, which I test successfully, in relation with with case-insensitive matching:

ADO Incoming:

// Map Jira statuses to Azure DevOps tags (case-insensitive)
if (!firstSync) {
// 1) Map Jira status names to DevOps tags (using lowercase keys)
def statusToTagMap = [
'to do': 'To Do',
'in progress': 'IN PROGRESS',
'done': 'DONE'
]

// 2) Get the Jira status from replica (convert to lowercase for matching)
def jiraStatus = replica.status?.name?.toString()?.trim()
def jiraStatusLower = jiraStatus?.toLowerCase()

// 3) Get existing tags on the work item
def existingTags = workItem.labels?.collect { it.label } ?: []

// 4) Remove any status-related tags
def statusTags = statusToTagMap.values() as Set
def nonStatusTags = existingTags.findAll { tag ->
!statusTags.contains(tag)
}

// 5) Add the new status tag if mapped
if (jiraStatusLower && statusToTagMap.containsKey(jiraStatusLower)) {
def newTag = statusToTagMap[jiraStatusLower]
nonStatusTags.add(newTag)
}

// 6) Update the work item tags
workItem.labels = nonStatusTags.collect { tag ->
nodeHelper.getLabel(tag)
}
}

I am glad that we were able to address the scenario and appreciate your patience/cooperation during this time. :slight_smile:

Happy Exalating!

Hello Jillani,

Ive created a new ticket for the azure side exalate button not loading.

EASE-46327

Thanks

1 Like

Hi @Lautaro

Sounds good and I can see that @ashar.iqbal is working on it and will be addressing it accordingly.

Pertaining to the script shared above, may I know if you had a chance to review it?

Hello Jillani,

With the script ive shared ive solved all the issues i was having regarding the tags and status.

Im on another thread fixing the other issue.

Thank you

Hi @Lautaro

Sounds great. :slight_smile:

Just confirming that by another thread, you are referring to the EASE - 46327, correct?