📜 ⬆️ ⬇️

Jira customization for your needs. Perfect flow and perfect ticket


If you work in an IT company, then most likely your processes are built around the well-known Atlassian product - Jira. There are many task trackers on the market for solving the same problems, including open-source solutions (Trac, Redmine, Bugzilla), but perhaps it is Jira that is most widely used today.

My name is Dmitry Semenikhin, I am a Timlid in the company of Badoo. In a small series of articles, I will tell you exactly how we use Jira, how we set it up for our processes, what was “screwed” on top and how we turned the issue-tracker into a single communication center for the task and simplified our lives. In this article, you will see our flow from the inside, learn how you can “twist” your Jira, and read about the additional features of the tool that you might not have known.

The article focuses primarily on those who are already using Jira, but may be experiencing difficulties with the integration of its standard capabilities into existing processes in the company. Also, the article may be useful for companies that use other task trackers, but are faced with some limitations and are thinking about changing the solution. The article is not built on the principle of "problem - solution", in it I describe the existing tools and features built by us around Jira, as well as the technologies that we used to implement them.

Additional features Jira


To make the subsequent text more understandable, let's look at what tools Jira provides to us for implementing non-standard requests - those that go beyond the standard Jira functionality.
')

REST API


In general, an API command call is an HTTP request to an API URL specifying the method (GET, PUT, POST and DELETE), the command and the request body. The request body as well as the API response are in JSON format. An example request that returns a JSON representation of the ticket:

GET /rest/api/latest/issue/{ticket_number} 

Using the API, you can, using scripts in any programming language:


Detailed API documentation is provided here .

We wrote our own high-level Jira API client in PHP that implements all the commands we need. Here is an example of commands for working with comments:

 public function addComment($issue_key, $comment) {  return $this->_post("issue/{$issue_key}/comment", ['body' => $comment]); } public function updateComment($issue_key, $comment_id, $new_text) {  return $this->_put("issue/{$issue_key}/comment/{$comment_id}", ['body' => $new_text]); } public function deleteComment($issue_key, $comment_id) {  return $this->_delete("issue/{$issue_key}/comment/{$comment_id}"); } 

Webhooks


Using the webhook, you can customize the call to an external callback function on your host for various events in Jira. At the same time, you can configure as many such rules as you like so that different URLs will be “twitched” for different events and for tickets that correspond to the filter specified in the webhook. The webhooks configuration interface is available to the Jira administrator.

As a result, you can create rules like this:

Name : “SRV - New Feature created / updated”
URL : www.myremoteapp.com/webhookreceiver
Scope : Project = SRV AND type in ('New Feature')
Events : Issue Updated, Issue Created

In this example, the specified URL will be called for the events of creating and changing tickets corresponding to the Scope filter. In this case, the body of the request will contain all the necessary information about what exactly has changed and what event has occurred.

It is important to understand that Jira does not guarantee that your event will be delivered. If the external URL did not answer or answered with an error, it will not be seen anywhere (except for logs, perhaps). Therefore, the webhook event handler should be as reliable as possible. For example, events can be put in a queue and try to process until it is successful. This will help solve problems with temporarily unavailable services, for example, any external database necessary for proper event handling.

Detailed webhooks documentation is provided here .

ScriptRunner


This is a plugin for Jira, a very powerful tool that allows you to customize a lot in Jira (including it can replace webhooks). To use this plugin requires knowledge of Groovy. The main advantage of the tool for us is that you can embed custom logic online in the flow. The code of your script will be executed immediately in the Jira environment in response to a specific action. For example, you can make your button in the ticket interface, clicking on which will create tickets associated with the current task or run unit tests for this task. And if something goes wrong, you, as a user, will immediately find out about it.

Those interested can read the documentation .

Flow: what is hidden under the hood


And now how we apply additional features of Jira in our projects. Consider this in the context of passing our typical flow ticket from creation to closing. At the same time about the flow itself will tell.

Open / backlog


So, first the ticket gets into the backlog of new tickets with the Open status. Then the component lead, after seeing the new ticket on its dashboard, decides whether to assign a ticket to the developer right now or send it to the backlog of known tickets ( Backlog status) to assign it later when a free developer appears and higher priority tickets are closed. This may seem strange, since it seems logical to do the opposite: create tickets in the Backlog status, and then transfer them to the Open status. But this scheme has taken root in our country. It allows you to easily configure filters to reduce the time taken to decide on new tickets. An example of a JQL filter that shows new tasks for a lead:

Project = SRV AND assignee is EMPTY AND status in (Open)

In progress


Technical nuances of working with Git
It should be noted that our work on each task is carried out in a separate Git-branch. As for this, we have an agreement that the name of the branch at the beginning must contain the ticket number. For example, SRV-123_new_super_feature . Also, comments to each commit in a branch must contain a ticket number in the format [SRV-123]: {comment}. We need such a format, for example, to correctly remove a “bad” task from a build. How this is done is described in detail in the article .

