Visualizing At Risk Tasks with VisualScript

Overview

Management wanted to know what tasks were at risk for being late and which issues were holding things back. So I decided to create a script that lets you easily see issues that are approaching their due date or are late.

At risk issues in Jira data visualization

A visual will help focus on issues that may need attention without searching through a long list of issues in our current sprint. The script can automate this process so we can always call up a list of projects and issues automatically just by providing some basic information like the name of the project and a due date.

The idea is the user would provide the name of the projects they were interested in tracking and a due date and my script would automatically generate a visual. If an issue is in one of the parameter projects and has a due date that is n ("days before") days prior to the current date, then it's included in the script's output. I wanted a shape for each issue that was "at risk", organized in some sort of tree structure. A mind map fit my needs perfectly.

Once I settled on a mind map, I decided what information I wanted to present for each issue. I wanted to include the key for each issue, the first 60 characters of the summary, the type (story, sub-task, etc), the priority, and the due date. With that many information "parts", I decided to organize each issue's "shape" into a table. Using a table gave me a way to put all the information in the same place in every shape. I also decided to make the issue key a hyperlink to the Jira page for that issue.

Script UI

The user interface I set up to allow users to interact with this script looks like this:

Field for projects takes a comma-separated list of project keys.

I also created an input for specifying the number of days prior to the current date they wanted a report for. Any issue that has a due date later than today's date minus this number of days is considered "at risk".

How I Implemented It

Obtaining the Data

To create the script, we first have to gather the information required from Jira. Using the parameters passed in from the UI, I use a helper function available in the built-in Script Editor (also the Data Query SDK) called VSCallFunctionForJiraQuery() to query Jira's REST API. The string "projects" in the code block below is taken from the parmsObj with a little clean up (quotes around each project name added, extraneous blanks striped out, etc).

VSCallFunctionForJiraQuery(gSDJSLocalHost, 'project in (' + projects + ') AND status IN ("Open", "In Progress" ) AND duedate > -' + paramsObj.daysBefore+ 'd ORDER BY project ASC, duedate ASC', queryResponseHandler);

Building the Model

To get the results, you simply call the queryResponseHandlerfunction(), another helper function available in the Script Editor.

Once I have the data from Jira, I need to build a model of the information that I want to depict. I scan through the list of issues returned by the query, creating an object containing a property for each project. The project property is an array of the issues of that project.

// Build the model from the issues list var issuesLen = issues.length; for (i = 0; i < issuesLen; i++) { // If we don't have an entry for this project yet, then add it. if (!projects[issues[i].fields.project.name]) { projects[issues[i].fields.project.name] = {issues: []}; } // Add this issue to the project's issues list projects[issues[i].fields.project.name].issues.push(issues[i]); }

Constructing the VisualScript from the Model

Once the model is built, I use it to construct VisualScript SDK objects. Earlier I decided to use a mind map to display this information, so I'm setting the type of visual to be created in VisualScript to be a mind map. After the document and "root shape" is constructed, I add a "branch" to the root shape's connector for each project, and then add a shape for each issue in that project.

// Now build the VisualScript var vs = new VS.Document(); vs.SetTemplate(VS.Templates.Mindmap); var rootShape = vs.GetTheShape() .SetFillColor("#b3d7ee") .SetLineThickness(0) .SetLabel("Projects"); var rootShapeConnector = rootShape.AddShapeConnector(); for (var project in projects) { currProjectShape = rootShapeConnector.AddShape() .SetFillColor("#FBD585") .SetLineThickness(0) .SetLabel(project); currProjectConnector = currProjectShape.AddShapeConnector(); // For each issue in this project, add a shape for it to the project connector var projectIssuesLen = projects[project].issues.length; for (i = 0; i < projectIssuesLen; i++) { var issue = projects[project].issues[i]; // Add a shape for the issue to the current project connector newShape = currProjectConnector.AddShape() .SetID(ID++) .SetFillColor("#b3d7ee") .SetLineThickness(0) .SetMinWidth(250) .SetShapeType(VS.ShapeTypes.Rectangle); // Set up the table to hold the issue information var newShapeTable = newShape.AddTable(3, 2); <Additional Information pushed from issue to the newShape> }

Rendering the VisualScript

The last step is to use the callback function to render the VisualScript as an image and display it in the Jira 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 parameters in the future. In the top section of the VisualScript edit page, you'll see a field labeled "Parameter Template". The specification string for the UI Builder's "Parameter Template" is:

{"projects": "{{formValue1}}", "daysBefore": "{{formValue2}}"}
      

The placement of the quotation marks is important to ensure that the script receives a parameter string that can be passed as an argument to JSON.parse to produce an object representation of the parameters.

When the I enter this string into VisualScript, the UI Builder displays a form that I can use to customize. For each parameter, I can choose a displayed name, an optional default value, and a brief description of the parameter and how it's used. Initially, it looks like this:

