How to manage multiple service desks in Jira Service Desk


Posted by
Nicolas ESTEVES

March 6, 2018

Making the choice to work with Jira Service Desk helps minimize ticket processing time while improving the quality of responses. Because these advantages are inherently appealing, there is a direct impact on the number of teams that want to use it, and therefore indirectly affects the number of service desk projects on a single Jira instance. If we take into account the versatility of some agents who serve on multiple teams, the following paradox emerges over time: the more an agent works on different service desk projects, the less efficient she will be.

The explanation for this phenomenon is fairly obvious; currently, Jira Service Desk does not offer an interface that combines every ticket, thus preventing an agent from having access to everything in a single view. The agent therefore needs to switch from one project to another in order to see the ticket queues assigned to each project. In some extreme cases, this complicated user experience leads agents to give up and go back to a traditional Jira dashboard, depriving them of one of the key features of Jira Service Desk: its interface for agents.

But there are solutions!

In this article, we’ll see the use case in which Valiantys encountered this problem and how we worked around it. For interested readers, the second part will also cover more technical details on the solution as well as a few alternative work-arounds.

Valiantys’ support architecture

As an Atlassian Platinum Solution Partner, Valiantys offers a support plan for Atlassian products. Like many clients, we have chosen Jira Service Desk to optimize this activity as much as possible. Our support team therefore uses Jira Service Desk each day through a central project named Valiantys Support (VS).

As our support plan evolved to offer “unlimited” contracts, we needed to create new support projects which were each fully dedicated to one client. This left us in the situation where our agents were required work on multiple service desk projects.

Let’s look at an example support project for a client on an “unlimited” contract, named Client Support (CS), which is of course a service desk project. In this case, we have issues that are created in both projects, but we must also prioritize them in order to adhere to all the SLAs in place. With a single CS project, it was possible to keep working by switching between the two support projects. But with a nearly constant increase in the number of projects similar to the CS project, we were getting dangerously close to the support team losing productivity.

Valiantys Support: A service desk project

Synchronizing issues between two Jira Service Desk projects

In order for agents to avoid switching between projects, the queues of the main project (VS) needed to be able to show issues that are in another project (CS) on the same screen. However, Jira Service Desk does not make it possible to include issues from other projects in its queues:

No CS issues are displayed in VS – even though they exist.

Based on this observation, the syncing-based solution was the natural choice. Whenever an issue is created in a CS project, an equivalent issue is automatically created in the VS project. Afterward, we synchronized each update made to the CS issue in the corresponding issue within the VS project, giving us a consistent view. Here’s what it looks like:

Creating a CS issue.

Request seen by the client via the CS project portal.

Now that the issue has been created, here’s what the agents see in the VS project:

Note the presence of both keys: CS and VS.

With respect to what the agents might see in the CS project, we can skip over it because the goal here is simply that they don’t need to go looking for it.

In order to not disrupt the client with multiple reference keys, all the work is done in the CS issue: transitions, comments, reports, etc. The VS issue exists only to give a view of the key aspects of the original issue. Here is what an agent sees when she clicks on each key:

 

CS issue: the issue the agent is to work on.

VS issue: transitions are blocked, and a message asks the agent to go to the CS issue.

Thanks to syncing, we have therefore centralized all issues related to the support activity into a single service desk project. The only notable difference for agents when they work on synced issues is that they need to do so on the CS issue, not the VS issue.

Now let’s see the technical measures put in place to achieve this result.

Technical measures

Two apps are used to manage syncing.

Exocet: Automate the creation of linked tickets

Exocet makes it possible to automatically create a VS issue when an issue is created in CS. While doing so, it also links the two issues to one another. This link adds visibility while also synchronizing the issues’ fields.

Mapping the fields

In this section, the fields are matched to the future linked issues. Nothing special to mention for the configuration. The documentation can be found here.

In our case, the VS and CS projects are very similar, so all the fields are mapped to themselves.

Creation

This is an operation with a fairly traditional configuration. If you want to see the documentation for it, it’s right here.

No operation is shown because it is configured directly by the post-function in the workflow(s) of the CS project.

Here, we specify in what case the operation is available.

You need to consider using the mapping created in the previous step.

Synchronization

Finally, synchronization is configured as follows. For more information, see here.

For this section, we’ll use the mapping already defined.

Script Runner: Sync the transitions using Groovy scripts

We use Script Runner in order to manage the syncing of transitions, display dynamic fields in issues, and configure scripted conditions on transitions.

Script Listener

Here is the Script Listener we used:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.user.ApplicationUsers
import com.atlassian.jira.bc.issue.IssueService.TransitionValidationResult
import com.atlassian.jira.bc.issue.IssueService
import com.atlassian.jira.issue.IssueInputParameters
import javax.servlet.http.HttpServletRequest
import com.atlassian.jira.web.ExecutingHttpRequest
import com.atlassian.jira.event.type.EventDispatchOption
import org.apache.log4j.Category
  
// Debug
//Category log = log
//log.setLevel(org.apache.log4j.Level.DEBUG)
  
// Managers
def issueManager = ComponentAccessor.getIssueManager()
def issueLinkManager = ComponentAccessor.getIssueLinkManager()
def eventManager = ComponentAccessor.getIssueEventManager()
def userManager = ComponentAccessor.getUserManager()
def issueService = ComponentAccessor.getComponent(com.atlassian.jira.bc.issue.IssueService)
  
// Constantes et variables
def issue = event.getIssue()
def myLink = 10640 // Synchronization link
def user = event.getUser()
def Issue linkedIssue = null
def transitionId = null
 
// Recherche d'une demande liée à synchroniser
def inwardLinks = issueLinkManager.getInwardLinks(issue.getId()) // Dans un sens
for (IssueLink oneLink : inwardLinks) {
    if (oneLink.getLinkTypeId() == myLink){
        linkedIssue = oneLink.getSourceObject()
    }
}
if (linkedIssue == null){
    def outwardLinks = issueLinkManager.getOutwardLinks(issue.getId()) // Puis dans l'autre
    for (IssueLink oneLink : outwardLinks) {
        if (oneLink.getLinkTypeId() == myLink){
            linkedIssue = oneLink.getDestinationObject()
        }
    }
}
  
// Identification de la transition passée
HttpServletRequest request = ExecutingHttpRequest.get()
if (request != null){
    transitionId = request.getParameter("action")  
    // Exécution de la transition sur la demande liée
    def transitionNum = Integer.parseInt(transitionId)
    if (transitionId != null && transitionId && user.getName() != "robot" && linkedIssue != null){
        // On éxécute avec le robot pour éviter les boucles
        def userRobot = userManager.getUserByName("robot")
        def transitionResult = issueService.validateTransition(userRobot, linkedIssue.getId(), transitionNum, issueService.newIssueInputParameters())
        if (transitionResult.isValid()){
            issueService.transition(userRobot, transitionResult)
        }
    }
}

This Script Listener listens for transitions that took place on CS issues, and replicates them on the corresponding VS issues. We use a “robot” user to perform these transitions.

This script works only when the synced issues use the same workflow. That’s because the Script Listener captures the transition number from the CS, and uses the same one to perform the transition on the VS. If the workflows are different, you need to manually map the transition IDs of each workflow (or choose an alternative – see below).

The scripted fields

Two Scripted Fields are used in the use case presented in this article:

  • The Sync Key field, which makes it possible to display the CS reference in the VS ticket, particularly as a column in the queues:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.attachment.Attachment
import com.atlassian.jira.config.properties.APKeys
import org.apache.log4j.Category
 
//Category log = log
//log.setLevel(org.apache.log4j.Level.DEBUG)
 
def Long LINK_ID = 10640
def Long VS_ID = 13141
 
def baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL)
def issueManager = ComponentAccessor.getIssueManager()
def issueLinkManager = ComponentAccessor.getIssueLinkManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def Issue linkedIssue = null
 
