Visualize Your Blocking Relationships for a Jira Issue

Overview

How do you know what blocks an issue and what issues are blocked by any given issue? You can scroll and click around in Jira to get an answer, but a picture is worth a thousand words. At a glance, a visual can show all the blocking relationships of a single issue.

I created a script to visualize this from Jira data using VisualScript Studio. Each dependency and dependency direction (if applicable) is broken out into its own list of shapes. For example, blocking dependencies, which have an "inward" blocking direction (shapes that are blocking the target shape) as well as an "outward" blocking direction (shapes that are blocked by the target), have a list of shapes for each of the two "directions".

Jira blocking issue visualization

The user interface for letting end users interact with my script is very simple. It's just a text field that lets you specify the key of the Jira issue whose dependencies you want to visualize.

Jira blocking issue key UI

How I Implemented It

Obtaining the Data

The first thing I did was issue a Jira REST API call to obtain information about the target Jira issue. To do this, I used a helper function built into the Script Editor and the VisualScript Data Query SDK to call the API using VSCallFunctionForJiraIssue() and added user entered Jira issue key. I specify that I want it to call the function handleResponse() with the results.

// Issue the query to get the information from Jira VSCallFunctionForJiraIssue(gSDJSLocalHost, params, handleResponse);

Building the Model

The handleResponse() function passes me a JSON object representing the target issue (parameter "targ"). The IssueLinks property (targ.fields.issuelinks) has the information I need to build my model. Each object in the targ.issuelinks array can be have either an outwardIssue property or an inwardIssue property. Using the presence of an inwardIssue property to distinguish between the two "directions", I pull the issueName ("blocks" or "is blocked by"), the issue's key and the issue's summary into an object that is pushed onto the issueDirections object's property for that issueName. This simple model is all I need to create the VisualScript markup for this script.

// Handle response from Query for Jira Issue function handleResponse(targ) { var i, issueDirections, issueConnector = null, issueConnectorShape, vs = {}, issue, newShape, rootShape, rootConnector, table, cell; if (!targ) { alert("No parameter entered, or parameter is not an existing issue key"); callback(null); return; } // Build the model from the information returned by the query if (targ.fields.issuelinks) { issueDirections = {}; for (i = 0; i < targ.fields.issuelinks.length; i++) { var issuelink = targ.fields.issuelinks[i]; var issueName = issuelink.inwardIssue ? issuelink.type.inward : issuelink.type.outward; var issueKey = issuelink.inwardIssue ? issuelink.inwardIssue.key : issuelink.outwardIssue.key; var issueSummary = issuelink.inwardIssue ? issuelink.inwardIssue.fields.summary : issuelink.outwardIssue.fields.summary; if (!issueDirections[issueName]) { issueDirections[issueName] = { issues: [] }; } issueDirections[issueName].issues.push({ key: issueKey, summary: issueSummary }); } }

Constructing the VisualScript from the Model

Now that the model is built, I use the VisualScript SDK to build a JSON object that will, when rendered, produce the visual results I need.

// Build the VisualScript vs = new VS.Document() .SetTemplate(VS.Templates.Mindmap); rootShape = vs.GetTheShape() .SetLabel(targ.key) .SetFillColor("#b3d7ee") .SetLineThickness(0) .SetTextColor("#333333"); // Issues can be outbound or inbound. For each issue in the issue list, add it to the right "outbound" or "inbound" branch of our main shape's connector if (issueDirections) { rootConnector = rootShape.AddShapeConnector(); for (var direction in issueDirections) { issueConnectorShape = rootConnector.AddShape() .SetLabel(direction) .SetShapeType(VS.ShapeTypes.RoundedRectangle) .SetLineThickness(0) .SetFillColor("#FBD585") .SetTextColor("#333333"); issueConnector = issueConnectorShape.AddShapeConnector(); for (i = 0; i < issueDirections[direction].issues.length; i++) { issue = issueDirections[direction].issues[i]; newShape = issueConnector.AddShape() .SetFillColor("#b3d7ee") .SetLineThickness(0) .SetTextColor("#333333"); table = newShape.AddTable(2, 1); table.AddCell(1, 1) .SetLabel(issueDirections[direction].issues[i].key) .SetTextTruncate(60); table.AddCell(2, 1) .SetLabel(issueDirections[direction].issues[i].summary) .SetTextTruncate(60); } } }

