Создание расширений рабочего процесса

Применимость

Jira 7.0.0 и более поздних версий.

Уровень опыта

Продвинутый. Вы должны были пройти хотя бы один промежуточный учебник, прежде чем работать с этим учебником. См. Список учебников в DAC.

Оценка времени

Для завершения этого урока вам потребуется около 1 часа.

 

Обзор учебника

Администраторы Jira могут настраивать рабочие процессы Jira в соответствии с целями и процессами команд, которые используют проект. Рабочий процесс определяет, из каких состояний состоит жизненный цикл задачи  Jira, а также правила перехода задачи из одного статуса в другой. Среди других свойств администратор может указать действия, применимые к задаче в каждом состоянии, которые могут переходить из одного состояния в другое, и любые функции, которые инициируются переходом  рабочего процесса.

Чтобы узнать, как настроить рабочие процессы в Jira, см. страницу  Настройка  рабочего процесса. Он описывает рабочие процессы с точки зрения администратора Jira.

В этом уроке вы создадите приложение, которое позволяет создавать пользовательские элементы доступные рабочим процессам. Этот учебник состоит из трех частей. Каждая часть охватывает определенный тип модуля рабочего процесса, а именно:

  • В части 1 показано, как создать условие, которое предотвращает повторное открытие подзадачи, если родительская задача разрешена или закрыта.
  • В части 2 показано, как добавить функцию, которая закрывает родительскую задачу при закрытии последней подзадачи.
  • В части 3 показано, как добавить пользовательскую проверку в рабочий процесс.

Завершенное приложение будет состоять из следующих компонентов:

  1. Java-классы, инкапсулирующие логику приложения.
  2. Ресурсы для отображения пользовательского интерфейса приложения.
  3. Дескриптор приложения (то есть XML-файл), чтобы включить модуль плагина в приложении Atlassian.

Когда вы закончите, все компоненты будут упакованы в один JAR-файл.

Об этих инструкциях

Вы можете использовать любую поддерживаемую комбинацию операционной системы и IDE для создания этого приложения. Эти инструкции были написаны с использованием IntelliJ IDEA 2017.2 на Ubuntu Linux. Если вы используете другую операционную систему или комбинацию IDE, вы должны использовать эквивалентные операции для своей конкретной среды.

Этот учебник был последний раз проверен с помощью Jira 7.7.1.

Прежде чем вы начнете

Чтобы завершить этот учебник, вам необходимо знать следующее:

  1. Основы разработки Java: классы, интерфейсы, методы, использование компилятора и т. д.
  2. Как создать проект плагина Atlassian с помощью Atlassian Plugin SDK.
  3. Основы администрирования Jira, в частности, разработка и управление рабочими процессами проекта.

Источник приложения

Мы рекомендуем вам проработать этот учебник. Если вы хотите пропустить или проверить свою работу, когда закончите, вы можете найти исходный код приложения на Atlassian Bitbucket.

 

Чтобы клонировать репозиторий, выполните следующую команду:


git clone https://bitbucket.org/atlassian_tutorial/tutorial-jira-add-workflow-extensions

Кроме того, вы можете загрузить исходный код в виде ZIP-архива.

Часть 1. Создайте проект приложения и модуль условий рабочего процесса

В этой части вы создадите  условие пользовательского рабочего процесса. Условие препятствует переходу задачи из одного состояния в другое на основе определенного условия. В нашем случае это условие предотвращает повторное открытие подзадачи, если родительская задача разрешена или закрыта.

Шаг 1. Создайте проект приложения

На этом этапе вы будете использовать Atlassian Plugin SDK для создания скелета для вашего проекта приложения. Atlassian Plugin SDK автоматизирует большую часть разработки приложений для вас. Он включает команды для создания приложения и добавления модулей в приложение.

  1. Настройте SDK Atlassian Plugin и создайте проект, если вы этого еще не сделали.
  2. Откройте окно терминала и перейдите в каталог, в котором вы хотите сохранить проект приложения.
  3. Запустите следующую команду SDK:

atlas-create-jira-plugin

  1. При появлении запроса введите новые настройки приложения.

group-id

com.example.plugins.tutorial

artifact-id

add-workflow-extensions

version

1.0-SNAPSHOT

package

com.example.plugins.tutorial

  1. Подтвердите свои записи при появлении запроса.

SDK генерирует домашнюю директорию проекта с файлами проекта, такими как POM (то есть файл определения объектной модели проекта), исходный код заглушки и ресурсы приложения.

  1. Импортируйте проект в свою любимую среду IDE.

Шаг 2. Тонкая настройка POM

Это хорошая идея, чтобы ознакомиться с файлом конфигурации проекта, известным как POM. Среди других функций POM объявляет зависимости проекта и устанавливает параметры сборки. Он также содержит описательную информацию для вашего приложения.

Измените метаданные и добавьте зависимость следующим образом:

  1. Перейдите в корневую папку вашего проекта и откройте файл pom.xml.
  2. Добавьте название организации или организации и URL веб-сайта в элемент organization:

<organization>
    <name>Example Company</name>
    <url>http://www.example.com/</url>
</organization>

  1. Чтобы добавить осмысленное описание для вашего приложения, обновите элемент описания проекта description. Например:

<description> Расширяет отчеты о задачах JIRA. </ description>

Вводимые вами данные organization и description передаются в файл дескриптора приложения atlassian.plugin.xml. Оттуда Jira использует их на дисплее консоли администратора для вашего риложения.

  1. Раскомментируйте зависимость jira-core и измените ее scope к test.

<dependency>
    <groupId>com.atlassian.jira</groupId>
    <artifactId>jira-core</artifactId>
    <version>${jira.version}</version>
    <scope>test</scope>
  </dependency>

Хотя мы препятствуем использованию основных классов ядра Jira в вашем приложении, вам понадобятся они для некоторого кода тестирования, который SDK будет добавлять для вашего модуля.

  1. Сохраните файл.

Шаг 3. Добавьте модуль плагина в проект

На этом этапе вы добавите модуль в свой проект приложения. Модуль можно рассматривать как единую функциональность в  фреймворке плагина Atlassian. Мы добавим тот, который реализует наше пользовательское условие рабочего процесса. Позже вы добавите модули для функции post и валидатора.

Вы можете использовать генератор модуля плагина (то есть другую команду atlas) для генерации кода заглушки для модулей, которые требуются приложению.

  1. Откройте окно терминала и перейдите в корневую папку приложения, где находится файл pom.xml.
  2. Выполните следующую команду:

atlas-create-jira-plugin-module

  1. Введите номер для модуля условий рабочего процесса (в настоящее время 32).
  2. При появлении запроса введите следующую информацию.

Введите новое имя класса

ParentIssueBlockingCondition

Имя пакета

com.example.plugins.tutorial.jira.workflow

  1. Выберите «N» для «Показать расширенную настройку».
  1. Выберите N для добавления другого модуля плагина.

SDK генерирует стартовые файлы Java, файлы шаблонов и тестовый код для модуля. Он также добавляет модуль условий рабочего процесса workflow-condition в файлы pom.xml и atlassian-plugin.xml.

Стоит взглянуть на файл atlassian-plugin.xml. Это файл XML, который идентифицирует приложение для Jira и определяет требуемую функциональность приложения. Файл находится в вашем проекте в разделе src / main / resources.

В дескрипторе вы заметите, что SDK добавил модуль условий рабочего процесса  workflow-condition с тремя ресурсами Velocity. Ресурс представления view использует файл шаблона parent-issue-blocking-condition.vm, в то время как ресурсы input-parameters и edit-parameters используют parent-issue-block-condition-input.vm. Поскольку взаимодействие конечного пользователя между входом в начальные настройки конфигурации для функции (условие рабочего процесса в нашем случае) - это то же самое, что и редактирование этих параметров, мы назначили один шаблон для обоих ресурсов. Если ваше приложение вызвало это, вы можете определить отдельные шаблоны для этих просмотров.

На следующем шаге мы изменим шаблоны.

Шаг 4. Напишите пользовательский интерфейс

Наше состояние будет относительно простым. Родительская проблема должна находиться в определенном состоянии для условия, которое должно быть выполнено. Выполните логику для этого в шаблонах следующим образом:

  1. В окне терминала перейдите к src / main / resources / templates / conditions / и откройте файл parent-issue-blocking-condition.vm.
  2. Добавьте описание намерения условия, а затем добавьте логику для итерации и отображения списка статусов.

Они будут предоставлены представлению классов Java, которые вы создадите позже. Ваш шаблон должен выглядеть примерно так:


Родительская задача должна иметь один из следующих статусов чтобы позволить одному из следующих переходов подзадачи:
(The parent issue must have one of the following statuses to allow sub-task transitions:)
#foreach ($status in $statuses)
    <b>$status.getName()</b>
    #if($velocityCount != $statuses.size())
        #if($velocityCount == ($statuses.size() - 1))
             or
        #else
            ,
        #end
    #else
        .
    #end
#end

Код имеет отступы, чтобы упростить его чтение, но, как и все шаблоны, вам, возможно, придется поместить все это на одну строку, чтобы избежать нежелательных пробелов. Как вы можете видеть, он просто петляет вокруг предоставленных параметров  Velocity $statuses и печатает список каждого статуса, разделенный запятыми.

 

  1. В том же каталоге откройте файл parent-issue-blocking-condition-input.vm.
  2. Добавьте код шаблона, который позволяет пользователям выбирать состояния, в которых должна выполняться родительская задача, чтобы можно было закрыть дочернюю проблему, например:

<tr bgcolor="ffffff">
    <td align="right" valign="top" bgcolor="fffff0">
        <span class="label">Statuses:</span>
    </td>
    <td bgcolor="ffffff" nowrap>
        <table cellpadding="2" cellspacing="2">
        #foreach ($status in $statuses)
            <tr>
                <td><input type="checkbox" name="$status.getId()"
                #if (${selectedStatuses})
                    #if (${selectedStatuses.contains($status.getId())})
                    CHECKED
                    #end
                #end
                ></td>
                <td>#displayConstantIcon ($status) $status.getName()</td>
            </tr>
        #end
        </table>
        <br><font size="1">The parent issue statuses required to allow sub-task issue transitions.</font>
    </td>
</tr>

Обратите внимание, что мы используем макрос displayConstantIcon для отображения значка состояния рядом с каждым статусом.

 

Шаг 5. Напишите классы Java

До сих пор вы создавали заглушки для своих модулей плагинов и определяли представления. На этом этапе вы будете писать классы Java.

  1. Откройте файл ParentIssueBlockingConditionFactory.java. Обратите внимание, что он содержит методы, которые предоставляют параметры для каждого из представлений. На данный момент параметр представляет собой единственный  параметр Velocity word со значением по умолчанию test. Мы изменим методы, чтобы сделать следующее:
  • Метод getVelocityParamsForInput должен заполнять statuses  параметров Velocity всеми доступными статусами.
  • Метод getVelocityParamsForView должен заполнять статусы параметра Velocity statuses только теми статусами, которые были выбраны пользователем. Для этого мы получаем аргумент statuses из ConditionDescriptor.
  • Метод getVelocityParamsForEdit должен заполнять Velocity параметр selectedStatuses статусами, хранящимися в аргументе состояний ConditionDescriptor.
  • Метод getDescriptorParams используется для извлечения параметров в ConditionDescription, поэтому мы получим его, чтобы получить статусы, указанные во входе map, и сохраним их в параметре statuses. Здесь используется список, разделенный запятыми, но вы можете использовать любой механизм, который вам нравится для достижения этого результата.

 

  1. Замените класс следующим:

package com.example.plugins.tutorial.jira.workflow;

import com.atlassian.jira.issue.comparator.ConstantsComparator;
import com.atlassian.jira.issue.status.Status;
import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory;
import com.atlassian.jira.plugin.workflow.WorkflowPluginConditionFactory;
import com.atlassian.jira.util.collect.MapBuilder;
import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.plugin.spring.scanner.annotation.imports.JiraImport;
import com.opensymphony.workflow.loader.AbstractDescriptor;
import com.opensymphony.workflow.loader.ConditionDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
 
