Customizing Jira to 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 Jira is the most widely used today.

    My name is Dmitry Semenikhin, I am a team leader at Badoo. In a short series of articles, I’ll tell you exactly how we use Jira, how we customized it for our processes, what good things were “screwed” on top, and how we turned the issue tracker into a single communication center for the task and simplified our life. In this article, you will see our flow inside, learn how you can “twist” your Jira, and read about additional features of the tool that you might not know about.

    The article is primarily aimed at those who already use Jira, but may have difficulty integrating its standard features into existing processes in the company. Also, the article may be useful to companies that use other task trackers, but have encountered some restrictions and are considering a change of decision. The article is not built on the principle of “problem - solution”, in it I describe the existing tools and features that we built around Jira, as well as the technologies that we used to implement them.

    Additional features of Jira


    To make the following text more understandable, let's figure out what tools Jira provides us with for the implementation of non-standard Wishlist - those that go beyond the standard Jira functionality.

    REST API


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

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

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

    • create tickets;
    • modify any properties of tickets (built-in and custom);
    • write comments;
    • using JQL (built-in query language) to receive any ticket lists;
    • and much more.

    Detailed API documentation is available here .

    We wrote our own high-level Jira API client in PHP, which 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 webhook, you can configure the call of an external callback function on your host to various events in Jira. At the same time, you can configure as many rules as you like so that different URLs will “twitch” for different events and for tickets that match 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:

    the Name : "the SRV - the Feature Created the New / updated"
    the URL : www.myremoteapp.com/webhookreceiver
    the Scope : by Project = the AND of the type in the SRV ( 'the New the Feature')
    Events Read : Updated Issue, Issue Created

    In In this example, the specified URL will be triggered for ticket creation and change events corresponding to the filter.Scope . At the same time, 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 respond or answered with an error, this will not be visible anywhere (except for the logs, perhaps). Therefore, the webhook event handler should be as reliable as possible. For example, you can queue events and try to process them until it succeeds. This will help solve problems with temporarily unavailable services, for example, any external database necessary for the correct processing of the event.

    Detailed documentation on webhooks is available here .

    Scriptrunner


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

    Those interested can read the documentation .

    Flow: what is hidden under the hood


    And now about how we apply additional features of Jira in our projects. Consider this in the context of going through our typical flow ticket from creation to closing. At the same time I’ll tell you about the flow itself.

    Open / backlog


    So, first the ticket gets into the backlog of new tickets with the Open status . Further, the component leader, upon seeing a new ticket on his dashboard, makes a decision: assign a ticket right now to the developer or send it to the backlog of known tickets ( Backlog status ) so that you can assign it later when a free developer appears and higher priority tickets will be closed. This may seem strange, since it seems logical to do the opposite: create tickets in Backlog status , and then translate them into Open status. But we have taken root precisely this scheme. It allows you to easily configure filters to reduce the decision time for 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 we work on each task in a separate Git branch. As for this, we have an agreement that the branch name at the beginning should contain the ticket number. For example, SRV-123_new_super_feature . Also, comments for each commit in the branch should contain the ticket number in the format [SRV-123]: {comment}. We need such a format, for example, for the correct removal of 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, 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 you try to push a commit with an “incorrect” comment, such a push will be rejected. An attempt to start a branch without a ticket number at the beginning will also be rejected.

    When a ticket hits the developer, the first thing he decomposes. 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 begins to write code.

    It is customary for us to set the due date to the 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 HipChat corporate messenger. A special script every two hours:

    • using the Jira REST API selects tickets in in progress status with an empty due date field ( project = SRV AND status = 'In Progress' AND duedate is EMPTY );
    • selects incomplete tickets with due date older than the current date ( project = SRV AND status = 'In Progress'  AND duedate is not EMPTY AND duedate <now () );
    • recognizes the developer for each ticket by reading the corresponding field in the ticket, as well as the developer’s lead;
    • groups tickets by developers and leads and sends reminders to HipChat using its API.

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

    • The name of the Git branch, as well as comments on commits, are checked for compliance with our rules;
    • it is checked that the ticket with which the branch is associated is not closed (you cannot push new code into closed tickets);
    • The syntax of the modified PHP files is checked (PHP -l file_name.php );
    • formatting is checked;
    • if the ticket into which the branch is pushed is in the Open status , then it will automatically be transferred to the In Progress status ;
    • the ticket is attached to the branch, the corresponding entry is made in the custom field of the Commits ticket using the Jira API. It looks like this:


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

    • a comment is created in the ticket with all the commits in this push.

      (Aida is the conditional name of our automation complex for working with Jira, Git and not only. It is from this name that automatic comments appear in the ticket. We wrote more about Aida in the article ).
      A click on the hash of the commit opens diff with the previous revision of the branch (I’ll show below how it looks like);
    • it checks if there are files in the branch that may require translation into supported languages ​​(for example, web page templates), and if there are any, then the new \ Changed value is set to the custom field of the Lexems ticket. This ensures that the ticket will not go to production without a completed translation;
    • the name of the employee who pushes the branch is added to the list of developers (a custom field of the Developers ticket )

    On review


    After writing the code and 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 ). Typically, the developer decides who will review his ticket. Most likely, it will be another developer who is well versed in the right part of the code. The review takes place using the Codeisok tool , which opens immediately with the desired diff by clicking on the branchdiff link in the Commits ticket field or in the comment as a commit hash in the comments.

    The reviewer sees something like this:


    Having finished the review, the reviewer presses the Finish button , and, among other things, at this moment the following happens:

    • using the JIra API, a comment is created in the ticket with comments from the reviewer in the context of the code. It looks something like this:


    • if there were any comments on the code and the reviewer decided to reopen the ticket, the developer will receive a notification about this in HipChat (this is done using the webhook rule, which works on reopening);
    • The Reviewers ticket field is populated .

    Resolved


    Further, if the review was successful, the ticket is sent to the backlog of QA engineers in the Resolved status . But at the same time, using webhook for the resolved event, automatic tests on the branch code are launched in the background. After a few minutes, a new comment will appear in the ticket, which will inform you of the test results.



    Also, at any time, you can manually initiate a repeated test run by clicking on the special Run unit tests button 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, a translation into which triggers a Groovy script for the ScriptRunner plugin. The script calls an external URL, which initiates the test run, and if the URL responded 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 all is well, a shot is created (for example, by clicking on the Create shot link in the Commits field ) - the directory on the dedicated server to which changes from the ticket are copied that are adjacent to the current master. The server works with production data: the databases and services are the same that serve real users. Thus, the tester can open a web site or connect to the shot using a mobile client and "isolate" the feature in the production environment. "Isolated" means that no other code / functionality, except for 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 problem.

    Shot resources are accessed using special URLs that are generated in the shot creation script 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 shot generation, a script is launched that analyzes the contents of the modified files and creates requests for translation of the found new tokens. After the translation is completed, the value of the Lexems field is changed to Done and the ticket can be added to the build.

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

    In Build / In Build - OK


    We upload the code twice a day - in the morning and in the evening. To do this, a special build-branch is created, which will eventually be merged with master and laid out “in battle”.

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

    • the translation for the ticket is completed or nothing needs to be translated ( Lexems in ('No', 'Done') );
    • the developer is present at the workplace (the automatic merger system checks on the internal base whether the developer is on vacation or on sick leave, and if so, the ticket can only be frozen manually by release engineers or another responsible developer, which is indicated in the special field Vice Developer ; the leader of the absent developer in this case receives a notification that the ticket cannot be automatically added to the build);
    • the ticket does not have the Up in Build flag set to by Developer (this is a special custom field of the ticket that allows the developer to determine when the ticket will go to the build);
    • the ticket branch does not depend on another branch that has not yet reached the master or the current build. We do our best to avoid such a situation, but sometimes this happens when the developer creates his own branch not from master, but from a branch of another ticket, or when he freezes another branch to himself. This can also be done by chance, so we decided that additional protection would not hurt.

    It is worth noting 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 ticket comment. After resolving the conflict, the ticket returns 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 were posted with each build. For example, to search 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


    Further, on the build, our entire set of tests is run (Unit, integration, Selenium-, etc.). If everything is fine, the build is frozen in master, and the code is laid out for production. The ticket is transferred to the On Production status .

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

    After two weeks, tickets in the On Production - OK status are automatically transferred to the Closed status , if someone has not done this manually before.

    It is also worth mentioning additional statuses in which the ticket may be located:

    • Requirements - when it is not possible to quickly obtain from the customer the necessary clarifications on the task, and without them further work on the ticket is impossible, the ticket is transferred to this status and assigned to the one who needs to give explanations;
    • Suspended - if the ticket work is suspended, for example, if the developer is blocked by tasks of an adjacent team or was forced to switch to a more urgent task;
    • Reopened - a task can be rediscovered to the developer after a review, after testing, after an unsuccessful attempt to merge a branch with master.

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


    Ticket - communication center for the task


    As a result of passing the ticket through the flow, its header takes on something like this:



    What else is interesting here that we customized for ourselves and which I have not mentioned yet?

    • Component - used to cluster a ticket within a large department. Different subgroups are responsible for different components and, accordingly, on their dashboards they see only tasks for their components. For example, I can list all open bugs for the components of my team with this query:

      Project = SRV AND type = Bug AND status = Open AND component in componentsLeadByUser(d.semenihin)

    • Review - whether code review is needed. Default is needed. If the field value is set to No, the ticket will immediately get the Resolved
      status .
      QA - Does the tester need verification. Default is needed. If the field value is set to No, the ticket will immediately go to the In Shot - OK
      status .
      Sprint - in our case, it is relevant only for tasks with the New Feature type, a plan for which we draw up in advance for a week.
    • Due date - the developer determines the date when the ticket will be on production. Exhibited before starting work on the task.
    • Situation - in fact, a short log with a brief description of the current status of the task. For example, “20/08 I am waiting for translations” , “21/08 Clarification is required from the customer on problem X” . This helps to see a brief summary of the task in the list of other tasks.
    • Msg4QA - information for QA engineers, which the developer shares to simplify the testing process

    We try to conduct a discussion of controversial issues with the task director in the comments of the ticket, and not "smear" important clarifications by mail and instant messengers. If the discussion nevertheless took place “on the side”, it is highly desirable to copy what you have agreed on into the ticket.

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

    • commits
    • review results;
    • test run results.

    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, minimized automatic comments look compact.



    JS code of the script that we 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 = $('Collapse Bots');
                    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. i.e. 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?


    Using the Jira API and webhooks we do the following things:

    • send a notification to HipChat if any of the employees was mentioned in a comment (it helps to quickly resolve issues);
    • send notifications to HipChat when a ticket is assigned for review and when the ticket gets to production (how exactly we implemented this will be described in the next article);
    • системные архитекторы с помощью специального интерфейса в пару кликов создают тикеты различным командам (клиентским и серверным) для реализации проекта (при этом тикеты корректно заполняются нужными полями и линкуются между собой; это помогает нам эффективно организовать синхронизацию работы команд);
    • мы автоматически отслеживаем появление новых версий клиентов; после этого специальный скрипт создаёт тикет серверной команде, чтобы мы внесли изменения в некоторые конфиги;
    • скрипт периодически снимает срезы по задачам в статусе In progress для статистики;
    • скрипт определяет задачи, которые надолго «зависают» в определённых статусах (например, On Review), и отправляет соответствующие уведомления ответственным сотрудникам;
    • если сотрудник в этот день отсутствует в офисе и об этом есть соответствующая запись во внутренней базе, то к его имени в Jira добавляется информация об этом (например, «d.semenihin (Day off)»). Очень полезная фича.

    Итоги


    Jira is an excellent tool that, as standard, allows you to solve most of the problems associated with organizing project management. But, as you know, every business has its own nuances. And to adapt Jira to the peculiarities of your processes, this product has additional features that, in the right hands, will allow you to solve almost any problem.

    In the next article, I plan to share our experience in setting up dashboards - Lida and development. I will also talk about setting up notifications in Jira and share secrets about how we organize the synchronization of the work of different teams based on Jira. I hope that something from our experience will be useful to you.

    Thanks for attention!

    Also popular now: