Atlassian Jira. Admin evolution. Part 1

Hello!

In this series of articles I will show you how the same task can be solved in many different ways in Atlassian Jira Server/Data Center.

I will solve this task with different skillsets and understanding of Atlassian Jira. I will start with the lowest quality solution and move to higher quality solutions.

After reading this series of articles you will be able to understand what is the quality of solutions you have in your Atlassian Jira.

I will not produce the complete code of the task for each solution. My goal is to show techniques which can be used to configure Atlassian Jira and let you understand the quality of each of the techniques.

Task

We need to create an Approval process for our company. It will be a very simple process but enough to create all types of solutions.

Here is the workflow for our issues:

Here is the business process:

  • during creation of an issue the manager of the reporter should be set automatically as the approver of the issue in the Approver field. The Approver field should not be available for editing.
  • the manager of a user must be taken from a lookup table. The lookup table should be managed by a lookup user who does not have admin access to Jira or access to the database.
  • only the user in the Approver field can transition an issue from the “On Approval” status to the “Ready For Work” status.
  • if the user in the Approver field has left our company (we deactivated this user), then the Approver field should be set with the head manager in our company.

The lowest quality solution possible

How would I approach this task if I knew nothing about Atlassian Jira?

DB Table

If we want to set the manager of the reporter as the approver of an issue automatically, then we need a lookup table where we would find information about the manager for each user. How to have this lookup table? Of course, we can create a table in our Jira database. And I will create one. I use Postgres for my Jira instance that is why I will take my favourite GUI for managing Postgres database and create a table:

I provided user_managers as the name of the table and added two columns: user and manager.

Now we need to let our lookup user to edit this table. We can not grant access to the database to the lookup user. We need somehow to create a UI in Jira to let the lookup user edit this table.

How to do that?

JSP

Atlassian Jira runs on Tomcat, maybe we can add an html file to Tomcat and then call it from our browser? Let’s have a look at our Tomcat root folder:

I can see in the root folder the login.jsp file. Jsp would be a good solution for us because we can produce HTML code and execute Java code in a jsp file.

Let’s try to call login.jsp from our browser.

As you can see I called the login.jsp by http://localhost:2990/jira/login.jsp and the login page has been displayed.

Good. Let’s create our own jsp page inside the root folder called mypage.jsp:

<%@ page import="com.atlassian.jira.component.ComponentAccessor" %>
<%@ page import="com.atlassian.jira.util.mobile.JiraMobileUtils" %>
<%@ taglib uri="webwork" prefix="ww" %>
<%@ taglib uri="webwork" prefix="ui" %>
<%@ taglib prefix="page" uri="sitemesh-page" %>
<html>
<head>
	<title><ww:text name="'common.words.login.caps'"/></title>
    <meta name="decorator" content="login" />
</head>
<body>
    <p>Here is my JSP Page</p>
</body>
</html>

All I do in this jsp page, I display the “Here is my JSP page” message.

And now I call mypage.jsp by http://localhost:2990/jira/mypage.jsp

As you can see the page was not found. Does it mean that we can not add our own jsp page to our Jira instance? I do not think so, I think a good restart always helps. Let’s restart our Jira and try to call mypage.jsp again.

This time we have our message, which means that our page was found and displayed.

Well, now we need to add a form for fill our lookup table in mypage.jsp file.

I will not create a table manually I will use a javascript framework for it. Let’s say webix.

I downloaded the webix js libraries and put in the webix folder in the Tomcat root folder. Then I changed mypage.jsp:

<%@ page import="com.atlassian.jira.component.ComponentAccessor" %>
<%@ page import="com.atlassian.jira.util.mobile.JiraMobileUtils" %>
<%@ taglib uri="webwork" prefix="ww" %>
<%@ taglib uri="webwork" prefix="ui" %>
<%@ taglib prefix="page" uri="sitemesh-page" %>
<%
String tableData = "[{ \"id\":1, \"user\":\"User1\", \"manager\":\"Manager1\"},{ \"id\":2, \"user\":\"User2\", \"manager\":\"Manager2\"}]";
%>
<html>
<head>
	<meta name="decorator" content="login" />
    <title>Set Managers for Users</title>
    <link rel="stylesheet" href="webix/codebase/webix.css?v=7.3.0" type="text/css" charset="utf-8">
    <script src="webix/codebase/webix.js?v=7.3.0" type="text/javascript" charset="utf-8"></script>