These requirements are controlled by git-hooks. For example, here is the contents of prepare-commit-msg, which prepares a comment for the commit by getting the ticket number from the name of the current branch:

 #!/bin/bash b=`git symbolic-ref HEAD| sed -e 's|^refs/heads/||' | sed -e 's|_.*||'` c=`cat $1` if [ -n "$b" ] && [[ "$c" != "[$b]:"* ]] then echo "[$b]: $c" > $1 fi 

If a commit with a “wrong” comment is attempted to be launched, such a push will be rejected. Also, an attempt to push a branch without a ticket number at the beginning will be rejected.

When the ticket gets on the developer, first of all it is decomposed. The result of the decomposition is the developer's idea of ​​how to solve the problem and how long the solution will take. After all the main details have been clarified, the ticket is transferred to the In Progress status, and the developer starts writing code.

We have decided to set the due date task at the moment when it is transferred to the In Progress status. If the developer did not do this, he will receive a reminder in the corporate HipChat messenger. Special script every two hours:


Having made all the necessary commits, the developer pushes the thread into a common turnip. In this case, Git-hook post-receive is triggered, which makes a lot of interesting things:



( branchdiff is a link to a branch with a head from which the current branch originated, in our tool review of the Codeisok code);


On review


Having written the code and independently making sure that all the requirements for the task are met, and the tests are not broken, the developer assigns a ticket to the reviewer ( On Review status). Usually the developer himself decides who will review his ticket. Most likely, it will be another developer who perfectly understands the necessary part of the code. The review is performed using the Codeisok tool, which opens immediately with the necessary diff by clicking on the link branchdiff in the field of the Commits ticket or on the link in the form of a hash of the commit in the comments.

The reviewer sees something like this:


Having completed the review, the reviewer clicks the Finish button, and, among other things, at this moment the following occurs:




Resolved


Further, if the review was successful, the ticket is sent to the backlog of QA-engineers in the Resolved status. But along with this, using the webhook on the resolved event in the background, automatic tests are run on the code of the branch. After a few minutes, a new comment will appear in the ticket, which will report on the test results.



Also, at any time, you can manually initiate a rerun of tests by clicking the special button Run unit tests in the ticket menu. After a successful run, a new comment will appear in the ticket, similar to the previous one.


In fact, this button is one of the additional task statuses in the Jira workflow, translation into which triggers the triggering of the Groovy script for the ScriptRunner plugin. The script calls the external URL that initiates the test run, and if the URL responds successfully, the ticket returns to its previous status (in our case, Resolved ).

In Shot / In Shot - OK


The task is first tested in a devel environment. If everything is good, a shot is created (for example, by clicking on the Create shot link in the Commits field) - a directory on a dedicated server into which changes from the ticket copied with the current master are copied. The server works with production data: the bases and services are the same as those that serve real users. Thus, the tester can open a web site or connect to a shot using a mobile client and “isolate” check the feature in the production environment. “Isolated” means that no other code / functionality other than the new one from the branch and the current master is executed. Therefore, this stage of testing is perhaps the main one, since it allows the QA-engineer to most reliably find the problem directly in the test task.

Access to the resources of the shot is carried out by special URLs that are generated in the script for creating a shot and are placed in the ticket header using the Jira API. As a result, we see links to the site, admin panel, logs, and other tools that are executed in a shot environment:



Also at the moment of generating the shots, a script is launched that analyzes the contents of the modified files and creates requests for the translation of the found new lexemes. After the translation is completed, the value of the Lexems field changes to Done and the ticket can be added to the build.

If the test was successful, then the ticket is transferred to the status In Shot - OK.

In Build / In Build - OK


We post the code twice a day - in the morning and in the evening. To do this, create a special build-branch, which will eventually be merged with the master and laid out "into battle".

At the time of building a build branch, a special script receives a list of tickets in the status In Shot - OK using a JQL request and tries to lock them into the build branch when all of the following conditions are met:


It should be noted that automatic merging may not occur due to a merge conflict. In this case, the ticket is automatically transferred to the Reopen status and assigned to the developer, about which he immediately receives a notification in HipChat, and a corresponding message is added to the comment of the ticket. After resolving the conflict, the ticket is returned to the build.

If everything is fine and the ticket branch is frozen in the build, the ticket is automatically transferred to the In Build status, and the name of the build is written in the custom field of the Build_Name ticket.


Further, using this value, it is easy to get a list of tickets that have been posted with each build. For example, to look for someone to blame if something went wrong.

At the next stage, QA-engineers additionally check whether the task code works correctly in conjunction with other tasks in the build. If all is well, the ticket is manually set to In Build - OK.

