Применимость |
Это руководство относится к JIRA 5.0. |
Уровень опыта |
Это промежуточный учебник. Вы должны были пройти хотя бы один учебник для начинающих, прежде чем работать с этим учебником. См. Список обучающих программ для разработчиков. |
Временная оценка |
Для завершения этого урока вам потребуется около 1 часа. |
Обзор учебника
В этом учебном пособии показано, как создать плагин JIRA, который связывается с экземпляром Confluence через ссылку приложения. Плагин добавит панель на страницу просмотра задач в JIRA. Когда пользователь открывает задачу в JIRA, плагин просматривает пространство Confluence, связанное с проектом JIRA, и перечисляет страницы, в которых упоминается задача. Другими словами, панель отображает любую страницу в пространстве Confluence, которая ссылается на текущую просмотренную задачу по ключу задачи.
Вот как это будет выглядеть:
РИСУНОК
Для этого потребуется небольшая административная работа вместе с вашей разработкой. Во-первых, вам нужно настроить связь приложения между экземпляром JIRA и установкой Confluence. Затем вам необходимо настроить связь проекта между проектом JIRA и пространством Confluence.
Плагин JIRA будет состоять из следующих компонентов:
- Java-классы, инкапсулирующие логику плагина.
- Ресурсы для отображения пользовательского интерфейса плагина (UI).
- Дескриптор плагина (файл XML), чтобы включить модуль плагина в приложение Atlassian.
Когда вы закончите, все эти компоненты будут упакованы в один JAR-файл.
Об этих инструкциях
Вы можете использовать любую поддерживаемую комбинацию ОС и IDE для создания этого плагина. Эти инструкции были написаны с использованием Ubuntu Linux. Если вы используете другую комбинацию ОС или IDE, вы должны использовать эквивалентные операции для своей конкретной среды.
Этот учебник был последний раз проверен с JIRA 6.0.
Предварительное знание
Чтобы завершить этот учебник, вам необходимо знать следующее:
- Основы разработки Java: классы, интерфейсы, методы, использование компилятора и т. д.
- Как создать проект плагина Atlassian с помощью Atlassian Plugin SDK.
- Как открыть проект плагина в вашей среде IDE, например Eclipse или IDEA.
Источник плагина
Мы рекомендуем вам проработать этот учебник. Если вы хотите пропустить или проверить свою работу, когда закончите, вы можете найти исходный код плагина на Atlassian Bitbucket. Bitbucket служит публичному репозиторию Git, содержащему код учебника. Чтобы клонировать репозиторий, выполните следующую команду:
git clone https://bitbucket.org/atlassian_tutorial/jira-applinks
Кроме того, вы можете скачать исходный код в виде ZIP-архива, выбрав здесь: https: //bitbucket.org/atlassian_tutorial/jira-applinks
Шаг 1. Создайте проект плагина
На этом этапе вы будете использовать две команды atlas- для создания кода-заглушки для вашего плагина. atlas-команды являются частью Atlasian Plugin SDK и автоматизируют большую часть разработки плагина для вас.
- Если вы еще не настроили SDK Atlassian Plugin, сделайте это сейчас: настройте SDK Atlassian Plugin и создайте проект.
- Откройте терминал и перейдите в каталог, в котором вы хотите создать проект плагина.
- Введите следующую команду для создания скелета плагина:
atlas-create-jira-plugin
- Выберите вариант создания плагина для JIRA 5.0.
- В ответ на запрос введите следующую информацию для своего плагина:
group-id |
com.example.plugins.tutorial |
artifact-id |
tutorial-jira-ual |
version |
1.0-SNAPSHOT |
package |
com.example.plugins.tutorial |
- Подтвердите свои записи при появлении запроса.
Шаг 2. Просмотрите и настройте сгенерированный код заглушки
Это хорошая идея, чтобы ознакомиться с файлом конфигурации проекта, известным как POM (файл определения объектной модели проекта). В этом разделе вы просмотрите и настроите файл POM.
Добавить метаданные плагина к POM
POM находится в корне вашего проекта и объявляет зависимости проекта и другую информацию.
Добавьте некоторые метаданные о своем плагине и вашей компании или организации. Вам также нужно добавить зависимость от ссылок на приложения здесь.
- В окне команд перейдите в новый каталог, созданный для вашего проекта, tutorial-jira-ual.
- Откройте файл pom.xml для редактирования и добавьте название вашей компании или организации и ваш сайт в элемент <organization>:
<organization>
<name>Example Company</name>
<url>http://www.example.com/</url>
</organization>
- Обновите элемент <description>:
<description>This is the UAL plugin tutorial for Atlassian JIRA.</description>
<dependency>
<groupId>com.atlassian.applinks</groupId>
<artifactId>applinks-api</artifactId>
<version>3.2</version>
<scope>provided</scope>
</dependency>
- Добавьте зависимость на ссылки приложения
- Сохраните файл.
Шаг 3. Добавьте ваш модуль плагина в дескриптор плагина
Теперь используйте генератор модуля плагинов (еще один atlas-команду), чтобы создать код-заглушку для модулей, необходимых плагину. Тип модуля плагина является модулем плагина панели вкладок задач.
Добавьте его следующим образом:
- В командном окне перейдите в корневую папку вашего проекта плагина (где находится pom.xml).
- Запустите модуль atlas-create-jira-plugin.
- Чтобы добавить тип модуля, выберите «Панель вкладок».
- При появлении запроса введите следующую информацию, чтобы описать свой модуль плагина:
- Введите новое имя класса: ConfluenceSpaceTabPanel
- Введите имя пакета: com.example.plugins.tutorial.jira.tabpanels
- При появлении запроса с «Показать расширенную настройку» выберите «N» (для «Нет»).
- Когда будет предложено добавить дополнительный модуль плагина, выберите «N».
На данный момент ваш плагин содержит следующие файлы:
Файл |
Описание |
LICENSE |
Лицензия для этого плагина |
pom.xml |
Файл модели объекта проекта Maven. |
README |
Файл 'readme' для этого проекта. |
src/main/java/com/example/plugins/
tutorial/jira/tabpanels/ConfluenceSpaceTabPanel.java |
Создан класс панели вкладки «Задача». |
src/main/java/com/example/plugins/
tutorial/MyPlugin.java |
Пустой файл Java-класса, созданный плагином atlas-create-jira-plugin. Игнорировать или удалить это. |
src/main/resources/atlassian-plugin.properties |
Файл, содержащий пары ключа / значения i18n. |
src/main/resources/atlassian-plugin.xml |
Дескриптор плагина Atlassian. |
src/main/resources/templates/
tabpanels/confluence-space-tab-panel.vm |
Макрос Velocity для рендеринга содержимого на панели вкладки «Задача». |
src/test/java/com/example/plugins/tutorial/
jira/tabpanels/ConfluenceSpaceTabPanelTest.java |
Тестовый файл для класса панели вкладки «Задача». |
src/test/java/com/example/plugins/tutorial/
MyPluginTest.java |
Пустой файл Java, созданный плагином atlas-create-jira-plugin. Игнорировать или удалить это. |
src/test/java/it/MyPluginTest.java |
Пустой файл Java, созданный плагином atlas-create-jira-plugin. Игнорировать или удалить это. |
src/test/resources/TEST_RESOURCES_README |
Файл readme для тестовых ресурсов. |
src/test/xml/TEST_XML_RESOURCES_README |
Файл readme для тестовых ресурсов. |
velocity.log |
Выходной файл журнала, созданный модулем atlas-create-jira-plugin-module. Игнорировать или удалить это. |
Шаг 4. Измените дескриптор плагина
Теперь отредактируйте файл дескриптора плагина atlassian-plugin.xml, чтобы импортировать компонент ссылок приложения (EntityLinkService) следующим образом:
- В окне команд перейдите в каталог src / main / resources.
- Добавьте элемент component-import ниже в файл atlassian-plugin.xml:
<atlassian-plugin ...>
...
<issue-tabpanel key="confluence-space-tab-panel" ...>
...
</issue-tabpanel>
<component-import key="entityLinkService">
<interface>com.atlassian.applinks.api.EntityLinkService</interface>
</component-import>
...
</atlassian-plugin>
Обратите внимание на элемент issue-tabpanel. Мы добавили это, когда запустили команду atlas-create-jira-plugin-module.
Шаг 5. Разработайте свой код плагина.
Вы уже создали заглушки для своих модулей плагина. Теперь вы напишете код, который заставит ваш плагин что-то делать.
Напоминаем, что этот плагин будет связываться с сайтом Confluence через ссылки приложения. Он ищет поиск в Confluence для упоминания текущей просмотренной задачи и отображает результаты на странице просмотра задачи.
Для этого в панели вкладки «Задача» необходимо использовать EntityLinkService для извлечения и использования сылки приложения к Confluence.
Генератор кода SDK дал нам несколько необходимых нам методов, таких как getActions () и showPanel (). Мы расширим код следующим образом:
- Откройте файл ConfluenceSpaceTabPanel.java для редактирования. Вы можете найти файл в каталоге src / main / java / com / example / plugins / tutorial / jira / tabpanels в вашем доме проекта.
- Во-первых, добавьте некоторые статусы импорта в те, которые нам дал SDK:
import com.atlassian.applinks.api.ApplicationLinkRequest;
import com.atlassian.applinks.api.ApplicationLinkRequestFactory;
import com.atlassian.applinks.api.ApplicationLinkResponseHandler;
import com.atlassian.applinks.api.CredentialsRequiredException;
import com.atlassian.applinks.api.EntityLink;
import com.atlassian.applinks.api.EntityLinkService;
import com.atlassian.applinks.api.application.confluence.ConfluenceSpaceEntityType;
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.plugin.issuetabpanel.AbstractIssueTabPanel;
import com.atlassian.jira.plugin.issuetabpanel.IssueAction;
import com.atlassian.jira.util.http.JiraUrl;
import com.atlassian.jira.web.ExecutingHttpRequest;
import com.atlassian.sal.api.net.Request;
import com.atlassian.sal.api.net.Response;
import com.atlassian.sal.api.net.ResponseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
Другой оператор импорта может оставаться, за исключением следующего. Вам нужно удалить его, так как мы используем пользователя пакета Crowd:
import com.opensymphony.user.User;
- После строки, которая создает экземпляр регистратора, добавьте следующий код:
private final EntityLinkService entityLinkService;
public ConfluenceSpaceTabPanel(EntityLinkService entityLinkService)
{
this.entityLinkService = entityLinkService;
}
- Обратите внимание, что мы вводим EntityLinkService в ConfluenceSpaceTabPanel через конструктор класса.
- Метод getActions определяет, что отображается на нашей вкладке. Замените его содержимое следующим текстом:
EntityLink entityLink = entityLinkService.getPrimaryEntityLink(issue.getProjectObject(), ConfluenceSpaceEntityType.class);
if (entityLink == null)
{
return Collections.singletonList(new GenericMessageAction("No Link to a Confluence for this JIRA Project configured"));
}
Этот код использует EntityLinkService, чтобы найти ссылку на приложение, которую администраторы настроили между проектом JIRA и пространством Confluence. Если он не находит ссылку, он просто печатает сообщение на панели вкладок, говоря так.
- Теперь, когда у вас есть объект EntityLink, используйте его для создания фабрики запросов для отправки аутентифицированных HTTP-запросов на связанную установку Confluence:
ApplicationLinkRequestFactory requestFactory = entityLink.getApplicationLink().createAuthenticatedRequestFactory();
Эта фабрика запросов проверяет подлинность на Confluence, используя тип аутентификации, настроенный системным администратором. С помощью этой фабрики запросов вы можете выполнить аутентифицированный запрос API REST Confluence для выполнения поиска.
- Инициализируйте несколько переменных:
final String query = issue.getKey();
String confluenceContentType = "page";
final String spaceKey = entityLink.getKey();
- Затем добавьте инструкцию Java try с указанным содержимым:
try
{
ApplicationLinkRequest request = requestFactory.createRequest(Request.MethodType.GET, "/rest/prototype/1/search?query=" + query + "&spaceKey=" + spaceKey + "&type=" + confluenceContentType);
String responseBody = request.execute(new ApplicationLinkResponseHandler<String>()
{
public String credentialsRequired(final Response response) throws ResponseException
{
return response.getResponseBodyAsString();
}
public String handle(final Response response) throws ResponseException
{
return response.getResponseBodyAsString();
}
});
Это создает запрос к API REST Confluence, который ищет ключевую проблему задачи и выполняет запрос, сохраняя результаты в строке responseBody.
- Теперь проанализируйте ответ запроса REST GET, поместите результаты в список, который мы можем представить на вкладке задачи:
Document document = parseResponse(responseBody);
NodeList results = document.getDocumentElement().getChildNodes();
List<IssueAction> issueActions = new ArrayList<IssueAction>();
for (int j = 0; j < results.getLength(); j++)
{
NodeList links = results.item(j).getChildNodes();
for (int i = 0; i < links.getLength(); i++)
{
Node linkNode = links.item(i);
if ("link".equals(linkNode.getNodeName()))
{
NamedNodeMap attributes = linkNode.getAttributes();
Node type = attributes.getNamedItem("type");
if (type != null && "text/html".equals(type.getNodeValue()))
{
Node href = attributes.getNamedItem("href");
URI uriToConfluencePage = URI.create(href.getNodeValue());
IssueAction searchResult = new GenericMessageAction(String.format("Reference to Issue found in Confluence page <a target=\"_new\" href=%1$s>%1$s</a>", uriToConfluencePage.toString()));
issueActions.add(searchResult);
}
}
}
}
return issueActions;
- Закройте блок the try и добавьте инструкции catch:
}
catch (CredentialsRequiredException e)
{
final HttpServletRequest req = ExecutingHttpRequest.get();
URI authorisationURI = e.getAuthorisationURI(URI.create(JiraUrl.constructBaseUrl(req) + "/browse/" + issue.getKey()));
String message = "You have to authorise this operation first. <a target=\"_new\" href=%s>Please click here and login into the remote application.</a>";
IssueAction credentialsRequired = new GenericMessageAction(String.format(message, authorisationURI));
return Collections.singletonList(credentialsRequired);
}
catch (ResponseException e)
{
return Collections.singletonList(new GenericMessageAction("Response exception. Message: " + e.getMessage()));
}
catch (ParserConfigurationException e)
{
return Collections.singletonList(new GenericMessageAction("Failed to read response from Confluence." + e.getMessage()));
}
catch (SAXException e)
{
return Collections.singletonList(new GenericMessageAction("Failed to read response from Confluence." + e.getMessage()));
}
catch (IOException e)
{
return Collections.singletonList(new GenericMessageAction("Failed to read response from Confluence." + e.getMessage()));
}
}
Обратите внимание на инструкцию catch для CredentialsRequiredException. Запрос может выдать это исключение, если этот завод запроса пытается использовать аутентификацию OAuth. Для аутентификации OAuth требуется пользователь (зарегистрированный в локальном приложении) для получения «токена доступа» из удаленного приложения. Это позволяет пользователю (локального приложения) отправлять запросы удаленному приложению от имени этого пользователя. В рамках процесса аутентификации OAuth пользователю может быть предложено войти в учетную запись удаленного приложения.
CredentialsRequiredException выкидывается, если пользователь еще не получил требуемый токен доступа. Это исключение содержит метод, который возвращает URI авторизации, через который пользователи могут авторизоваться в удаленном приложении (и, следовательно, может потребовать, чтобы они вошли в это приложение), чтобы получить требуемый токен доступа. Вы должны связать пользователя с этим URI, чтобы они могли получить запрос на токен доступа
- Наконец, добавьте метод parseResponse, который мы вызывали ранее.
private Document parseResponse(String body) throws ParserConfigurationException, IOException, SAXException
{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputStream is = new ByteArrayInputStream(body.getBytes("UTF-8"));
return db.parse(is);
}
- Сохраните и закройте файл.
Объединяя все это, ваш класс должен выглядеть примерно так:
package com.example.plugins.tutorial.jira.tabpanels;
import com.atlassian.applinks.api.ApplicationLinkRequest;
import com.atlassian.applinks.api.ApplicationLinkRequestFactory;
import com.atlassian.applinks.api.ApplicationLinkResponseHandler;
import com.atlassian.applinks.api.CredentialsRequiredException;
import com.atlassian.applinks.api.EntityLink;
import com.atlassian.applinks.api.EntityLinkService;
import com.atlassian.applinks.api.application.confluence.ConfluenceSpaceEntityType;
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.tabpanels.GenericMessageAction;
import com.atlassian.jira.plugin.issuetabpanel.AbstractIssueTabPanel;
import com.atlassian.jira.plugin.issuetabpanel.IssueAction;
import com.atlassian.jira.plugin.issuetabpanel.IssueTabPanel;
import com.atlassian.jira.util.http.JiraUrl;
import com.atlassian.jira.web.ExecutingHttpRequest;
import com.atlassian.sal.api.net.Request;
import com.atlassian.sal.api.net.Response;
import com.atlassian.sal.api.net.ResponseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ConfluenceSpaceTabPanel extends AbstractIssueTabPanel implements IssueTabPanel
{
private static final Logger log = LoggerFactory.getLogger(ConfluenceSpaceTabPanel.class);
private final EntityLinkService entityLinkService;
public ConfluenceSpaceTabPanel(EntityLinkService entityLinkService)
{
this.entityLinkService = entityLinkService;
}
public List getActions(Issue issue, User remoteUser)
{
EntityLink entityLink = entityLinkService.getPrimaryEntityLink(issue.getProjectObject(), ConfluenceSpaceEntityType.class);
if (entityLink == null)
{
return Collections.singletonList(new GenericMessageAction("No Link to a Confluence for this JIRA Project configured"));
}
ApplicationLinkRequestFactory requestFactory = entityLink.getApplicationLink().createAuthenticatedRequestFactory();
final String query = issue.getKey();
String confluenceContentType = "page";
final String spaceKey = entityLink.getKey();
try
{
ApplicationLinkRequest request = requestFactory.createRequest(Request.MethodType.GET, "/rest/prototype/1/search?query=" + query + "&spaceKey=" + spaceKey + "&type=" + confluenceContentType);
String responseBody = request.execute(new ApplicationLinkResponseHandler<String>()
{
public String credentialsRequired(final Response response) throws ResponseException
{
return response.getResponseBodyAsString();
}
public String handle(final Response response) throws ResponseException
{
return response.getResponseBodyAsString();
}
});
Document document = parseResponse(responseBody);
NodeList results = document.getDocumentElement().getChildNodes();
List<IssueAction> issueActions = new ArrayList<IssueAction>();
for (int j = 0; j < results.getLength(); j++)
{
NodeList links = results.item(j).getChildNodes();
for (int i = 0; i < links.getLength(); i++)
{
Node linkNode = links.item(i);
if ("link".equals(linkNode.getNodeName()))
{
NamedNodeMap attributes = linkNode.getAttributes();
Node type = attributes.getNamedItem("type");
if (type != null && "text/html".equals(type.getNodeValue()))
{
Node href = attributes.getNamedItem("href");
URI uriToConfluencePage = URI.create(href.getNodeValue());
IssueAction searchResult = new GenericMessageAction(String.format("Reference to Issue found in Confluence page <a target=\"_new\" href=%1$s>%1$s</a>", uriToConfluencePage.toString()));
issueActions.add(searchResult);
}
}
}
}
return issueActions;
}
catch (CredentialsRequiredException e)
{
final HttpServletRequest req = ExecutingHttpRequest.get();
URI authorisationURI = e.getAuthorisationURI(URI.create(JiraUrl.constructBaseUrl(req) + "/browse/" + issue.getKey()));
String message = "You have to authorise this operation first. <a target=\"_new\" href=%s>Please click here and login into the remote application.</a>";
IssueAction credentialsRequired = new GenericMessageAction(String.format(message, authorisationURI));
return Collections.singletonList(credentialsRequired);
}
catch (ResponseException e)
{
return Collections.singletonList(new GenericMessageAction("Response exception. Message: " + e.getMessage()));
}
catch (ParserConfigurationException e)
{
return Collections.singletonList(new GenericMessageAction("Failed to read response from Confluence." + e.getMessage()));
}
catch (SAXException e)
{
return Collections.singletonList(new GenericMessageAction("Failed to read response from Confluence." + e.getMessage()));
}
catch (IOException e)
{
return Collections.singletonList(new GenericMessageAction("Failed to read response from Confluence." + e.getMessage()));
}
}
public boolean showPanel(Issue issue, User remoteUser)
{
return true;
}
private Document parseResponse(String body) throws ParserConfigurationException, IOException, SAXException
{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputStream is = new ByteArrayInputStream(body.getBytes("UTF-8"));
return db.parse(is);
}
}
Дополнительные сведения и TODO см. В комментариях к коду в файле BitBucket.
Шаг 6. Сборка, установка и запуск плагина
Теперь запустите JIRA с помощью плагина:
- Вернитесь к командной строке, перейдите в домашнюю директорию проекта.
- Войдите atlas-run.
- Откройте браузер и откройте приложение, запущенное с помощью atlas-run, например, перейдя по адресу http: // localhost: 2990 / jira.
- На экране входа введите имя пользователя admin и пароль администратора admin.
- Следуйте указаниям мастера, чтобы создать новый проект.
- Храните это окно открытым, когда выполняете следующие шаги.
Затем настройте экземпляр Confluence, против которого вы будете тестировать ваш плагин, как описано ниже. Обратите внимание, что эти шаги включают настройку ссылки на приложение и ссылку на проект. Шаги здесь подробно не описаны. Если вы новичок в администрации Atlassian, вы должны увидеть документацию JIRA для получения подробной информации о том, как выполнить эти задачи.
- Запустите экземпляр Confluence, против которого вы будете тестировать свой плагин:
atlas-run-standalone --product confluence
- Когда Confluence завершает запуск, вернитесь в окно браузера с открытым JIRA и перейдите к консоли администрирования.
- В консоли администрирования JIRA нажмите ссылку «Ссылки на приложения» в меню слева.
- Следуйте инструкциям мастера, чтобы настроить обратную связь приложений между JIRA и экземпляром Confluence.
- Вернитесь на страницу администрирования вашего вновь созданного проекта и настройте ссылку проекта между ним и встроенным демонстрационным пространством в Confluence.
- Создайте новую задачу в проекте JIRA.
- В демонстрационном пространстве в Confluence создайте новую страницу или отредактируйте существующую и добавьте ключ задачи на страницу. Вы можете сделать это в форме макроса задачи JIRA.
- Сохраните страницу.
- Вернитесь в JIRA, просмотрите созданные вами задачи и откройте вкладку панели пространства Confluence. Вы должны увидеть название страницы Confluence, в которой вы упомянули задачу. Если вы не видите страницу Confluence, сначала попробуйте переиндексировать Confluence, а затем повторите попытку просмотра задачи.
Поздравляю, вот и все
Есть шоколад!
По материалам Atlassian JIRA Server Developer Implementing application links in JIRA