Разработка простого плагина для JIRA для работы с базой данных

    Плагин будет представлять собой вкладку в административной части проекта, через которую и будем осуществлять работу с базой данных.

    Плагин буду делать для джира 4.4.4. Для начала создадим пустой проект. Проект можно создать с помощью Atlassian SDK, а затем открыть в любимой IDE. В данном случае я буду работать с Netbeans. Файловая структура проекта будет выглядеть следующим образом:


    Создадим страничку, на которой будет находиться наш функционал. В джире есть несколько механизмов для создания страниц – report, webwork actions, servlet, для решения этой задачи выберем webwork actions. В момент знакомства с JIRA в этом механизме все было предельно просто, понятно и знакомо (по сути это тот же Apache Struts). Создадим новый класс MyAction.java, унаследованный от класса JiraWebActionSupport. И создадим velocity шаблон succeess.vm такого содержания:

    MyAction template
    

    Теперь осталось добавить следующий элемент в atlassian-plugin.xml для создания новой страницы:

    <webwork1 key="actions" name="MyActions">
     <actions>            
      <action name="com.edsd.jira.plugins.simpleplugin.action.MyAction" alias="action" roles-required="admin">
       <view name="success">/myaction/success.vm</view>
      </action>
     </actions>
    </webwork1>
    

    И при переходе по ссылке http://myserver/secure/action.jspa мы увидим созданную нами страницу:



    Теперь сделаем так, чтоб эта страничка стала такой же, как вкладка в админской части проекта.
    Для начала сделаем саму ссылку:



    Чтоб ссылка выглядела отдельной группой, добавим в atlassian-plugin.xml новую секцию (web-section). Затем в эту секцию уже добавим саму ссылку (web-item):

    <web-section key="my_section" name="MySection" location="atl.jira.proj.config" weight="50"/>
    <web-item key="my_item_link" name="MyTab" section="atl.jira.proj.config/my_section"  weight="10">
      <label key="MyLink" />
      <link linkId="my_item_link">/secure/action.jspa</link>
    </web-item>
    

    Но при клике по ссылке мы все равно будем получать страницу без оформления, чтобы это исправить внесем изменения в MyAction.java. На самом деле при переходе по ссылке http://myserver/secure/action.jspa вызывается метод execute() и возвращаемое им значение –это success поэтому-то мы именно так описали наш action в atlassian-plugin.xml. Так мы будем явно передавать, какой проект сейчас активен, и добавлять «шапку» проекта к вкладке.

    public class MyAction extends JiraWebActionSupport {
      private Project project;
      @Override
      public String execute() throws Exception {
        project = getSelectedProjectObject();
    //добавление шапки проекта к вкладке
        request.setAttribute("com.atlassian.jira.projectconfig.util.ServletRequestProjectConfigRequestCache:project", project);
        return super.execute();
      }
    }
    

    Но так передается только html код без css. Чтоб добавить css к странице допишем в atlassian-plugin.xml

    <web-resource key="my-resources">
        <dependency>com.atlassian.jira.jira-project-config-plugin:project-config-global</dependency>
        <context>my-resources</context>
      </web-resource>
    

    Здесь мы добавили зависимость dependency для самих ресурсов и объявили через context то, как мы можем получить доступ к этим ресурсам через менеджера.
    Тогда velocity шаблон приведем к такому виду:

    <!DOCTYPE HTML>
    <html>
        <head>
            <title>MyTestPage</title>
            <meta name="decorator" content="atl.admin"/>
            <meta name="projectKey" content="$project.getKey()"/>
            <meta name="projectId" content="$project.getId()"/>
            <meta name="admin.active.tab" content="my_item_link"/>
            <meta name="admin.active.section" content="atl.jira.proj.config"/>
            $webResourceManager.requireResourcesForContext("my-resources")
        </head> 
        <body>
            MyAction template
        </body>
    </html>
    

    В результате получим страницу:



    Добавим функционал работы с базой данных. Пользоваться будем Active Objects (дальше AO). Для начала создадим пакет logic. В нем опишем объект, с которым будем работать, пусть это будет элементарный Student:

    public interface Student {
        public void setName(String name);
        public String getName();
    }
    

    В пакете entity опишем наш интерфейс-сущность, который будем хранить в БД. Пусть для примера в базе еще будем хранить время создания этой записи:

    import java.util.Date;
    import net.java.ao.Entity;
    public interface StudentEntity extends Entity, Student {
        public Date getCreated();
        public void setCreated(Date created);
    }
    

    Добавим теперь наш AO в atlassian-plugin.xml:

    <component-import key="ao" name="Active Objects service" interface="com.atlassian.activeobjects.external.ActiveObjects">
      <description>Component to access Active Objects functionality from the plugin</description>
    </component-import>
        
    <ao key="ao-module">
      <entity>com.edsd.jira.plugins.simpleplugin.entity.StudentEntity</entity>
    </ao>
    

    Подробнее про подключение AO к плагину можно почитать тут.

    Теперь разберемся с взаимодействием нашего приложения с базой данных. Определим интерфейс StudentDAO из пакета DAO, содержащий набор необходимых методов:

    public interface StudentDAO {
        public StudentEntity addStudent(Student student) throws Exception; 
        public StudentEntity[] getStudents() throws Exception; 
    }
    Реализацию интерфейса сделаем в пакете DAO.Impl в классе StudentDAOImpl. 
    public class StudentDAOImpl implements StudentDAO {
        private final ActiveObjects ao; 
        public StudentDAOImpl(ActiveObjects ao) {
            this.ao = ao;
        }
        @Override
        public StudentEntity addStudent(final Student student) throws Exception {
            return ao.executeInTransaction(new TransactionCallback<StudentEntity>() {
                @Override
                public StudentEntity doInTransaction() {
                    StudentEntity entity = ao.create(StudentEntity.class); 
                    entity.setName(student.getName());
                    entity.setCreated(new Date(System.currentTimeMillis()));
                    entity.save();
                    return entity;
                }
            });
        }
        @Override
        public StudentEntity[] getStudents() throws Exception {
            return ao.executeInTransaction(new TransactionCallback<StudentEntity[]>() {
                @Override
                public StudentEntity[] doInTransaction() {
                    return ao.find(StudentEntity.class);
                }
            });
        }
    }
    

    Теперь создадим класс DAOFactory в пакете DAO, к которому будем обращаться за нашими реализациями DAO, от которых и будем вызывать необходимые нам методы:

    public class DAOFactory {
        private static StudentDAO studentDAO = null;
        private static DAOFactory instance = null;
        private static ActiveObjects ao;
        public DAOFactory(ActiveObjects ao) {
            DAOFactory.ao = ao;
        }
        public static synchronized DAOFactory getInstance() {
            if (instance == null) {
                instance = new DAOFactory(ao);
            }
            return instance;
        }
        public StudentDAO getStudentDAO() {
            if (studentDAO == null) {
                studentDAO = new StudentDAOImpl(ao);
            }
            return studentDAO;
        }
    }
    

    Добавим в atlassian-plugin.xml нашу DAOFactory. Это необходимо чтобы избежать проблем с обращением к AO. Кому интересно: рассуждение на тему Active Objects injection.

       <component key="dao-factory" class="com.edsd.jira.plugins.simpleplugin.DAO.DAOFactory">
        </component>
    

    Собственно, все необходимое для работы с базой данных мы описали и реализовали, осталось теперь добавить функционал в MyAction:

    public class MyAction extends JiraWebActionSupport {
        private Project project;
        private StudentEntity[] students;
        @Override
        public String execute() throws Exception {
            project = getSelectedProjectObject();
    request.setAttribute("com.atlassian.jira.projectconfig.util.ServletRequestProjectConfigRequestCache:project", project);
            students = DAOFactory.getInstance().getStudentDAO().getStudents();
            return super.execute();
        }
        public String doAdd() throws Exception {
            String name = request.getParameterValues("name")[0]; 
            Student student = new StudentImpl(name); 
            DAOFactory.getInstance().getStudentDAO().addStudent(student); 
            ServletActionContext.getResponse().sendRedirect("/secure/action.jspa");
            return NONE;
        }
    }
    

    И в шаблоне velocity привести тег body к такому виду:

    <body>
            <form action="/secure/action!add.jspa">
                <input type="text" name="name"/>
                <input type="submit" value="OK"/>
            </form>
            <table>
                <thead>
                    <th>id</th>
                    <th>name</th>
                    <th>created</th>
                </thead>
                <tbody>
                #foreach($student in $students)
                    <tr>
                        <td>$student.getID()</td>
                        <td>$student.getName()</td>
                        <td>$student.getCreated()</td>
                    </tr>
                #end
                </tbody>
            </table>
        </body>
    

    В общем, запускаем, тестируем, получаем:



    На этом на первый раз все, в следующей статье расскажу:

    • как создать таблицы, аналогичные таблицам во вкладках Компоненты и Версии,
    • как создать выпадающий список, с таким же оформлением как стандартный в JIRA,
    • что такое JIRA.Restfultable
    • и как пользоваться JIRA REST API.

    Весь код плагина на GitHub

    Продолжение: придаём нашему плагину нормальный внешний вид.



    Больше проектов:
    Как за 5233 человеко-часа создать софт для микротомографа
    SDK для внедрения поддержки электронных книг в формате FB2
    Управление доступом к электронным документам. От DefView до Vivaldi
    Интегрируем две системы видеонаблюдения: Axxon Next и SureView
    Подробнее о разработке софта рентгеновского томографа
    «Сфера»: как мониторить миллиарды киловатт-часов
    Разработка простого плагина для JIRA для работы с базой данных
    В помощь DevOps: сборщик прошивок для сетевых устройств на Debian за 1008 часов
    Автообновление службы Windows через AWS для бедных
    • +9
    • 13,4k
    • 5
    Edison 83,13
    Изобретаем успех: софт и стартапы
    Поделиться публикацией
    Комментарии 5
    • +1
      Раньше занимался плагинами для Джиры, и тема для меня интересная в том плане, насколько процесс разработки изменился в лучшую сторону.
      У них уже есть hot reload? Раньше надо было переустанавливать плагин через админку (а еще раньше — перезапускать весь инстанс).
      Как решаете вопрос с использованием общего кода? Например, часто возникает необходимость в общих для нескольких плагинов утилитах, или подключении какой-нибудь библиотеки (например Joda-time), которую хотелось бы использовать в нескольких разных плагинах и при этом не включать ее в каждый из них.
      И спасибо за статью.
      • +2
        1) При запуске atlas-run из директории с плагином в JIRA появляются dev-тулы, в том числе hot-reload. Минус этого — нет тестовых данных. Поэтому я верен переустановке через админку.
        2) Вроде был способ подключать OSGI и non-OSGI компоненты, сейчас не вспомню
        3) Насчет библиотек — так все же за Вас делает maven

      • +2
        Спасибо за статью, буду очень рад подробному примеру с RESTfull table и выпадающими списками. Сам раскопал это наполовину, но возникли некоторые затруднения.
        • 0
          Может кому нужно — пример плагина для Bamboo.
          • 0
            Для JIRA 4.4.4? А это будет работать на 6.3 или 6.4? И почему для такой не свежей версии? Просто свежие инсталляции (меньше пары лет) с 6ой ветки идут…

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое