Atlassian Jira Software функциональность в Jira плагине

jq8bvotywz5icixwn6cg0ltebj4.jpeg Часто вижу вопросы о том, как сделать плагин с использованием функциональности из Jira Software. В интернете найти информацию сложно, поэтому я решил сделать статью, в которой расскажу, как подключить Jira Software функциональность к плагину для Jira.
Часто возникает необходимость получить все задачи, которые связаны с эпиком, или добавить задачу к эпику. Это обычно делают через связь типа «Epic Link», но давайте попробуем это сделать «правильно» через сервисы, которые предоставляет Jira Software.

Итак, в этой статье разработаем плагин, который выводит информацию обо всех задачах в эпике и добавляет задачи в эпик через созданный нами REST вызов.

Создаем плагин


Открываем терминал и выполняем команду:

atlas-create-jira-plugin


Терминал будет задавать вопросы. Ниже привожу вопросы и ответы на них:

Define value for groupId: : ru.matveev.alexey.sw.tutorial
Define value for artifactId: : sw-tutorial
Define value for version:  1.0.0-SNAPSHOT: :
Define value for package:  ru.matveev.alexey.sw.tutorial: :
Confirm properties configuration:
groupId: ru.matveev.alexey.sw.tutorial
artifactId: sw-tutorial
version: 1.0.0-SNAPSHOT
package: ru.matveev.alexey.sw.tutorial
Y: : Y


Вносим изменения в pom.xml


Меняем свойство jira.version на 7.9.0:

7.9.0


Добавляем в maven-jira-plugin в тэг configuration:


    
        jira-software
        ${jira.software.application.version}
    


Добавляем свойство jira.software.application.version:

7.9.0


Строки выше позволят нам запустить Jira Software с помощью команды atlas-run версии 7.9.0.

Так же необходимо увеличить размер JVM, так как в моем окружении Jira Software не запустилась с ошибкой о нехватке памяти:

-Xms512M -Xmx1g 


Добавляем REST модуль


В терминале запускаем следующую команду:

atlas-create-jira-plugin-module


Ниже привожу вопросы и ответы на них:

Enter New Classname MyRestResource: :
Choose a number (1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34): 14
Enter Package Name ru.matveev.alexey.sw.tutorial.rest: :
Enter REST Path /myrestresource: :
Enter Version 1.0: :
Show Advanced Setup? (Y/y/N/n) N: : N
Add Another Plugin Module? (Y/y/N/n) N: : N


Добавляем функциональность Jira Software


Запускаем в терминале:

atlas-run


После того, как Jira запустится, в папке плагина появится диреректория target. Нужно перейти в директорию target/jira/home/plugins/installed-plugins и поискать jar файл, который начинается с jira-greenhopper-plugin. В моем случае файл выглядит вот так:

jira-greenhopper-plugin-7.9.0-DAILY20180326142825.jar


Такое название файла означает, что версия jira-greenhopper-plugin плагина 7.9.0-DAILY20180326142825. Поэтому добавляем зависимость от плагина jira-greenhopper-plugin в файл pom.xml следующим образом:


    com.atlassian.jira.plugins
    jira-greenhopper-plugin
    7.9.0-DAILY20180326142825
    provided


После того, как мы добавили плагин в зависимости, мы можем использовать функциональность Jira Software. Но теперь нам нужно понять, какие сервисы Jira Software мы можем использовать.
Jira использует OSGI, что означает, что плагин должен экспортировать сервисы, которые могут быть использованы другими плагинами. Давайте попробуем найти сервисы, которые экспортирует Jira Software.

В браузере переходим по ссылке:

http://localhost:2990/jira/plugins/servlet/upm#osgi 


Мой экран выглядит вот так:

cax-gnkkrtutvj_hkczx1q0zv20.png

Найдем Atlassian Greenhopper плагин и откроем Registered Services меню:

6fiyu7iagarctenexpg6kdw7z-c.png

Среди всех сервисов будут и необходимые нам сервисы:

Service 1477 com.atlassian.greenhopper.service.issue.RapidViewIssueService
Service 1476 com.atlassian.greenhopper.service.issuelink.EpicService


Напишем просмотр задач в эпике и добавление задачи в эпик


Удалим файл sw-tutorial/src/test/java/ut/ru/matveev/alexey/sw/tutorial/rest/MyRestResourceTest.java.

Изменим содержимое файла sw-tutorial/src/main/java/ru/matveev/alexey/sw/tutorial/rest/MyRestResource.java на:

package ru.matveev.alexey.sw.tutorial.rest;

import com.atlassian.greenhopper.model.Epic;
import com.atlassian.greenhopper.service.Page;
import com.atlassian.greenhopper.service.PageRequests;
import com.atlassian.greenhopper.service.ServiceOutcome;
import com.atlassian.greenhopper.service.issue.RapidViewIssue;
import com.atlassian.greenhopper.service.issue.RapidViewIssueService;
import com.atlassian.greenhopper.service.issuelink.EpicService;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.jql.builder.JqlQueryBuilder;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.user.ApplicationUser;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
import com.atlassian.query.Query;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Path("/message")
public class MyRestResource {

    private final EpicService epicService;
    private final IssueManager issueManager;
    private final JiraAuthenticationContext jiraAuthenticationContext;
    private final RapidViewIssueService rapidViewIssueService;

    @Inject
    public MyRestResource(@ComponentImport EpicService epicService,
                          @ComponentImport IssueManager issueManager,
                          @ComponentImport JiraAuthenticationContext jiraAuthenticationContext,
                          @ComponentImport RapidViewIssueService rapidViewIssueService) {
        this.epicService = epicService;
        this.issueManager = issueManager;
        this.jiraAuthenticationContext = jiraAuthenticationContext;
        this.rapidViewIssueService = rapidViewIssueService;
    }

    @Path("/hello")
    @GET
    @AnonymousAllowed
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public Response getMessage()
    {
       return Response.ok(new MyRestResourceModel("Hello World")).build();
    }

    @Path("/epictasks")
    @GET
    @AnonymousAllowed
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public Response getEpicTasks(@QueryParam("epic key") String epicKey)
    {
        ApplicationUser user = jiraAuthenticationContext.getLoggedInUser();
        ServiceOutcome epic = epicService.getEpic(user, epicKey);
        Query query = JqlQueryBuilder.newBuilder().where().buildQuery();
        ServiceOutcome> issues = rapidViewIssueService.getIssuesForEpic(user, epic.getValue(), PageRequests.request(0L, 100), query);
        List issueList = issues.getValue().getValues().stream().map(el -> el.getIssue().getSummary()).collect(Collectors.toList());
        return Response.ok(new MyRestResourceModel(issueList.toString())).build();
    }

    @Path("/epictasks")
    @POST
    @AnonymousAllowed
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public Response addTaskToEpic(@QueryParam("epic key") String epicKey,
                                  @QueryParam("issue key") String issueKey)
    {
        ApplicationUser user = jiraAuthenticationContext.getLoggedInUser();
        ServiceOutcome epic = epicService.getEpic(user, epicKey);
        Set issueSet = new HashSet<>();
        issueSet.add(issueManager.getIssueByCurrentKey(issueKey));
        epicService.addIssuesToEpic(user, epic.getValue(), issueSet);
        return Response.ok(new MyRestResourceModel(issueKey + " added to " + epicKey)).build();
    }
}


Код выше сырой и был создан лишь для того, чтобы показать, как можно работать с Jira Software. Специально для упрощения кода были опущены все необходимые проверки, поэтому код может вызывать NullPointerException при передачи неверных параметров.

В коде создается метод epictasks. Если к epictasks обратиться через метод GET и передать ключ эпика, то будут выведены все задачи в переданном эпике. Если к epictasks обратиться через метод POST и передать ключ эпика и ключ задачи, то задача будет добавлена в эпик.

Допустим у нас есть эпик с ключом SW-1 и задача с ключом SW-5. Сначала давайте обратимся в epictasks через GET:

vyiqixpk6vfizex5ujznayxaa6i.png

Из скриншота выше мы видим, что у эпика SW-1 не задач.

Теперь обратимся к epictasks через метод POST и передадим в него епик SW-1 и задачу SW-5:

tws-pfmpai73t5ohwuwkvtnudlm.png

Мы видим, что задача SW-5 была добавлена в эпик SW-1.

Давайте еще раз попробуем вызвать epictasks через метод GET:

uzwv5ugxh529csiwmcp47rlir34.png

Мы видим, что у эпика SW-1 появилась задача SW-5.

Таким образом мы выполнили цель данной статьи: мы добавили задачу в эпик и получили информцию о задачах в эпике с помощью функциональности Jira Software.

Исходный код плагина можно взять здесь.

© Habrahabr.ru