UI builder form

After overriding the defaults and providing additional information, I ended up with this:

UI builder form

More Information

This is just one example of a visual that can be produced using data from Jira with VisualScript with a minimum amount 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)
        {
        var i;
        var paramsObj = JSON.parse(params);

        var projectTokens = paramsObj.projects.split(',');

        var projects = "";

        
// Build the projects specification as a comma-separated, enclused in quotes list of project names for (i = 0; i < projectTokens.length; i++) { if (projects.length > 0) { projects += ","; } projects += "\"" + projectTokens[i].trim() + "\""; } // Query Jira for all the open & in progress issues in the specified projects that have due dates later than the date threshold (now - paramsObj.daysBefore days) VSCallFunctionForJiraQuery(gSDJSLocalHost, 'project in (' + projects + ') AND status IN ("Open", "In Progress" ) AND duedate > -' + paramsObj.daysBefore + 'd ORDER BY project ASC, duedate ASC', queryResponseHandler); function queryResponseHandler(issues) { var browseLink = localhost + "/browse/"; var currProjectShape, currProjectConnector; var projects = {}; var i, ID = 1; if (!issues) { alert("An error ocurred processing your request"); callback(null); } if (issues.length == 0) { alert("No issues found matching your parameters"); callback(null); } if (issues.length > 100) { alert(issues.length + " issues were returned. The maximum that can be displayed is 200. Please reduce the number of projects or adjust the days-before parameter"); callback(null); }
// Build the model from the issues list var issuesLen = issues.length; for (i = 0; i < issuesLen; i++) { // If we don't have an entry for this project yet, then add it. if (!projects[issues[i].fields.project.name]) { projects[issues[i].fields.project.name] = { issues: [] }; } // Add this issue to the project's issues list projects[issues[i].fields.project.name].issues.push(issues[i]); }
// Now build the VisualScript var vs = new VS.Document(); vs.SetTemplate(VS.Templates.Mindmap); var rootShape = vs.GetTheShape() .SetFillColor("#b3d7ee") .SetLineThickness(0) .SetLabel("Projects"); var rootShapeConnector = rootShape.AddShapeConnector(); for (var project in projects) { currProjectShape = rootShapeConnector.AddShape() .SetFillColor("#FBD585") .SetLineThickness(0) .SetLabel(project); currProjectConnector = currProjectShape.AddShapeConnector(); // For each issue in this project, add a shape for it to the project connector var projectIssuesLen = projects[project].issues.length; for (i = 0; i < projectIssuesLen; i++) { var issue = projects[project].issues[i]; // Add a shape for the issue to the curent project connector newShape = currProjectConnector.AddShape() .SetID(ID++) .SetFillColor("#b3d7ee") .SetLineThickness(0) .SetMinWidth(250) .SetShapeType(VS.ShapeTypes.Rectangle); // Set up the table to hold the issue information var newShapeTable = newShape.AddTable(3, 2); //Issue Type newShapeTable.AddCell(1, 1) .SetLabel(issue.fields.issuetype.name) .SetTextSize(8) .SetTextAlignH(VS.TextAlignH.Left); // Priority name (High/low, etc) decorated with the status color newShapeTable.AddCell(2, 1) .SetLabel(issue.fields.priority.name) .SetTextSize(8) .SetTextColor(GetPriorityColor(issue)) .SetTextAlignH(VS.TextAlignH.Left); var dueDate = "No Due Date"; if (issue.fields.duedate) { var ddate = new Date(issue.fields.duedate); dueDate = ddate.toLocaleDateString(); } newShapeTable.AddCell(3, 1) .SetLabel(dueDate) .SetTextSize(8) .SetTextAlignH(VS.TextAlignH.Left); newShapeTable.AddCell(1, 2) .SetLabel(issue.key) .SetTextHyperlink(browseLink + issue.key) .SetTextAlignH(VS.TextAlignH.Left); // Summary newShapeTable.AddCell(2, 2) .SetLabel(issue.fields.summary) .SetTextSize(8) .SetTextTruncate(75) .SetTextAlignH(VS.TextAlignH.Left); newShapeTable.AddColumnProperties(1) .SetWidth(60) .SetLineThickness(0); newShapeTable.AddRowProperties(1) .SetLineThickness(0); } } callback(vs.toJSON()); function GetPriorityColor(issue) { var statusColors = [ "#E60000", "#E60000" ,"#FFF600", "#009243", "#009243"]; var colorIndex = parseInt(issue.fields.priority.id); colorIndex--; // priority.id is 1-based. Make index 0-based if (isNaN(colorIndex) || (colorIndex < 0) || (colorIndex >= statusColors.length)) { return "#000000"; } return statusColors[colorIndex]; } } }