After creating the VS.Document() and getting the "root shape", I iterate through each property in the model's issueDirections. For each "direction", I create a shape to represent the direction. I attach a shapeConnector to it and then add a shape to that connector for each issue in the direction's issue list. I chose to represent the issue in a table inside the shape for formatting purposes. The top row of the table contains the key and the bottom row contains the summary.

Rendering the VisualScript

The last step is to use the callback function with the VisualScript object (vs). This takes care of producing an image and displaying it in the gadget.

callback(vs.toJSON());

Creating the UI

After the script is complete, I set up the UI so that the user could provide the required parameter. In the top section of the VisualScript Studio edit page, you'll see a field labeled "Parameter Template". The specification string for the UI Builder's "Parameter Template" is very simple:

Parameter template

All I really needed to do was provide a more descriptive display name.

Display name

This will display a field that prompts the user with the text in the "displayed name" field, and will pass whatever the user types in via the params parameter to the VisualScript function.

More Information

This is just one example of the useful visuals that can be produced using VisualScript and VisualScript Studio with a minimum of development effort.

The full source for this script can be seen here. To make the code easier to understand, I highlighted the part used for querying the data in green, the part for building the model in orange, and the part that creates VisualScript from the data in blue.

function VisualScript(localhost, params, callback) { // Shows a display of all dependencies of the parameter issue. Please refer to https://www.visualscript.com/learn/visualscript-sdk/ for information on use of VisualScript // Issue the query to get the information from Jira VSCallFunctionForJiraIssue(gSDJSLocalHost, params, handleResponse); // Handle response from Query for Jira Issue function handleResponse(targ) { var i, issueDirections, issueConnector = null, issueConnectorShape, vs = {}, issue, newShape, rootShape, rootConnector, table, cell; if (!targ) { alert("No parameter entered, or parameter is not an existing issue key"); callback(null); return; }
// Build the model from the information returned by the query if (targ.fields.issuelinks) { issueDirections = {}; for (i = 0; i < targ.fields.issuelinks.length; i++) { var issuelink = targ.fields.issuelinks[i]; var issueName = issuelink.inwardIssue ? issuelink.type.inward : issuelink.type.outward; var issueKey = issuelink.inwardIssue ? issuelink.inwardIssue.key : issuelink.outwardIssue.key; var issueSummary = issuelink.inwardIssue ? issuelink.inwardIssue.fields.summary : issuelink.outwardIssue.fields.summary; if (!issueDirections[issueName]) { issueDirections[issueName] = { issues: [] }; } issueDirections[issueName].issues.push({ key: issueKey, summary: issueSummary }); } }
// Build the VisualScript vs = new VS.Document() .SetTemplate(VS.Templates.Mindmap); rootShape = vs.GetTheShape() .SetLabel(targ.key) .SetFillColor("#b3d7ee") .SetLineThickness(0) .SetTextColor("#333333"); // Issues can be outbound or inbound. For each issue in the issue list, add it to the right "outbound" or "inbound" branch of our main shape's connector if (issueDirections) { rootConnector = rootShape.AddShapeConnector(); for (var direction in issueDirections) { issueConnectorShape = rootConnector.AddShape() .SetLabel(direction) .SetShapeType(VS.ShapeTypes.Rectangle) .SetLineThickness(0) .SetFillColor("#FBD585") .SetTextColor("#333333"); issueConnector = issueConnectorShape.AddShapeConnector(); for (i = 0; i < issueDirections[direction].issues.length; i++) { issue = issueDirections[direction].issues[i]; newShape = issueConnector.AddShape() .SetFillColor("#b3d7ee") .SetLineThickness(0) .SetTextColor("#333333"); table = newShape.AddTable(2, 1); table.AddCell(1, 1) .SetLabel(issueDirections[direction].issues[i].key) .SetTextTruncate(60); table.AddCell(2, 1) .SetLabel(issueDirections[direction].issues[i].summary) .SetTextTruncate(60); } } } // Render the VisualScript callback(vs.toJSON()); } }