Creating a Dynamic Onboarding Experience on Jira: A Step-By-Step Guide

Anik Chatterjee
Ounass
Published in
11 min readMar 18, 2020

Onboarding is essential across all companies. Imagine you’re a new employee, walking into the office where everyone is unknown to you. You’re excited about where this journey is going to take you but have no idea where to begin. In this case, if you’re presented with a proper onboarding kit, you have a goal set in front of you that’s both motivating and helps you build a proper impression towards the team you’re about to start working with. First impressions matter quite unquestionably. Even if this is looked at from a different perspective, as companies grow they implement new methodologies of working which becomes quite an essential part of their culture. “What we do” and “How we do it” matters and without an onboarding process in place, all a new joiner can rely on is their past experience and execute things on their own without having much in-depth knowledge on the ways of working followed by the company, both culturally and technically which could potentially prove to be concerning down the line. This uncertainty can most definitely be disentangled by onboarding an employee via a structured process in place.

As our team at Al Tayer Digital grew, the aforementioned reasons became more evident and it was necessary to implement a proper framework in place. It was essential for new joiners to be familiar with the intricacies of our systems, tools, & architectures which are in place in the company, the “why’s” and the “how’s” of their job domains, getting to know their fellow peers, so on and so forth.

In addition to this, it also is crucial to make sure that our onboarding process is goal-driven and consistent across all new joiners. Having a set of tasks laid out for them helps them keep track of what needs to be done next, as well as makes it structured and successively progressive.

We sat down to brainstorm on how to approach this and achieve what we sought out to do. We jotted down what we needed
* A tool to work with — We had Jira for task creation as well as Confluence for documentation
* A system in place — We started with common tasks for all new joiners and role-specific tasks for the roles employees were being onboarded under. Based on the roles, these were further divided into
** Meet — Where new joiners would be asked to meet and sit down with other peers
**
Read — Where new joiners would be asked to go through and read documentation mentioned on the Jira ticket
**
Listen — Watch videos related to organizational events or other miscellaneous videos to understand further how the company operates
**
Execute — Execution of test cases or code
**
Write — Writing test cases or code
* Changeable Parameters — We needed parameters that could be configured by changing values on the fields on Jira. We here at Al Tayer Digital have two main teams; Ounass & Nisnass. Based on the brand the new member joined under, the peers they’d meet would differ, so the brand and name fields should be dynamic.
* Process Automation— I happened to be familiar with writing scripts on Jira so I was more than happy to be a part of this project and be able to write code to automate this process.

Now that we had an idea of what needs to be done, we started executing upon our action points.

Onboarding Kanban Board on DO Project

The first stage was setting up an ‘Onboarding’ project on Jira. This would be the project where all the tasks would reside with a Kanban board associated with it so tasks can be transitioned easily.

Onboarding Task Sheet Snippet

Next was to map the task types to their respective summaries and descriptions in a tabular format which would later be mapped to the summary and description fields of the tasks. Description text was also modified to contain parameters such as {BuddyName}, {LineManagerName}, {BrandName}, and so on to act as placeholders and be dynamically changed by the script.

After the sheet was ready to go, it was essential for us to cover the parameters and map each of them to a separate field on the Jira ticket creation screen, so they can be replaced by the inputs in the fields provided by the creator. Since screen configurations on Jira are associated withissuetypes (ticket type), I created a task type namely ‘Onboarding’ and associated this screen exclusively for that ticket type. This is how the creation screen looks like

Onboarding Issue Create Screen

After the fields on Jira were configured and embodied onto the create screen, the final remaining piece of the puzzle was to automate the task creation process.

Doing it through Google Sheets seemed to be an option I wanted to explore by invoking the Sheets API v4, and Jira’s Rest API is powerful in itself so I set out to work on this.

Before writing the script, I set a condition on Scriptrunner that this script will execute only if the condition issue.issueType.name.match(‘^(Onboarding)$’) != null(Wherein it detects that the issue type is called ‘Onboarding’) is met to avoid any unnecessary triggers. I also set the script to execute the “Create Issue’ event and only for the Onboarding (DO) project. Now that the fail-safes were in place, I started writing code.

First and foremost, we needed data for the tasks. To be able to read the data, I invoked the get response to Sheets API and passed the spreadsheetId, sheetRange, & API_key as parameters and mapped them to a list variable. Since I only want data from the first three columns, I’ve specified the sheet range as A:C.