@Scanned
public class ParentIssueBlockingConditionFactory extends AbstractWorkflowPluginFactory
    implements WorkflowPluginConditionFactory {
 
  @JiraImport
  private final ConstantsManager constantsManager;
 
  public ParentIssueBlockingConditionFactory(ConstantsManager constantsManager) {
    this.constantsManager = constantsManager;
  }
 
  protected void getVelocityParamsForInput(Map<String, Object> velocityParams) {
    //all available statuses
    Collection<Status> statuses = constantsManager.getStatuses();
    velocityParams.put("statuses", Collections.unmodifiableCollection(statuses));
  }
 
  protected void getVelocityParamsForEdit(Map<String, Object>  velocityParams, AbstractDescriptor descriptor) {
    getVelocityParamsForInput(velocityParams);
    velocityParams.put("selectedStatuses", getSelectedStatusIds(descriptor));
  }
 
  protected void getVelocityParamsForView(Map<String, Object>  velocityParams, AbstractDescriptor descriptor) {
    Collection selectedStatusIds = getSelectedStatusIds(descriptor);
    List<Status> selectedStatuses = new ArrayList<>();
    for (Object selectedStatusId : selectedStatusIds) {
      String statusId = (String) selectedStatusId;
      Status selectedStatus = constantsManager.getStatus(statusId);      
if (selectedStatus != null) { selectedStatuses.add(selectedStatus);       }     
}     selectedStatuses.sort(new ConstantsComparator());       
velocityParams.put("statuses", Collections.unmodifiableCollection(selectedStatuses));   
}     public Map<String, Object> getDescriptorParams(Map conditionParams) {     // процесс сопоставления который будет содержать параметры запроса
     //  fтеперь просто конкатенация в строку, разделенную запятой    
//  производственный код сделает что-то более надежное.     
Collection statusIds = conditionParams.keySet();     
StringBuilder statIds = new StringBuilder();       
for (Object statusId : statusIds) {       statIds.append((String) statusId).append(",");     }       
return MapBuilder.build("statuses", statIds.substring(0, statIds.length() - 1));   }     
private Collection getSelectedStatusIds(AbstractDescriptor descriptor) {     
Collection<String> selectedStatusIds = new ArrayList<>();     
if (!(descriptor instanceof ConditionDescriptor)) {       throw new IllegalArgumentException("Descriptor must be a ConditionDescriptor.");     }       
ConditionDescriptor conditionDescriptor = (ConditionDescriptor) descriptor;       
String statuses = (String) conditionDescriptor.getArgs().get("statuses");     
StringTokenizer st = new StringTokenizer(statuses, ",");       
while (st.hasMoreTokens()) 
{       selectedStatusIds.add(st.nextToken());     
}     return selectedStatusIds;   
} 
} 
  1. Откройте файл ParentIssueBlockingCondition.java. Обратите внимание, что он расширяет AbstractJiraCondition и реализует метод passCondition. Этот метод будет содержать логику самого WorkflowCondition. Это требует трех аргументов:
  • transientVars - это Map карта переменных, доступных только для этого метода. Она заполняется Jira и гарантирует, что доступны общедоступные переменные, такие как originalissueobject, который содержит объект IssueObject, связанный с рабочим процессом.
  • args - это Map карта, которая содержит значения, поднятые прямо из ввода формы и ConditionDescriptor, поэтому ваши «статусы» "statuses" будут доступны здесь.
  • ps содержит свойства, которые определены в XML рабочего процесса и сохраняются на разных этапах рабочего процесса. На практике это редко используется.
  1. Замените автоматически сгенерированный код следующим:

package com.example.plugins.tutorial.jira.workflow;
 
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.workflow.WorkflowFunctionUtils;
import com.atlassian.jira.workflow.condition.AbstractJiraCondition;
import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.plugin.spring.scanner.annotation.imports.JiraImport;
import com.opensymphony.module.propertyset.PropertySet;
import java.util.StringTokenizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import java.util.Map;
 
@Scanned
public class ParentIssueBlockingCondition extends AbstractJiraCondition {
    private static final Logger log = LoggerFactory.getLogger(ParentIssueBlockingCondition.class);
 
    public static final String FIELD_WORD = "word";
 
    @JiraImport
    private IssueManager issueManager;
 
    public ParentIssueBlockingCondition(IssueManager issueManager){
        this.issueManager = issueManager;
    }
 
    public boolean passesCondition(Map transientVars, Map args, PropertySet ps) {
        Issue subTask = (Issue) transientVars.get(WorkflowFunctionUtils.ORIGINAL_ISSUE_KEY);
 
        Issue parentIssue = issueManager.getIssueObject(subTask.getParentId());
        if (parentIssue == null) {
            return false;
        }
 
        String statuses = (String) args.get("statuses");
        StringTokenizer st = new StringTokenizer(statuses, ",");
 
        while (st.hasMoreTokens()) {
            String statusId = st.nextToken();
            if (parentIssue.getStatus().getId().equals(statusId)) {
                return true;
            }
        }
        return false;
    }
}

Здесь мы вводим IssueManager, используя инъекцию конструктора, также добавляем аннотацию @Scanned и @JiraImport, чтобы заставить Atlassian Spring Scanner заметить наш класс и импортировать IssueManager из Jira.

 