def issueLinksIn = issueLinkManager.getInwardLinks(issue.getId())
for (IssueLink oneLink : issueLinksIn) {
    if (oneLink.getLinkTypeId() == LINK_ID && oneLink.getSourceObject().getProjectId() != VS_ID){
        linkedIssue = oneLink.getSourceObject()
        break
            }
}
 
if (linkedIssue){
   '<a href="' + baseUrl + '/browse/' + linkedIssue.getKey() +'">' + linkedIssue.getKey() + '</a>'
}

The way it works is fairly simple; it searches issues related to the VS issue for the one that uses the link mentioned in the Exocet configuration. What it returns is simply a (clickable) link to that issue.

  • The Synchronized Issue field, which is used to display a warning message:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.attachment.Attachment
import com.atlassian.jira.config.properties.APKeys
import org.apache.log4j.Category
 
//Category log = log
//log.setLevel(org.apache.log4j.Level.DEBUG)
 
def Long LINK_ID = 10640
def Long VS_ID = 13141
 
def baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL)
def issueManager = ComponentAccessor.getIssueManager()
def issueLinkManager = ComponentAccessor.getIssueLinkManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def Issue linkedIssue = null
 
def issueLinksIn = issueLinkManager.getInwardLinks(issue.getId())
for (IssueLink oneLink : issueLinksIn) {
    if (oneLink.getLinkTypeId() == LINK_ID && oneLink.getSourceObject().getProjectId() != VS_ID){
        linkedIssue = oneLink.getSourceObject()
        break
            }
}
 
if (linkedIssue){
    def sync = '<a href="' + baseUrl + '/browse/' + linkedIssue.getKey() +'">' + linkedIssue.getKey() + '</a>'
    def writer = "<div class=\"aui-message aui-message-error\">" +
        "<p class=\"title\">" +
        "<strong>Please work on the synchronized issue: " + sync + "</strong>" +  
        "</p>" +
        "<p>The current one has been created for tracking purposes only.</p>" +
        "</div><!-- .aui-message --> "
    return writer
}

Same principle as the previous field, but this time we add a defined form and display this field in the issue.

Scripted conditions

Finally, the transitions of our workflow are defined by conditions so they are not displayed in the VS issue if it is linked to a CS issue. To do so, we use a Simple Scripted Condition (on each transition).

cfValues['Synchronized Issue'] == null || currentUser.username == "robot"

The transitions are displayed only if the returned value is “true”. (coche)

It should be noted that the robot user sees the transitions in all cases. This is a prerequisite so that it can run transitions in Script Listener.

Alternative: Syncing transitions with the Automation module of Jira Service Desk

It is possible to sync the transitions in Script Listener. For example, one may use the Automation module of Jira Service Desk. This makes it possible to run a transition on an issue of the service desk project when a linked issue was also transitioned.

 

Pros
Cons
  • This solution is fairly simple to implement.
  • This solution is not generic.
  • No need to write a line of code.
  • Each time the workflow is changed (adding or removing transitions), the Automation rule must be modified to take these changes into account. The Script Listener applies the same transition to it in all cases, no matter what happens.

Alternative: Queues

Valiantys chose syncing because it’s the most reliable and flexible solution for our support needs. Nonetheless, other solutions exist, which might be suitable for other cases.

Recently launched on the Atlassian Marketplace, Queues makes it possible to incorporate issues from other projects into a service desk project’s queues. Although this app addresses the issue presented in this article, it has some limitations: For example, it cannot display all field types that you want in the columns. Queues actually replaces the traditional display of Jira Service Desk queues.

A final word

Jira Service Desk is being increasingly adopted by teams. It is therefore very likely that you’ll be faced with this problem one day. Nonetheless, you have everything it takes to offer your teams the best solution for their needs.

And if you ever need a hand to address this challenge, feel free to call one of our consultants, who will be happy to help you implement this solution!