def getColumns = get(“https://sheets.googleapis.com/v4/spreadsheets/{spreadsheetId}/values/(Sheet1!A:C}?key={API-KEY}")
.asObject(Map)
.getBody()
def colVal = getColumns.values

After getting the sheet object, now we need to obtain the fields object from Jira to get the fields we want, such as Line Manager Name, Buddy Name, and the rest. We call the field object and run it once (since API requests are an expensive function in terms of execution time) and get the list of all the fields.

@Field customFields = get("/rest/api/2/field")
.asObject(List)
.body
.findAll { (it as Map).custom } as List<Map>

We pass the field name as an argument when calling the function below

def getField(x)
{
def customFieldId = customFields.find { it.name == x }?.id
def customF = issue.fields[customFieldId]
def customFVal
if(customF != null)
{
customFVal = “[“ + customF.displayName + “|~accountid:” +
customF.accountId + “]” as String
}
else
{
customFVal = “[$x not set]”
}
return customFVal
}
def buddyName = getNameField('Buddy Name')
println buddyName
//output:
//
[John Doe|~accountid:5c76544ea7a4c9433528ca2e]
//if buddy is set as John Doe for example
//else
//[Buddy Name not set]
def lineManagerName = getNameField('Line Manager Name')
println lineManagerName
//output:
//
[Jane Doe|~accountid:9b7623235fc94335oitssb2e]
//if buddy is set as Jane Doe for example
//else
//[Line Manager Name not set]

to help us convert the field value to a @Mention tag. Jira does not recognize a string if it is preceded with an @ symbol. It needs to be in a special format, i.e [John Doe|~accountid:5c76544ea7a4c9433528ca2e]

Depicting how the value from the name field should replace the parameter on the Jira ticket

Next step would be to get the transpose of our parent list structure which was mapped to a list variable. We get the index values of each of the child lists and map them to a separate list by:

println colVal //output: 
//[[type1, summary1, description1],[type2, summary2, description2],[type3, summary3, description3]...]
def type = colVal*.getAt(0)
def summ = colVal*.getAt(1)
def desp = colVal*.getAt(2)
println type//output:
//type = [type1, type2, type3,.....]
println summ//output:
//summ = [summary1, summary2, summary3,.....]
println desp//output:
//desp = [description1, description2, description3,.....]

As a fail safe to avoid creating any tasks without descriptions or without summaries, I assert the column size values against each of the three columns.

assert type.size() == summ.size() && assert type.size() == desp.size() && assert summ.size() == desp.size()

Now that we have the column lists, we need to run a regex (or regular expression) check and replace those tags on the third list, i.e the description list. (Since Jira does not support tagging on task summaries). The following lists

println desp//output:
//[description1, description2, description3,.....]
println fieldString//output:
//[{EmployeeName}, {BuddyName}, {LineManagerName},.....]
println fieldVal//output:
//[employeeName, buddyName, lineManagerName,.....] <- this is a list of variables

are passed as arguments to the function below

println desp//output:
//[When you join the organization, we are setting a buddy to help you with orientation. {BuddyName} is going to be your buddy. I’ll make sure to introduce you two, .............]
def regexFunc(a, x, y)
{
def temp = []
a.eachWithIndex { it, s ->
x.eachWithIndex { xit, i ->
def pattern = ~/\{([${x[i]}]*)\}/
def result = pattern.Matcher(a[s])
result.eachWithIndex { rit, r ->
def getRes = result[r][0]
if(x[i] == getRes)
{
a[s] = a[s].replace(getRes, y[i])
}
}
}
temp.add(a[s])
}
return temp
}
desp = regexFunc(desp, fieldString, fieldVal)
println desp
//output:
//[When you join the organization, we are setting a buddy to help you with orientation. @Username is going to be your buddy. I’ll make sure to introduce you two, ..............]
If buddy name is set then…

After this function has executed, if the Buddy Name field is set as let’s say Anik Chatterjee,

…this becomes…

the {BuddyName} parameter inside the text will be replaced with Anik Chatterjee on the task description.

…this…

The tag also notifies the user being tagged on Jira via its notification system.

…else this if field is empty.

If no value is set for the field, the parameter gets replaced with [$fieldname not set] (in this case, [Buddy Name not set])

Now that we have the values we need, we need to perform a REST call to write these values to Jira. We first get the issue id by performing a GET request as follows and map the issue’s ID to a variable

@Field getIssue = get(“/rest/api/2/issue/” + issue)
.asObject(Map)
.getBody()
def parentIssueId = getIssue.id

Since we are also dynamically assigning the user for every task that’s been created, we also store the assignee’s accountId in a separate field called, which will be used to set the assignee on the sub tasks, and simultaneously the parentIssueId will set the sub task's parent to be that of the Onboarding issue type. issueTypeId and projectId will be the ID values of both the sub-task issue type number on Jira as well as the DO project’s number (in our case). In our case, they are 10009 and 12755 respectively. Now that we our variables, we call this function and pass our summary and description values as arguments

def restFunc(x, y)
{
def respA = post(“/rest/api/2/issue”)
.header(“Content-Type”, “application/json”)
.body(
fields: [
“project” : [
“id”: projectId
],
“issuetype”: [
“id”: issueTypeId
],
“parent”: [
“id”: parentIssueId
],
“summary”: x,
“description”: y,
“assignee”: [
“id”: assigneeAccountId
],
])
.asObject(Map)
assert restFunc.status == 200 //whether request was successful
}

This can be done in any preferred method, but I use a switch case since there are multiple variations to which tasks need to be read and posted in miscellaneous cases.

If role is set as Fullstack Developer, then…

This switch is done based on the value provided to the ‘Role’ field on Jira.

…only the fields in green are being passed to the function to be written onto Jira as separate tasks.

As depicted in the example sheet snippet on my left, if let’s say a person was joiner as a ‘Fullstack Developer’. Based on the role that’s being set on the onboarding task screen, only the tasks in green would be made for that person, and not the ones in red. This is the same with any other field value for the ‘Role’ field wherein the tasks created and assigned to the person include the common tasks and role-specific tasks.

I’ve depicted an example of a successive POST request below

INFO - Serializing object into 'interface java.util.Map'
INFO - POST /rest/api/2/issue asObject Request Duration: 1200ms

and also the nano time log from execution to completion of all the requests (in our case, role name was ‘Product’). Our product role comprises of 28 tasks. Execution time took about 36684ms to complete execution.

request duration * number of API calls + additional cost (in time) incurring operations = total execution time

INFO - Execution time in nanoseconds : 36684034154
INFO - Execution time in milliseconds : 36684
After a successful POST function, a new sub-task is created on Jira and the fields…

A POST request duration to an issue in our case could be anywhere between 900ms and 1600ms with variance depending on the number of fields being written. Since we also performed three other API calls in addition to this to get the Sheet values, get the created issue and get the Jira fields, this checks out.

…get written onto that sub-task (notice how the parameter has also changed to a mention tag)

After a POST function succeeds for issue creation , a new sub-task is made with the summary and description both from the sheet. The assignee is the same that’s set on the parent / main task during creation. Then the next values in the list are passed as arguments to the function and another POST function is executed as the next sub-task in the ‘Onboarding’ Epic.

Demonstration of the tasks being made on Jira on jirabot for Slack

On my left is a demonstration of tasks being created in real-time as visualized on our ‘jirabot’ we use on Slack. Since the switch case is sequentially executed, the API requests do not precede or succeed the other without adhering to the sequence, allowing us to structure our tasks wherein one has to be done after the previous one has been accomplished by the assignee in order for them to be on track and the process be streamlined.

And there we have it. With Jira being dynamic in terms of its functionality and accessible to almost every new joiner since we closely work with the tool, this implementation was quite robust and easy to execute at the same time. Fill in a couple of fields, click a button, and the script does the rest.

Below I’ve demonstrated how the structure of the Jira tasks looks like on our ‘Onboarding’ project.

Demonstration of how the tasks are structured once the code has executed.

In addition to everything I’ve covered, we’re also gathering feedback regarding this process and understand how can this be further optimized. Every new joiner writes a review on their last onboarding task to provide feedback, improvement points, or general suggestions.

Here are a couple of feedbacks we received by our fellow peers regarding this Onboarding process

“Thank you for this very informative and well-organized onboarding process. It is a very nice way to prepare a newcomer to the work, just like the tutorial stage in the introduction of computer games.

I believe I’ve gained a high-level perspective about the company, its products/services as well as technical details of the development process.”

— Mahmut Ö, Fullstack Developer

“These onboarding tasks along with the details have been very helpful to get familiar to our products,environments, etc. Highly appreciated.”

— Jitendra Gupta, Lead DevOps Engineer

“I appreciate having been part of this onboarding process. I felt that the tasks were both well thought out as well as constructive and they were valuable in helping me know the organization better.”

— Roopa Khatri, Scrum Master

With this process in place, we are well on our way of accomplishing the goals we sought out to achieve.. to implement a goal-driven process in order to help the new members of our team feel more welcome and propelled to work with us and for them to grasp the “Why’s” and “How’s” about our company much better.

--

--

Anik Chatterjee
Ounass
Writer for

Scrum Master & Assisting Delivery, Deep Learning & Automation Enthusiast, Wordsmith