Логика проверки условия проста: метод получает задачу подзадачи с карты transientVars, получает список допустимых статусов из args, получает родительскую задачу из IssueManager с помощью getParentId (), и если статус родителя один из допустимых статусов, метод возвращает true.

Шаг 6. Создание, установка и запуск приложения.

На этом этапе вы запустите Jira и посмотрите, что вы сделали до сих пор. Однако сначала вам нужно иметь дело с тестами, которые предоставил SDK. До сих пор вы внесли достаточно изменений в исходный код, что утверждения в тестовом коде дадут вам ошибки.

Детали тестирования приложений выходят за рамки этого урока. Но если вы уже знакомы с написанием и запуском тестов приложений, вы можете попробовать обновить тесты самостоятельно. Для более быстрой альтернативы попробуйте один из следующих вариантов:

  • Для этого учебника замените каталог src / test эквивалентным тестовым каталогом в репозитории Bitbucket. Обязательно используйте ветвь part1, потому что в других ветвях есть тестовый код для классов, которые вы еще не добавили.
  • Запустите команду atlas-run, упомянутую позже, с флагом -DskipTests = true, который обходит тестирование (хотя тесты все еще компилируются).
  • На данный момент удалите каталог src / test из вашего проекта.

Чтобы запустить Jira с установленным вами приложением, выполните следующие действия:

  1. Убедитесь, что вы сохранили все изменения кода до этой точки.
  2. Откройте окно терминала и перейдите в корневую папку приложения, где находится файл pom.xml.
  3. Выполните следующую команду:

atlas-run

Напоминаем, добавьте -DskipTests = true, чтобы не запускать тестовый код.

Эта команда создает код приложения, запускает экземпляр Jira и устанавливает ваше приложение. Это может занять минуту или две. Когда это будет сделано, вы должны увидеть что-то подобное в конце вывода терминала:


[INFO] [talledLocalContainer] Tomcat 6.x started on port [2990]
[INFO] jira started successfully in 149s at http://atlas-laptop:2990/jira
[INFO] Type Ctrl-D to shutdown gracefully
[INFO] Type Ctrl-C to exit

  1. В своем браузере перейдите в локальный экземпляр Jira (URL-адрес указывается в выводе терминала).
  2. Войдите в систему, используя admin / admin по умолчанию.
  3. Создайте проект при появлении запроса.

На следующем шаге вы создадите рабочий процесс и протестируете свое приложение.

 

Шаг 7. Настройте рабочий процесс и проверьте условие

Следующие шаги описывают, как применить условие рабочего процесса в Jira как администратор Jira. Если вы не знакомы с рабочими процессами или не знаете, как администраторы могут их настраивать и применять, читайте больше на странице «Настройка рабочего процесса» в документации Jira.

Вот  версия резюме:

  1. Нажмите значок cog> Задачи, а затем щелкните «Рабочие процессы» в меню слева или используйте ярлык «.», чтобы открыть поиск и начать ввод Workflows.
  2. Нажмите ссылку «Копировать» рядом с рабочим процессом Jira (он может быть скрыт в разделе «Inactive»). Мы будем работать с измененной версией рабочего процесса по умолчанию. Кроме того, вы можете создать новый и начать с нуля.
  3. В текстовом представлении нажмите «Переоткрыть задачу» в столбце «Переходы».
  4. На вкладке «Условия» нажмите «Добавить».
  5. Нажмите «Состояние блокировки родительской задачи»> «Добавить».
  6. На странице «Добавить параметры к условию» выберите «Открыть» и «Повторно открыть».
  7. В левом меню выберите «Схемы рабочего процесса»> «Добавить схему рабочего процесса».
  8. Введите имя и описание схемы и нажмите «Добавить».
  9. Нажмите «Добавить рабочий процесс»> «Добавить существующий» и выберите рабочий процесс, что вы создали.
  10. Присвойте рабочий процесс типу задачи подзадачи и нажмите «Готово».
  11. Перейдите на страницу администрирования проекта Jira, измените схему рабочего процесса по умолчанию на новую схему рабочего процесса. Выполните шаги, чтобы связать и перенести проект на новую схему.

