Creating a custom dialog in Jira (Gotta Script'em All 3/6)

CUSTOM DIALOG

Welcome back to the third post in the Gotta Script-em All series where we explore the capabilities of Jira through a step-by-step guide to creating seamless integration links between Jira and other tools.  Make sure you check out the previous two posts in this series if you haven't yet.

In part 1 we explored how to create a custom button and rest endpoint on your Jira user interface.  In part 2 we go back to basics with a guide to writing clean code. In this post (part 3), we resume our quest for achieving custom user interface (UI) goodness using ScriptRunner. We get deeper into the nitty-gritty of manipulating custom fields in Jira by creating a custom dialog that derives data from these fields - sound cool? Let's crack on.

A brief overview

 

An important component of ScriptRunner is its ability to read and write (manipulate) any system or custom field. I can give you a simple example: take two number fields, multiply them and put the result into a third field. As Jira won't do this by default, now we have a script to build.

 

Today, we need to copy the code for a custom dialog and replace our Rest EndPoint script (that runs when you click on the 'Send xMatters' button) to show a dialog.

 

Then we fill that dialog with all sorts of data and in the meantime learn the difference between reading a system field and a custom field.

 

We will also figure out how it's possible to send the issue key to the Rest EndPoint when we click on the button.

1Krisz

We have a dialog box, but now it's time to create a plan to conquer the world or at least fill the dialog box with text and data from Jira.

In this post we will:

  1. Find out what issue the dialog is connected to

    1. How to pass the issue key to the dialog
    2. How to pass data using rest API?
  2. Gather custom field data

    1. Provide data, based on issue key
    2. Put the data in the dialog box
  3. Make the data editable

    1. How to edit the text before sharing

The reason behind our previous post (part 2) on writing clean code was so that now we can come up with handy little methods and discuss what piece of the puzzle they provide. Once we put them in one file (in the end) all we need is to call each of them at the right time.

I always prefer to create my methods first before assembling the entire script. By following this approach, it will make it a lot easier to test the script as well.

Get issue object by key

Since we are dealing with a single issue in Jira, we can use a method that delivers that issue based on the key handed over by the 'button' we created in our previous blog 'Creating buttons and Rest Endpoints'.

// Code box

copy

Copied!

 

static Issue getIssueByKey (String issueKey) {
    return ComponentAccessor.issueManager.getIssueByCurrentKey(issueKey)
}

Get custom field data

The sole purpose of this method is to return custom field data as a string. Of course, it could be more sophisticated but, for this script, its not necessary.

// Code box

copy

Copied!

 

static String getCustomFieldData(String key, String customFieldID) {
    Issue issue = ComponentAccessor.issueManager.getIssueByCurrentKey(key)
    return issue.getCustomFieldValue(ComponentAccessor.getCustomFieldManager()
        .getCustomFieldObject(customFieldID))
}

Provide issue key when calling Rest URL

It's super easy to provide the issue key to your Rest EndPoint script. It would also be quite easy to provide more data on the issue by typing issue.summary or issue.report, etc.

However the only 100% unique identifier of an issue is the key, similar to the unique sequence of numbers and letters on a car licence plate.  So if you want to hand over information from a script and you have limited space its best to just send the issue key and then get the issue object by key. This will provide all the information you would ever need to about the issue.

// Code box

copy

Copied!

 

/rest/scriptrunner/latest/custom/showXMattersDialog?issueKey=${issue.key}

Trimming the issue key

The challenge we face is simple: the key that is handed over by our button, wants to be an object but is displayed as a string. An object contains brackets. Instead of appearing as 'KEY-123' our issue key is '[KEY-123]'.

Luckily we can remove these brackets in one easy step. Even better, we can go all out and remove any possible risk or character that may challenge our issue key. Here we go:

// Code box

copy

Copied!

 

static trimIssueKey(String key) {
    key = key.replaceAll(/^\[|]$/, '')
    return key
}

Updating the text area

The plan is to gather all the information we need and put it into a text area:

// Code box

copy

Copied!

 

static getTextArea(String issueKey, List<String> CustomFieldIDs) {
    return """
This is the actual message that will be sent to xMatters with all the variables
we gather from custom fields.
It can be edited here before sent.
 
Custom Field A: ${getCustomFieldData(issueKey, CustomFieldIDs[0])}
Another Custom Field: ${getCustomFieldData(issueKey, CustomFieldIDs[1])}
 
Thank you for your attention.
 
Cheers,
The A Team"""
}

When you create a multi-line string, you can avoid the "String1"+"String2"+"String3" by using """ (three quotation marks) and then injecting all sorts of 'codeness' with ${myCode}. That's a convenient way of creating a complex string but also preserving readability.

Putting the information into a text area, involves creating a method that receives two key pieces of information: the issue key and the custom field IDs so we can put them into a text area later.

For this example, the custom field IDs are in a list:

// Code box

copy

Copied!

 

List<String> CustomFieldIDs = [
        "customfield_10201",
        "customfield_10200"
]

With this approach we now have a text area in our custom dialog that can be modified before sending the dialog to xMatters in part 4 of our blog series. 

8Krisz

The journey into JavaScript

At this point, you should be aware that if you show data in a text area and let the user modify it, you can no longer use ScriptRunner to submit the data for further processing. Here is the piece of code we need, it's pure JavaScript. There is no other alternative but to use Javascript once the dialog is set up and presented.

// Code box

copy

Copied!

 

<script>
    function submit() {
         
    }
    var el = document.getElementById("submit");
    if (el.addEventListener)
        el.addEventListener("click", submit, false);
    else if (el.attachEvent)
        el.attachEvent('onclick', submit);
</script>

I'm not a self-proclaimed JavaScript expert but, luckily in this instance, there is no need for an insanely complicated script. We just need to have three functions:

  1. To find out if the user clicked on the submit button
  2. To run a 'function' when the click happens
  3. To dream about what happens in the submit() function

There is a dark cloud on the horizon: submit(). We'll need to use restAPI within JavaScript now... but that's a tale for another time. For now, let's just focus on showing the data in the dialog.

Showing the dialog

It's not enough to create a couple of methods, you will need a proper structure, so here is the whole script for you to consider:

// Code box

copy

Copied!

 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
@BaseScript CustomEndpointDelegate delegate
import groovy.json.JsonSlurper
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.ContentType
import static groovyx.net.http.Method.*
import com.atlassian.jira.issue.fields.rest.json.beans.PriorityJsonBean
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript
import org.codehaus.jackson.map.ObjectMapper
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
//-------------------------------------------------------------------------
// This script can be applied as a rest-end-point that shows the dialog
// when a user clicks on the 'send xmatters' button
// Currently knows button placements for web fragments:
// - jira.issue.tools
// - operations-top-level
//-------------------------------------------------------------------------
// Please change the following so the script would suit your needs:
List<String> CustomFieldIDs = [
        "customfield_10201",
        "customfield_10200"
]
//-------------------------------------------------------------------------
static trimIssueKey(String key) {
    key = key.replaceAll(/^\[|]$/, '')
    return key
}
static getCustomFieldData(String key, String customFieldID) {
    Issue issue = ComponentAccessor.issueManager.getIssueByCurrentKey(key)
    return issue.getCustomFieldValue(ComponentAccessor.getCustomFieldManager()
        .getCustomFieldObject(customFieldID))
}
static getTextArea(String issueKey, List<String> CustomFieldIDs) {
    return """
This is the actual message that will be sent to xMatters with all the variables
we gather from custom fields.
It can be edited here before sent.
 
Custom Field A: ${getCustomFieldData(issueKey, CustomFieldIDs[0])}
Another Custom Field: ${getCustomFieldData(issueKey, CustomFieldIDs[1])}
 
Thank you for your attention.
 
Cheers,
The A Team"""
}
static getDialogText(String issueKey, List<String> CustomFieldIDs) {
    return """
<script>
    function submit() {
         
    }
    var el = document.getElementById("submit");
    if (el.addEventListener)
        el.addEventListener("click", submit, false);
    else if (el.attachEvent)
        el.attachEvent('onclick', submit);
</script>
<section role="dialog" id="sr-dialog"
    class="aui-layer aui-dialog2 aui-dialog2-medium" aria-hidden="true"
        data-aui-remove-on-hide="true">
<header class="aui-dialog2-header">
    <h2 class="aui-dialog2-header-main">xMatters Message</h2>
    <a class="aui-dialog2-header-close">
        <span class="aui-icon aui-icon-small aui-iconfont-close-dialog">
            Close
        </span>
    </a>
</header>
<div class="aui-dialog2-content">
    <p>Header of the dialog, some text about warnings and whatever....</p>
        <textarea id="sr-dialog-textarea" rows="15" cols="75">
            ${getTextArea(issueKey, CustomFieldIDs)}
        </textarea>
</div>
<footer class="aui-dialog2-footer">
    <div class="aui-dialog2-footer-actions">
        <button class="aui-button" id="submit">Send xMatters</button>
        <button id="dialog-close-button" class="aui-button aui-button-link"">
            Close
        </button>
    </div>
    <div class="aui-dialog2-footer-hint">This is a footer message</div>
</footer>
</section>
"""
}
showXMattersDialog() {
    MultivaluedMap queryParams ->
        String issueKey = queryParams.get("issueKey")
        issueKey = trimIssueKey(issueKey)
        String dialog = getDialogText(issueKey, CustomFieldIDs)
        Response.ok().type(MediaType.TEXT_HTML).entity(dialog).build()
}

As you can see the main section (showXMattersDialog) of the script is straightforward: it receives the issue key, trims it, gets the dialog body, then shows the dialog to the user. 

Join us again for part 4, where now that we have created a dialog with some custom text and custom field values, we will use it to send an alert or communication to our user community. To do this, we will leverage Jira's integration with xMatters to automatically transfer our issue key data and create our notification. 

Archive