On Production / On Production - OK / Closed


Next, the whole test suite (Unit, integration, Selenium-, etc.) is run through the build. If everything is good, the build is installed in the master, and the code is laid out on the production. The ticket is transferred to the On Production status .

Next, the developer (or customer) makes sure that the feature on the production works correctly, and sets the On Production - OK status to the ticket .

Two weeks later, tickets in the On Production - OK status are automatically transferred to the Closed status, if someone has not done it manually before.

Also worth mentioning are additional statuses in which the ticket can be located:


As a result, the simplified scheme of our workflow looks like this:


Ticket - task communication center


As a result of passing through the flow, its cap takes on the form:



What is more interesting here that we have customized for myself and what I have not yet mentioned?


We try to keep the discussion of controversial issues with the task designer in the comments of the ticket, and not to “smear” important clarifications by mail and instant messengers. If the discussion did take place “on the side,” it is highly desirable to copy what was agreed to in the ticket.

In addition to the "human" texts, as I mentioned above, in the commentary a lot of things are written automatically using the API:


Sometimes automatic comments can interfere, for example, with product managers. Therefore, we made a simple JS script that adds a button to the Jira interface and allows you to minimize all automatic comments, leaving only “human” ones. As a result, folded automatic comments look compact.



JS-code of the script that we have embedded in the ticket template
 window.addEventListener('load', () => {   const $ = window.jQuery;   const botsAttrMatch = [       'aida',       'itops.api'   ].map(bot => `[rel="${bot}"]`).join(',');   if (!$) {       return;   }   const AIDA_COLLAPSE_KEY = 'aida-collapsed';   const COMMENT_SELECTOR = '.issue-data-block.activity-comment.twixi-block';   const JiraImprovements = {       init() {           this.addButtons();           this.handleAidaCollapsing();           this.handleCommentExpansion();           // Handle toggle button and aida collapsing and put it on a loop           // to handle unexpected JIRA behaviour           const self = this;           setInterval(function () {               self.addButtons();               self.handleAidaCollapsing();           }, 2000);           addCss(`               #badoo-toggle-bots {                   background: #fff2c9;                   color: #594300;                   border-radius: 0 3px 0 0;                   margin-top: 3px;                   display: inline-block;               }           `);       },       addButtons() {           // Do we already have the button?           if ($('#badoo-toggle-bots').length > 0) {               return;           }           // const headerOps = $('ul#opsbar-opsbar-operations');           const jiraHeader = $('#issue-tabs');           // Only add it in ticket state           if (jiraHeader.length > 0) {               const li = $('<a id="badoo-toggle-bots" class="aui-button aui-button-primary aui-style" href="/">Collapse Bots</a>');               li.on('click', this.toggleAidaCollapsing.bind(this));               jiraHeader.append(li);           }       },       toggleAidaCollapsing(e) {           e.preventDefault();           const isCollapsed = localStorage.getItem(AIDA_COLLAPSE_KEY) === 'true';           localStorage.setItem(AIDA_COLLAPSE_KEY, !isCollapsed);           this.handleAidaCollapsing();       },       handleAidaCollapsing() {           const isCollapsed = localStorage.getItem(AIDA_COLLAPSE_KEY) === 'true';           const aidaComments = $(COMMENT_SELECTOR).has(botsAttrMatch).not('.manual-toggle');           if (isCollapsed) {               aidaComments.removeClass('expanded').addClass('collapsed');               $('#badoo-toggle-bots').text('Show Bots');           }           else {               aidaComments.removeClass('collapsed').addClass('expanded');               $('#badoo-toggle-bots').text('Collapse Bots');           }       },       handleCommentExpansion() {           $(document.body).delegate('a.collapsed-comments', 'click', function () {               const self = this; // eslint-disable-line no-invalid-this               let triesLeft = 100;               const interval = setInterval(() => {                   if (--triesLeft < 0 || self.offsetHeight === 0) {                       clearInterval(interval);                   }                   // Element has been removed from DOM. ie new jira comments have been added                   if (self.offsetHeight === 0) {                       JiraImprovements.handleAidaCollapsing();                   }               }, 100);           });           $(document.body).delegate(COMMENT_SELECTOR, 'click', function () {               $(this).addClass('manual-toggle');// eslint-disable-line no-invalid-this           });       }   };   JiraImprovements.init();   function addCss(cssText) {       const style = document.createElement('style');       style.type = 'text/css';       if (style.styleSheet) {           style.styleSheet.cssText = cssText;       }       else {           style.appendChild(document.createTextNode(cssText));       }       document.head.appendChild(style);   } }); 


What else?


API webhooks Jira :



Jira — , , . , , . Jira , .

— . Jira , Jira. , - .

Thanks for attention!

Source: https://habr.com/ru/post/424655/


All Articles