Когда вы закончите, настройки рабочего процесса будут выглядеть так.

                           РИСУНОК

  1. Создайте задачу и подзадачу в своем проекте, а затем закройте обе из них. Обратите внимание, что вы не можете повторно открыть подзадачу, так как она не имеет кнопки Reopen. Она появляется снова, только если вы снова открываете родительскую задачу.

 

 Вы можете оставить Jira и использовать команду atlas-package, которая запускает QuickReload для перезагрузки изменений вашего приложения на ходу. Это экономит время перезагрузки Jira каждый раз, когда вы меняете код приложения или файлы ресурсов.

Часть 2. Создание функции post рабочего процесса

На этом этапе вы создадите функцию рабочего процесса, которая автоматически закрывает родительскую задачу, когда все подзадачи будут закрыты. Если вы клонировали репозиторий Bitbucket для этого учебника, вы можете увидеть решение для этой части, проверив его в ветке part2:


git checkout part2

Шаг 1. Создайте модуль

Вы можете использовать генератор модуля плагина (то есть другую команду atlas) для генерации кода заглушки для модулей, требуемых приложением.

  1. Откройте окно терминала и перейдите в корневой каталог проекта.
  2. Выполните следующую команду:

atlas-create-jira-plugin-module

  1. Введите номер для модуля  функции Post рабочего процесса (в настоящее время 33).
  2. При появлении запроса введите следующую информацию.

Enter New Classname

Введите новое имя класса

CloseParentIssuePostFunction

Package Name

Имя пакета

com.example.plugins.tutorial.jira.workflow

  1. Выберите «N» для «Показать расширенную настройку».
  2. Выберите «N» для добавления другого модуля плагина.

 

Как и прежде, это создаст шаблоны и фабрики для вас.

Шаг 2. Запишите приложение

  1. Поскольку для этой функции нам не нужен интерфейс, мы удалим эти возможности из нашего модуля:
    1. Откройте файл atlassian-plugin.xml и найдите новый модуль функции workflow-function.
  2. Измените класс из com.example.plugins.tutorial.jira.workflow.CloseParentIssuePostFunctionFactory на com.atlassian.jira.plugin.workflow.WorkflowNoInputPluginFactory.
  3. Удалите параметры редактирования edit-parameters и элементы ресурсов input-parameters из одной и той же декларации модуля. Это должно оставить вам только объявление ресурса view для модуля, что указывает на шаблон close-parent-issue-post-function.vm.
  4. Сохраните файл.
  5. Удалите эти файлы:
  • java из src / main / java / com / example / plugins / tutorial / jira / workflow /.
  • шаблон close-parent-issue-post-function-input.vm из src / main / resources / templates / postfunctions /.
  1. Откройте шаблон close-parent-issue-post-function.vm и замените его содержимое на значимую информацию о вашей функции, например:

Parent Issue will be closed on closing final associated sub-task (all other associated sub-tasks are already closed).

Родительская задача будет закрыта при закрытии заключительной связанной подзадачи (все остальные связанные подзадачи уже закрыты). 

 

  1. Перейдите в src / main / java / com / example / plugins / tutorial / jira / workflow / и откройте файл CloseParentIssuePostFunction.java.

 

  1. Добавьте несколько объявлений переменных и конструктор для нашего класса, а также пометьте класс с помощью аннотации @Scanned , как это было ранее:

private static final Logger log = LoggerFactory.getLogger(CloseParentIssuePostFunction.class);
 
@JiraImport
private final WorkflowManager workflowManager;
@JiraImport
private final SubTaskManager subTaskManager;
@JiraImport
private final JiraAuthenticationContext authenticationContext;
@JiraImport
private IssueManager issueManager;   
private final Status closedStatus;   
public CloseParentIssuePostFunction(ConstantsManager constantsManager,                                     
WorkflowManager workflowManager,                                     
SubTaskManager subTaskManager,                                     
JiraAuthenticationContext authenticationContext,                                     
IssueManager issueManager) {     this.issueManager = issueManager;     
this.workflowManager = workflowManager;     
this.subTaskManager = subTaskManager;     
this.authenticationContext = authenticationContext;     
closedStatus = constantsManager.getStatus(Integer.toString(IssueFieldConstants.CLOSED_STATUS_ID)); 
} 
  1. Внедрите метод execute (). Этот метод выполняет большую часть нашей работы. Замените execute () следующим:

public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException {
    // Получить подзадачу
    MutableIssue subTask = getIssue(transientVars);
    // Получить родительскую задачу
    MutableIssue parentIssue = issueManager.getIssueObject(subTask.getParentId());
 
    // Убедитесь, что родительская задача еще не закрыта
    if (parentIssue == null || IssueFieldConstants.CLOSED_STATUS_ID == Integer
        .parseInt(parentIssue.getStatusId())) {
        return;
    }
 
    // Проверяем, что ВСЕ ДРУГИЕ подзадачи закрыты
    Collection<Issue> subTasks = subTaskManager.getSubTaskObjects(parentIssue);
 
    for (Iterator<Issue> iterator = subTasks.iterator(); iterator.hasNext(); ) {
        Issue associatedSubTask = iterator.next();
        if (!subTask.getKey().equals(associatedSubTask.getKey()) &&
            IssueFieldConstants.CLOSED_STATUS_ID != Integer.parseInt(associatedSubTask.getStatus().getId())) {
            return;
        }
    }
 
    // Все подзадачи теперь закрыты - закрыть родительскую задачу
    try {
        closeIssue(parentIssue);
    } catch (WorkflowException e) {
        log.error(
            "Ошибка при закрытии задачи: " + parentIssue.getKey() + ": " + e, e);
        e.printStackTrace();
    }
}

Найдите минутку, чтобы посмотреть на код. Как вы можете видеть, это сложно, потому что для получения статуса всех подзадач нам нужно взаимодействовать с рабочим процессом, IssueManager и SubTaskManager, а затем в нашем случае закрыть родительские задачи. Наша функция получает подзадачу с карты transientVars, а затем извлекает parentIssue. Если родительская задача уже закрыта, она просто выполняет возврат return. Затем он восстанавливает подзадачи с помощью SubTaskManager.

 

При написании своей собственной функции post вы можете либо ввести это в конструктор (как здесь), либо использовать ComponentAccessor для получения экземпляра. Если все остальные подзадачи закрыты, родительская задача также будет закрыта.

  1. Внедрите метод, который мы используем, чтобы закрыть задачу в нашем методе execute ():

private void closeIssue(Issue issue) throws WorkflowException {
    Status currentStatus = issue.getStatus();
    JiraWorkflow workflow = workflowManager.getWorkflow(issue);
    List<ActionDescriptor> actions = workflow.getLinkedStep(currentStatus).getActions();
    // look for the closed transition
    ActionDescriptor closeAction = null;
    for (ActionDescriptor descriptor : actions) {
        if (descriptor.getUnconditionalResult().getStatus().equals(closedStatus.getName())) {
            closeAction = descriptor;
            break;
        }
    }
    if (closeAction != null) {
        ApplicationUser currentUser = authenticationContext.getLoggedInUser();
        IssueService issueService = ComponentAccessor.getIssueService();
        IssueInputParameters parameters = issueService.newIssueInputParameters();
        parameters.setRetainExistingValuesWhenParameterNotProvided(true);
        IssueService.TransitionValidationResult validationResult =
                issueService.validateTransition(currentUser, issue.getId(),
                closeAction.getId(), parameters);
        IssueService.IssueResult result = issueService.transition(currentUser, validationResult);
    }
}

Здесь мы находим близкий переход с помощью WorkflowManager. Когда у вас есть переход, вы можете проверить этот переход с помощью IssueService, а затем закрыть задачу, используя метод IssueService#transition.

  1. Добавьте инструкции импорта для нового кода. Как и прежде, вы можете использовать предложения по импорту, сделанные вашей IDE, или обратиться к репозиторию Bitbucket.
  1. Сохраните файл.
  1. Перейдите на страницу src / test / java / com / example / plugins / tutorial / jira / workflow и удалите автоматически сгенерированный файл CloseParentIssuePostFunctionTest.java.

Шаг 3. Протестируете приложение.

На этом этапе вы проверите, что ваше приложение закрывает родительскую задачу, когда все подзадачи закрыты.

  1. Перезагрузите приложение в Jira:
  • Использование QuickReload с помощью команды atlas-package.
  • Перезапустите
  1. Перейдите на страницу администрирования рабочего процесса и отредактируйте пользовательский рабочий процесс.
  2. Добавьте функцию Close Parent Issue Post Function к переходу Close в рабочем процессе.

Обратите внимание, что существует несколько переходов Close, поэтому обязательно добавляйте их к каждой.

  1. Опубликуйте свой рабочий процесс.
  2. Вернитесь к своему проекту и создайте задачу с одним или несколькими подзадачами.
  3. Закройте все подзадачи и убедитесь, что родительская задача закрыта функцией post.

Часть 3. Создание валидатора

В этой части учебника вы добавите валидатор, который проверяет, вошел ли пользователь в файл fixVersion. Если нет, переход не удастся. Готовый код для этой части находится в ветке part3 репозитория Bitbucket для этого учебника.

Шаг 1. Создайте модуль

  1. Откройте окно терминала и перейдите в корневой каталог проекта.
  2. Выполните следующую команду:

atlas-create-jira-plugin-module

  1. Введите номер модуля валидатора рабочего процесса.
  2. При появлении запроса введите следующую информацию.

Enter New Classname

Введите новое имя класса

CloseIssueWorkflowValidator

Package Name

Имя пакета

com.example.plugins.tutorial.jira.workflow

  1. Выберите «N» для «Показать расширенную настройку».
  2. Выберите «N» для добавления другого модуля плагина.

Шаг 2. Отредактируйте код

Как и функция post, этот модуль не нуждается в конфигурационных экранах. Поэтому сначала вы удалите части пользовательского интерфейса приложения, а затем напишите логику валидатора.

 

  1. Чтобы отключить фабрику валидатора, созданную приложением, выполните одно из следующих действий:
  • Удалите класс CloseIssueWorkflowValidatorFactory.java.
  • Отредактируйте класс, чтобы он ничего не делал. Например, замените его содержимое следующим:

package com.example.plugins.tutorial.jira.workflow;
 
  import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory;
  import com.atlassian.jira.plugin.workflow.WorkflowPluginValidatorFactory;
  import com.google.common.collect.Maps;
  import com.opensymphony.workflow.loader.AbstractDescriptor;
 
  import java.util.Map;
 
  public class CloseIssueWorkflowValidatorFactory extends AbstractWorkflowPluginFactory implements WorkflowPluginValidatorFactory
  {
      public static final String FIELD_WORD="word";
 
      protected void getVelocityParamsForInput(Map velocityParams)
      {
      }
 
      protected void getVelocityParamsForEdit(Map velocityParams, AbstractDescriptor descriptor)
      {
      }
 
      protected void getVelocityParamsForView(Map velocityParams, AbstractDescriptor descriptor)
      {
      }
 
      public Map getDescriptorParams(Map validatorParams)
      {
          return Maps.newHashMap();
      }
  }

  1. Откройте CloseIssueWorkflowValidator.java и замените метод validate () следующим:

public void validate(Map transientVars, Map args, PropertySet ps) throws InvalidInputException
    {
        Issue issue = (Issue) transientVars.get("issue");
        // Проблема должна иметь fixVersion, иначе вы не можете ее закрыть
        if(null == issue.getFixVersions() || issue.getFixVersions().size() == 0)
        {
            throw new InvalidInputException("Issue must have a fix version");
        }
    }

Этот код проверяет, что задача имеет fixVersion. Если нет, он выдает исключение InvalidInputException.

  1. Откройте файл ресурсов close-issue-workflow-validator.vm и замените его содержимое описательным предложением или двумя, например:

Родительская проблема должна иметь один из следующих статусов, чтобы разрешить переходы подзадачи:
#foreach ($status in $statuses)
    <b>$status.getName()</b>
    #if($velocityCount != $statuses.size())
        #if($velocityCount == ($statuses.size() - 1))
             or
        #else,
            
        #end
    #else.
    #end
#end

  1. Поскольку он не будет вызываться, вы можете удалить шаблон close-issue-workflow-validator-input.vm.
  2. В файле atlassian-plugin.xml отредактируйте объявления ресурсов в новом элементе проверки рабочего процесса workflow-validator, чтобы их значения location ссылались на оставшийся шаблон close-issue-workflow-validator.vm.
  3. Удалите автоматически сгенерированный тестовый класс или исправьте красные тесты.

Шаг 3. Проверьте готовое приложение.

  1. Перезагрузите приложение с помощью QuickReload с помощью команды atlas-package.
  2. Отредактируйте рабочий процесс, чтобы закрыть переходы задач, используя недавно созданный Close Validation Workflow Validator.
  3. Перейдите на страницу администрирования проекта и создайте версию для своего проекта.
  4. Попытайтесь закрыть подзадачу, у которого нет версии исправления. Вы получите ошибку валидации.
  5. Дайте вашей подзадаче версию и посмотрите, можете ли вы ее закрыть сейчас.

Поздравляю, вот и все!

Имейте удовольствие!

 

По материалам Atlassian JIRA  Server Developer Creating workflow extensions