</head>
<body>
    <div class='header_comment'>Managers</div>
		<div id="testA" style='height:600px'></div>
		<hr>
		
		<script type="text/javascript" charset="utf-8">

		webix.ready(function(){
			grida = webix.ui({
				container:"testA",
				view:"datatable",
				columns:[
					{ id:"user",	header:"User", 			width:200 },
					{ id:"manager",	header:"Manager",		width:120 }
				],
				
				editable:true,
				editaction:"dblclick",
				autoheight:true,
				autowidth:true,

				data:'<%= tableData%>'
			});	
		});
		</script>
</body>
</html>

In this code I display the datatable widget from the webix framework:

  • I defined the tableData variable with json data. Later we shall query the user_managers table data from our Jira database into this variable
  • I imported necessary js files from webix: webix.css and webix.js (we should have used the minimised version, but it is not the point of this article)
  • I showed the table

Well, we can see a table with data and this data can be edited.

Now we have to select data from our user_managers database table. How to do it?

Have a look at this code in the mypage.jsp

<%@ page import="com.atlassian.jira.component.ComponentAccessor" %>
<%@ page import="com.atlassian.jira.util.mobile.JiraMobileUtils" %>
<%@ taglib uri="webwork" prefix="ww" %>
<%@ taglib uri="webwork" prefix="ui" %>
<%@ taglib prefix="page" uri="sitemesh-page" %>
<%
String tableData = "[{ \"id\":1, \"user\":\"User1\", \"manager\":\"Manager1\"},{ \"id\":2, \"user\":\"User2\", \"manager\":\"Manager2\"}]";
%>
<%@ page import="com.atlassian.jira.component.ComponentAccessor" %>

We import the ComponentAccessor class which is a class of Jira Java Api. After this import we can use this class in our mypage.jsp like this:

<%
JiraAuthenticationContext jac = ComponentAccessor.getJiraAuthenticationContext();
%>

Of course, to use the JiraAuthenticationContext we also need to import the JiraAuthenticationContext class. This way we can easily work with Jira Java API.

Also we can import classes from java.sql and select data from user_managers database table. Here is a code to select data from the user_managers table:

<%@ page import="com.atlassian.jira.component.ComponentAccessor" %>
<%@ page import="com.atlassian.jira.util.mobile.JiraMobileUtils" %>
<%@ page import="import java.sql.Connection" %>
<%@ page import="import java.sql.DriverManager" %>
<%@ page import="import java.sql.PreparedStatement" %>
<%@ page import="import java.sql.ResultSet" %>
<%@ taglib uri="webwork" prefix="ww" %>
<%@ taglib uri="webwork" prefix="ui" %>
<%@ taglib prefix="page" uri="sitemesh-page" %>
<%
try {

                String tableData = "";
                String user = "";
                String manager = "";
                Connection dbc = null; 
                String url = "jdbc:postgresql://localhost:5432/jira?user=jira&password=secret";
                Connection dbc = DriverManager.getConnection(url);
                ResultSet rs = null;
                String sql;
                PreparedStatement pst;
                sql = "select user, manager from user_managers";
                pst = dbc.prepareStatement(sql);
                rs = pst.executeQuery();

    
                while (rs.next()) {
                   /* 
Form the tableData variable here
                   */
                }
    
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
     
%>
<html>
<head>
	<meta name="decorator" content="login" />
    <title>Set Managers for Users</title>
    <link rel="stylesheet" href="webix/codebase/webix.css?v=7.3.0" type="text/css" charset="utf-8">
    <script src="webix/codebase/webix.js?v=7.3.0" type="text/javascript" charset="utf-8"></script>
</head>
<body>
    <p> <%= user%> <%= manager%></p>
    <div class='header_comment'>Managers</div>
		<div id="testA" style='height:600px'></div>
		<hr>
		
		<script type="text/javascript" charset="utf-8">

		webix.ready(function(){
			grida = webix.ui({
				container:"testA",
				view:"datatable",
				columns:[
					{ id:"user",	header:"User", 			width:200 },
					{ id:"manager",	header:"Manager",		width:120 }
				],
				
				editable:true,
				autoheight:true,
				autowidth:true,

				data:'<%= tableData%>'
			});	
		});
		</script>
</body>
</html>

I will not complete this solution. I think you have an idea how it works. You can add your own Java code to a jsp page where you would check that the current user is the lookup user, you can add buttons to create, delete and save rows in the table. And in the end you will have a page which will let the lookup user manage the user_managers table from Jira UI.

JavaScript

Now we can manage the user_managers table, let’s try to set the Approver field automatically when we create a new issue.

Here is the create issue dialog:

As you can see we’ve got the Reporter field and the Approver field. If the Reporter is Alexey Matveev then we should assign the manager for Alexey Matveev from the user_managers table to the Approver field.

Well, this issue create dialog is produced by HTML, we can open Developer Tools in the Chrome Browser and have a look how it is done:

I can see that the value of the Reporter field is admin and I can find this field in the html page by the “optgroup” tag with id “reporter-group-suggested” and the child tag “option” with the selected attribute.

Let’s have a look at the Approver field:

As you can see the value for the field is empty now and I can find this field in the html page by id equal to customfield_10300.

Ok. Now I need to add the manager for the admin user into the user_managers table

And next I would write a javascript code to search for the tag “option” with the selected attribute and this tag is the child of the “optgroup” tag with id “reporter-group-suggested”. After I found it, I would take the value of this “option” tag (it would be the user), select the manager for the user and add this manager as the value to the input tag with id “customfield_10300”.

But how to add this JavaScript code to Atlassian Jira?

Well, there are multiple ways. The most used way is to add a JavaScript code to the announcement banner.

Let’s first write the JavaScript code:

<script>
      setInterval(function(){ 
         if ($("optgroup#reporter-group-suggested > option:selected").val()) {
             $("#customfield_10300").val("manager1");
         }
      }, 3000);
</script>

This JavaScript code does the following:

  • starts a loop. If we do not make a loop, then this code will work before the reporter field has been displayed. In this case the reporter will be empty and we will not get the manager for the empty user. As a result the Approver field will remain empty.
  • every 3 seconds looks for the value in the reporter field and if found, sets the manager1 value to our Approver field. We set manager1 all the time. We will take the manager of the reporter from database later.

Add this JavaScript code to the announcement banner:

And now if I create a new issue, I will have the Approver field set to manager1:

Let’s push the Create button and have a look at the issue:

Nice. The manager1 user was set as the approver of the issue.

Next we need to disable the editing of the Approver field. All we need to do is to add the disabled property:

$("#customfield_10300").prop( "disabled", true );

Here is the complete code:

<script>
      setInterval(function(){ 
         if ($("optgroup#reporter-group-suggested > option:selected").val()) {
            $("#customfield_10300").val("manager1");
            $("#customfield_10300").prop( "disabled", true );
 
         }
      }, 3000);
</script>

And now the Approver field is disabled:

Done! Let’s push the Create button and have a look at the created issue:

As we can see the Approver field is empty. What happened? I guess, the problem is our added “disabled” property.

Let’s fix it! How else could we restrict editing? We could unfocus the Approver field if somebody wants to edit this field. In this case the field will not be still editable. Here is the code:

<script>
      setInterval(function(){ 
         if ($("optgroup#reporter-group-suggested > option:selected").val()) {
            $("#customfield_10300").val("manager1");
            $("#customfield_10300").focus(function() {
                 this.blur();
            });
         }
      }, 3000);
</script>

Let s have a look at the create issue screen:

The Approver field can not be edited. Now let’s push the Create button and have a look at the created issue:

And the Approver field has the manager1 value.

At last we did it!

More JS

Now we need to let only the user in the Approver field to transit an issue from the “On Approval” status to the “Ready For Work” status.

The easiest way is to hide the Ready For Work button in the Jira issue view:

Let’s open again the Jira view issue screen in the Developer Tools. Explore how we can find the “Ready For Work” button and then we will write a JavaScript code to hide this button if the user does not equal to the user in the Approver field. Here is the code:

 setInterval(function(){ 
         if ($("#customfield_10300-val").text()) {
           if (JIRA.Users.LoggedInUser.userName() == $.trim($("#customfield_10300-val").text())) {
            $('#action_id_11').addClass('hidden'); ;
           }
         }
      }, 3000);
  • again I start a loop. Without a loop nothing will work
  • I check if the currently logged in user equals to the value in the Approver field
  • if so I add class hidden to the Approver field

Now let’s open an issue under the admin user:

As you can see the “Ready For Work” button is absent.

And if we login under the manager1 user then the button will be there:

You can ask how I found the JIRA.Users.LoggedInUser.userName() method to get the currently logged in user. It is simple. You can use the Developer Сonsole to find out what functions are available in the page. We will discuss later if we should use these methods or not.

A bit more JSP

One thing left. I set the Approver as manager1 all the time, but I need to get the manager for the reporter from my user_managers table. How to do it?

Again we can create a jsp page. Let’s call this page getmanagerforuser.jsp with the following code:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%
try {
   String reporter=request.getParameter("reporter");
   response.getWriter().write("{\"reporter\":\""+ reporter+ "\",\"manager\":\"manager1\"}");

}   catch(Exception e)  {
}
%>

This code gets the url parameter which is called reporter and produces a json where the value of the reporter field is the value of the url parameter and the value of the manager field is manager1. Let’s call this jsp page:

As you can see I passed the myreporter value as the reporter parameter and the json file contains the value of the passed parameter.

Good! Now we need to call our jsp page from our JavaScript code. We will pass the value of the reporter field and we will get the manager. We can do it with the following JavaScript line:

            $.get( "/jira/getmanagerforuser.jsp?reporter="+ $("optgroup#reporter-group-suggested > option:selected").val(), function( data ) {
                 $("#customfield_10300").val(data.manager);
            });

In this line we call the getmanagerforuser.jsp, we pass the value of the reporter field and get the manager. Let’s open the create issue screen:

The field is empty. If we have a look at the Developer Tools we will see:

The jsp page was called correctly. The reporter parameter has the value admin and it is correct. Let’s have a look at the response:

As you can see our json is just a part of an html content and we need to cut the json from the content. Lets cut it. Here is the code:

 $.get( "/jira/getmanagerforuser.jsp?reporter="+ $("optgroup#reporter-group-suggested > option:selected").val(), function( data ) {
                 var startIndex = data.indexOf("<section id=\"content\" role=\"main\">") +34;
                 var endIndex = data.indexOf("</section>", startIndex);
                 var str = data.substring(startIndex, endIndex);
                 $("#customfield_10300").val(JSON.parse(str.trim()).manager);
            });

And now if we create an issue we will see that the Approver field has the manager1 value:

Here is the final JavaScript code:

<script>
      setInterval(function(){ 
         if ($("optgroup#reporter-group-suggested > option:selected").val()) {
            $.get( "/jira/getmanagerforuser.jsp?reporter="+ $("optgroup#reporter-group-suggested > option:selected").val(), function( data ) {
                 var startIndex = data.indexOf("<section id=\"content\" role=\"main\">") +34;
                 var endIndex = data.indexOf("</section>", startIndex);
                 var str = data.substring(startIndex, endIndex);
                 $("#customfield_10300").val(JSON.parse(str.trim()).manager);
            });

            
            $("#customfield_10300").focus(function() {
                 this.blur();
            });
         }
      }, 3000);
      setInterval(function(){ 
         if ($("#customfield_10300-val").text()) {
           if (JIRA.Users.LoggedInUser.userName() != $.trim($("#customfield_10300-val").text())) {
            $('#action_id_11').addClass('hidden');
           }
         }
      }, 3000);
</script>

And now we need to change our getmanagerforuser.jsp so that the manager would be taken from our database table. I will not provide this code, but you know how it can be done.

At last the jsp part and js part are over. Only one requirement left.

DB triggers and functions

If the manager has been deactivated we need to change this manager to the supermanager user. How to do it? Easy! We will create a trigger for the cwd_user table. And if a user is deactivated then we will update the customfieldvalue.

First we will create a pl sql procedure:

CREATE OR REPLACE FUNCTION public.changeusermanager()
	RETURNS trigger
	LANGUAGE plpgsql
AS $function$
	BEGIN
		IF NEW.active <> OLD.active and NEW.active = 0 THEN
		 	update customfieldvalue set stringvalue = 'supermanager' where customfield  = 10300 and stringvalue = NEW.user_name; 
		END IF;

		RETURN NEW;
	END;
$function$
;

And now we can create a trigger:

create trigger changemanager before
update
    on
    public.cwd_user for each row execute procedure public.changeusermanager();

Let’s check how it works. Open the User Management Jira settings and deactive the manager1 user:

Click the Update button and let’s open one of our created issues:

It worked!

We created a solution for our approval process and ready to inform our manager that the task has been completed!

Conclusion

For this kind of solution we used the following techniques: JSP pages, JavaScript code in its worst form, database triggers and functions. If you have this kind of techniques in your Jira instance, you should be really worried about the quality of your solutions.

Moreover, if you or your Jira administrator/developer use the Developer Tools to find out how Jira works and make JavaScript code based on what he/she noticed in the Developer Tools, your solutions also must be low quality.

In the next articles I will explain why these techniques are low quality and, what is most important, how to make a better quality solutions.

Leave a Reply

%d bloggers like this: