Написание гаджета, показывающего дни, оставшиеся в версии

Обратите внимание: этот учебник был написан и протестирован для работы с JIRA 4.0.1. Некоторые методы, используемые в этом руководстве, изменились, поэтому некоторые части учебника не будут работать с JIRA 6 и 7. Если вы хотите попробовать этот учебник, как это, пожалуйста, попробуйте установить и запустить более старую версию Atlassian SDK (https : //marketplace.atlassian.com/plugins/atlassian-plugin-sdk-windows/versions), например версии 4.2.x, вам также необходимо убедиться, что у вас установлен java 1.6.

Уровень опыта: Новичок

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

Обзор

В этом уроке мы собираемся создать новый гаджет Atlassian для JIRA, связать его внутри плагина, использовать ресурс REST, чтобы предоставить ему данные, и позволить этому гаджету поговорить с ресурсом. Этот гаджет отобразит дни, оставшиеся до того, как будет выпущена данная версия.

Ваш гаджет станет гаджетом «плагин». Это означает, что он будет встроен в плагин Atlassian. Плагин будет состоять из следующих частей:

  • Файл спецификации гаджета для хранения XML и JavaScript гаджета
  • Классы Java, реализующие ресурс REST, который будет использовать гаджет
  • Дескриптор плагина для включения модуля плагина в JIRA

Все эти компоненты будут содержаться в одном JAR-файле. Каждый компонент далее обсуждается в примерах ниже.

Если вас это интересует, вы можете сравнить автономные гаджеты и гаджеты, встроенные в плагины.

Источник плагина

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


$ git clone https://atlassian_tutorial@bitbucket.org/atlassian_tutorial/jira-days-left-in-version-gadget.git

Кроме того, вы можете скачать источник, используя страницу «Загрузки» здесь: https://bitbucket.org/atlassian_tutorial/jira-days-left-in-version-gadget

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

Используйте соответствующую команду atlas-create-application-plugin для создания своего плагина. Например, atlas-create-jira-plugin или atlas-create-confluence-plugin.

Мы будем использовать Atlassian Plugin SDK на протяжении всего учебника, поэтому убедитесь, что он установлен и работает, как описано здесь. Чтобы проверить, что вы готовы к работе, попробуйте команду atlas-version. Вы должны увидеть вывод следующим образом:


ATLAS Version:    3.0.4
ATLAS Home:       /Users/tchan/Products/atlassian-plugin-sdk-3.0.4
ATLAS Scripts:    /Users/tchan/Products/atlassian-plugin-sdk-3.0.4/bin
ATLAS Maven Home: /Users/tchan/Products/atlassian-plugin-sdk-3.0.4/apache-maven
--------
Executing: /Users/tchan/Products/atlassian-plugin-sdk-3.0.4/apache-maven/bin/mvn --version
Apache Maven 2.1.0 (r755702; 2009-03-19 06:10:27+1100)
Java version: 1.6.0_15
Java home: /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home
Default locale: en_US, platform encoding: MacRoman
OS name: "mac os x" version: "10.6.2" arch: "x86_64" Family: "mac"

При появлении запроса создайте плагин JIRA со следующими спецификациями:


Define value for groupId: : com.atlassian.plugins.tutorial
Define value for artifactId: : jira-gadget-tutorial.plugin
Define value for version:  1.0-SNAPSHOT: :
Define value for package:  com.atlassian.plugins.tutorial: :

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

Измените дескриптор плагина на src / main / resources / atlassian-plugin.xml, чтобы предоставить вашему плану уникальный ключ, имя, описание и поставщика, как показано ниже.

Вот плагин atlassian-plugin.xml для вашего плагина:


<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.artifactId}" plugins-version="2">
    <plugin-info>
        <description>${project.description}</description>
        <version>${project.version}</version>
        <vendor name="${project.organization.name}" url="${project.organization.url}" />
    </plugin-info>
</atlassian-plugin>

Шаг 3. Создание спецификации гаджета

Сначала мы создадим файл спецификации гаджета. Это src / main / resources / days-left-gadget.xml:


<?xml version="1.0" encoding="UTF-8" ?>
<Module>
    <ModulePrefs title="__MSG_gadget.days.left.title__"
                 directory_title="__MSG_gadget.days.left.title__"
                 description="__MSG_gadget.days.left.description__">
        <Require feature="dynamic-height"/>
        <Require feature="oauthpopup"/>
        <Require feature="setprefs"/>
        <Require feature="settitle"/>
        <Require feature="views"/>
        <Optional feature="atlassian.util"/>
        <Optional feature="gadget-directory">
            <Param name="categories">
                JIRA
            </Param>
        </Optional>
        #oauth
        #supportedLocales("gadget.common,gadget.days.left")
    </ModulePrefs>
    <UserPref name="isConfigured" datatype="hidden" default_value="false"/>
    <UserPref name="projectId" datatype="hidden"/>
    <UserPref name="version" datatype="hidden" default_value="auto"/>
    <Content type="html">
    <![CDATA[
       <!--We will be adding code here soon to create out gadget-->


  ]]>
  </Content>
</Module>

Вы должны распознать раздел <ModulePrefs> как контейнер метаданных для гаджета: заголовок, название каталога, описание и т. д. Раздел <Content> содержит HTML и / или JavaScript, которые управляют поведением гаджета. Мы оставили его пустым, а пока мы более внимательно смотрим на <ModulePrefs>.

Обратите внимание на функцию <Optional>. Эта функция «гаджет-каталог» указывает, что гаджет предназначен для JIRA и должен быть помещен в категорию «JIRA» в браузере каталогов гаджета. Без этого гораздо труднее найти и использовать гаджеты из браузера каталога.

Теперь мы добавим файл интернационализации в src / main / resources / i18n / i18n.properties:


#days left in iteration gadget
gadget.days.left.title = Days Left
gadget.days.left.subtitle= Days Left: {0} - {1}
gadget.days.left.description=Displays the days remaining in specified project iteration
gadget.days.left.daysAgo = Days Ago
gadget.days.left.today = Today!
gadget.days.left.daysRemaining = Days Remaining
gadget.days.left.autoOption = Next Release Due (auto)
gadget.days.left.noReleaseDate = None
gadget.days.left.noVersionWarning = Selected project has no unreleased versions
gadget.days.left.noReleaseDatesWarning = Selected project has no versions with future release dates.
gadget.days.left.configTitle = Days Left
gadget.days.left.releaseDate= Release Date

Шаг 4. Настройте дескриптор плагина и Maven POM

Теперь нам нужно отредактировать дескриптор плагина на src / main / resources / atlassian-plugin.xml, чтобы предоставить нашему плагину уникальный ключ и некоторую метаинформацию об этом плагине.

Гаджет - это модуль в atlassian-plugin.xml.

Для нашего плагина мы начнем с объявления модуля для спецификации гаджета:


<gadget  key="test" location="days-left-gadget.xml"/>

Необходимо отметить два требуемых свойства:

  • ключ должен быть уникальным для всех модулей в этом плагине.
  • location - путь к файлу спецификации гаджета, относительно src / main / resources.

Затем добавьте элемент <resource> для пакета сообщений:


<resource type="i18n" location="i18n/i18n" name="i18n" />

Затем добавьте элемент <rest> для вашего ресурса отдыха:


<rest key="tutorial-gadget-rest-resources" path="/tutorial-gadget" version="1.0">
    <description>Provides the REST resource for the project list.</description>
</rest>

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


<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.artifactId}" plugins-version="2">
    <plugin-info>
        <description>${project.description}</description>
        <version>${project.version}</version>
        <vendor name="${project.organization.name}" url="${project.organization.url}" />
    </plugin-info>

    <!--
        Registers the gadget spec as a plugin module. This allows the gadget to
        appear in the gadget directory and also allows administrators to
        disable/enable the gadget.
     -->
    <gadget  key="test" location="days-left-gadget.xml"/>

    <!-- Makes the gadget Locale messages available for the gadget's use. -->
    <resource type="i18n" location="i18n/i18n" name="i18n" />


    <!--Automatically finds all JAX-RS resource classes in the plugin andpublishes them.-->
    <rest key="tutorial-gadget-rest-resources" path="/tutorial-gadget" version="1.0">
        <description>Provides the REST resource for the project list.</description>
    </rest>

</atlassian-plugin>

Наконец, чтобы поддерживать включенный модуль REST, обновите файл pom.xml, чтобы он был идентичен тому, что показано ниже:


<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.atlassian.plugin.tutorial</groupId>
    <artifactId>jira-gadget-tutorial-plugin</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <name>jira-gadget-tutorial-plugin</name>
    <description>This is the com.atlassian.plugin.tutorial:jira-gadget-tutorial-plugin plugin for Atlassian JIRA.</description>
    <packaging>atlassian-plugin</packaging>

    <dependencies>
          <dependency>
            <groupId>com.atlassian.gadgets</groupId>
            <artifactId>atlassian-gadgets-api</artifactId>
            <version>1.1.5.rc1</version>
        </dependency>
        <dependency>
            <groupId>com.atlassian.gadgets</groupId>
            <artifactId>atlassian-gadgets-spi</artifactId>
            <version>1.1.5.rc1</version>
        </dependency>
        <dependency>
            <groupId>com.atlassian.jira</groupId>
            <artifactId>atlassian-jira</artifactId>
            <version>${jira.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.6</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.jira</groupId>
            <artifactId>jira-func-tests</artifactId>
            <version>${jira.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>jsr311-api</artifactId>
            <version>1.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.plugins.rest</groupId>
            <artifactId>atlassian-rest-common</artifactId>
            <version>1.1.0.beta6</version>
            <type>jar</type>
        </dependency>
       <dependency>
            <groupId>com.atlassian.jira</groupId>
            <artifactId>jira-rest-plugin</artifactId>
            <version>${jira.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.plugins.rest</groupId>
            <artifactId>atlassian-rest-common</artifactId>
            <version>1.0.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.3</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.sal</groupId>
            <artifactId>sal-api</artifactId>
            <version>2.1.beta4</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>com.atlassian.maven.plugins</groupId>
                <artifactId>maven-jira-plugin</artifactId>
                <version>3.0.4</version>
                <extensions>true</extensions>
                <configuration>
                    <productVersion>${jira.version}</productVersion>
                    <productDataVersion>${jira.data.version}</productDataVersion>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <properties>
        <jira.version>4.0.1</jira.version>
        <jira.data.version>4.0</jira.data.version>
    </properties>

</project>

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

  • Откройте командное окно и перейдите в корневую папку плагина (где находится pom.xml).
  • Запустите atlas-run (или atlas-debug, если вы захотите запустить отладчик в своей среде IDE).

С этого момента вы можете использовать QuickReload для переустановки своего плагина за сценой, когда работаете, просто перестроив свой плагин.

FastDev и atlas-cli устарели. Вместо этого используйте автоматическую переустановку плагинов с помощью QuickReload.

Чтобы запустить переустановку вашего плагина:

  1. Внесите изменения в модуль плагина.
  2. Откройте панель инструментов разработчика.

РИСУНОК

  1. Нажмите значок FastDev.

РИСУНОК

Система перестраивает и перезагружает ваш плагин:

РИСУНОК

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

  1. Откройте панель инструментов разработчика.
  2. Нажмите значок живой перезагрузки.

Значок начинает вращаться, показывая, что он включен.

  1. Отредактируйте ресурсы проекта.
  2. Сохраните изменения:

Вернувшись хост-приложения, ваш плагин отображает все видимые изменения пользователя сделанные вами.

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

 

Полные инструкции приведены в руководстве SDK.

Шаг 5. Сделайте доступными ресурсы для вашего гаджета

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

Создайте новый файл Java с именем DaysLeftInVersionResource.java в следующем месте: / src / main / java / com / atlassian / plugin / tutorial

Ваш DayLeftInVersionResource.java должен быть идентичен приведенному ниже коду:


package com.atlassian.plugin.tutorial;

import com.atlassian.jira.bc.issue.search.SearchService;
import com.atlassian.jira.project.Project;
import com.atlassian.jira.project.version.Version;
import com.atlassian.jira.project.version.VersionManager;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.util.velocity.VelocityRequestContextFactory;
import com.atlassian.jira.web.util.OutlookDate;
import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import static com.atlassian.jira.rest.v1.util.CacheControl.NO_CACHE;

/**
 * REST endpoint for days left in iteration gadget.
 *
 * @since v4.0
 */
@Path ("/days-left-in-iteration")
@AnonymousAllowed
@Produces ({ MediaType.APPLICATION_JSON })
public class DaysLeftInVersionResource
{

    static final int MILLISECONDS_IN_SEC = 1000;
    static final int SECONDS_IN_MIN = 60;
    static final int MINUTES_IN_DAY = 60;
    static final int HOURS_IN_DAY = 24;
    private static final ToStringStyle TO_STRING_STYLE = ToStringStyle.SHORT_PREFIX_STYLE;

    private final VersionManager versionManager;
    private final JiraAuthenticationContext authenticationContext;
    private final SearchService searchService;
    private VelocityRequestContextFactory velocityRequestContextFactory;

    public DaysLeftInVersionResource(final SearchService searchService, final JiraAuthenticationContext authenticationContext, final VelocityRequestContextFactory velocityRequestContextFactory, final VersionManager versionManager)
    {
        this.searchService = searchService;
        this.authenticationContext = authenticationContext;
        this.velocityRequestContextFactory = velocityRequestContextFactory;
        this.versionManager = versionManager;
    }

    @GET
    @Path ("/getVersions")

    public Response getVersionsForProject(@QueryParam ("projectId") String projectIdString)
    {
        Long projectId = Long.valueOf(projectIdString.substring("project-".length()));
        List<Version> versions = getVersionList(projectId);

        final OutlookDate outlookDate = authenticationContext.getOutlookDate();
        long daysRemaining;
        List<VersionInfo> versionList = new ArrayList<VersionInfo>();

        String releaseDate;
        for (Version v : versions){
            releaseDate = formatDate(v.getReleaseDate());
            Project srcProj = v.getProjectObject();
            ProjectInfo targetProj = new ProjectInfo(srcProj.getId(), srcProj.getKey(), srcProj.getName());
            if(releaseDate == ""){
                daysRemaining = 0;
            }
            else {
                daysRemaining = calculateDaysLeftInVersion(v.getReleaseDate());
            }
            versionList.add(new VersionInfo(v.getId(),v.getName(), v.getDescription(),releaseDate,targetProj, daysRemaining));
        }


        return Response.ok(new VersionList(versionList)).cacheControl(NO_CACHE).build();

    }

    public static long calculateDaysLeftInVersion(Date targetDate){
        Date currentDate = new Date(System.currentTimeMillis());
        Date releaseDate = targetDate; //TO DO need to write convert string to date FUNCTION
        long currentTime = currentDate.getTime();
        long targetTime = releaseDate.getTime();

        long remainingTime = targetTime - currentTime;  //remaining time in milliseconds
        long hoursRemaining = remainingTime/(MILLISECONDS_IN_SEC* SECONDS_IN_MIN * MINUTES_IN_DAY);
        long daysRemaining = remainingTime/(MILLISECONDS_IN_SEC* SECONDS_IN_MIN * MINUTES_IN_DAY * HOURS_IN_DAY); //
        if(hoursRemaining % HOURS_IN_DAY > 0 ) {
            daysRemaining++; //the days remaining includes today should be updated for different time z
        }
        return daysRemaining;
    }

    public  String formatDate(Date date){
        if(date == null) {
            return "";
        } else {
            OutlookDate outlookDate = authenticationContext.getOutlookDate();
            return outlookDate.formatDMY(date);
        }
    }
    public List<Version> getVersionList(Long projectId)
    {
        List<Version> versions = new ArrayList<Version>();

        versions.addAll(versionManager.getVersionsUnreleased(projectId, false));

        Collections.sort(versions, new Comparator<Version>()
        {
            public int compare(Version v1, Version v2)
            {
                if(v1.getReleaseDate()== null)
                {
                    return 1;
                }
                else if (v2.getReleaseDate() == null)
                {
                    return 0;
                }
                else {
                    return v1.getReleaseDate().compareTo(v2.getReleaseDate());

                }
            }
        });
        return versions;
    }
    ///CLOVER:OFF


    /**
     * The data structure of the days left in iteration
     * <p/>
     * It contains the a collection of versionData about all the versions of a particular project
     */
    @XmlRootElement
    public static class VersionList
    {
        @XmlElement
        Collection<VersionInfo> versionsForProject;

        @SuppressWarnings ({ "UnusedDeclaration", "unused" })
        private VersionList()
        { }

        VersionList(final Collection<VersionInfo> versionsForProject)
        {
            this.versionsForProject = versionsForProject;
        }

        public Collection<VersionInfo> getVersionsForProject()
        {
            return versionsForProject;

        }

    }
    @XmlRootElement
    public static class ProjectInfo
    {

        @XmlElement
        private long id;

        @XmlElement
        private String key;

        @XmlElement
        private String name;

        @SuppressWarnings ({ "UnusedDeclaration", "unused" })
        private ProjectInfo()
        {}

        ProjectInfo(final long id, String key, String name)
        {
            this.id = id;
            this.key = key;
            this.name = name;
        }

        public long getId()
        {
            return id;
        }

        public String getKey()
        {
            return key;
        }

        public String getName()
        {
            return name;
        }

        @Override
        public int hashCode()
        {
            return HashCodeBuilder.reflectionHashCode(this);
        }

        @Override
        public boolean equals(final Object o)
        {
            return EqualsBuilder.reflectionEquals(this, o);
        }

        @Override
        public String toString()
        {
            return ToStringBuilder.reflectionToString(this, TO_STRING_STYLE);
        }
    }
    @XmlRootElement
    public static class VersionInfo
    {
        @XmlElement
        private long id;

        @XmlElement
        private String name;

        @XmlElement
        private String description;

        @XmlElement
        private String releaseDate;

        @XmlElement
        private long daysRemaining;

        @XmlElement
        private boolean isOverdue;

        @XmlElement
        private ProjectInfo owningProject;


        @SuppressWarnings ({ "UnusedDeclaration", "unused" })
        private VersionInfo()
        { }

        VersionInfo(final long id, final String name, final String description, final String releaseDate,  final ProjectInfo owningProject, final long daysRemaining)
        {
            this.id = id;
            this.name = name;
            this.description = description;
            this.releaseDate = releaseDate;
            this.isOverdue = isOverdue();
            this.owningProject = owningProject;
            this.daysRemaining = daysRemaining;


        }

        public long getId()
        {
            return id;
        }

        public String getName()
        {
            return name;
        }

        public String getDescription()
        {
            return description;
        }

        public String getReleaseDate()
        {
            return releaseDate;
        }

        public long getDaysRemaining()
        {
            return daysRemaining;
        }

        public boolean isOverdue ()
        {
            if (daysRemaining < 0 )
            {
                isOverdue = true;
            }
            else
            {
                isOverdue = false;
            }
            return isOverdue;
        }

        public ProjectInfo getOwningProject()
        {
            return owningProject;
        }
    }
}

Чтобы узнать больше о написании ресурсов REST, ознакомьтесь с руководством по написанию служб REST.

Шаг 6. Добавьте гаджет в панель управления для тестирования.

Если вы еще этого не сделали, создайте новую панель управления для экземпляра JIRA.

Ваш гаджет уже может что-то сделать: он может сказать «Привет, мир!». Протестируйте его, добавив его в JIRA. Вам понадобится разработчик или тестовый экземпляр JIRA, где у вас есть административные разрешения для добавления гаджетов в этот экземпляр:

  1. Перейдите на панель управления JIRA, которую вы создали (или создаете новую), и нажмите «Добавить гаджет».
  2. Появится экран «Добавить гаджет», в котором отображается список гаджетов в вашем каталоге.

Ваш гаджет должен уже появиться в списке, потому что он добавлен как часть плагина.

Нажмите кнопку «Добавить сейчас» под гаджетами, чтобы добавить гаджет в панель управления.

На этом этапе вы можете добавить два или три проекта, каждый из которых содержит несколько версий в JIRA.

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

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

Шаг 7. Создайте режим конфигурации для гаджета

Теперь мы добавим код в раздел «Содержимое» нашего days-left-gadget.xml, чтобы создать режим конфигурации для нашего гаджета.

Добавьте следующий код в раздел содержимого (внутри раздела с надписью <[CDATA [DOCSPRINT: <! --Код для шага 7 идет здесь ->]]>) вашего файла days-left-gadget.xml


       #requireResource("com.atlassian.jira.gadgets:jira-global")
       #includeResources()

       <script type="text/javascript">
            (function ()
            {

                var gadget = AJS.Gadget({
                    baseUrl: "__ATLASSIAN_BASE_URL__",
                    useOauth: "/rest/gadget/1.0/currentUser",
                    config: {
                        descriptor: function(args)
                        {
                            var gadget = this;
                            gadgets.window.setTitle("__MSG_gadget.days.left.configTitle__");
                            var projectPicker = AJS.gadget.fields.projectPicker(gadget, "projectId", args.projectOptions);

                            return {

                                theme : function()
                                {
                                    if (gadgets.window.getViewportDimensions().width < 450)
                                    {
                                        return "gdt top-label";
                                    }
                                    else
                                    {
                                        return "gdt";
                                    }
                                }(),
                                fields: [
                                    projectPicker,
                                    AJS.gadget.fields.nowConfigured()
                                ]
                            };
                        },
                        args: function()
                        {
                            return [
                                {
                                    key: "projectOptions",
                                    ajaxOptions:  "/rest/gadget/1.0/filtersAndProjects?showFilters=false"

                                }


                            ];
                        }()
                    },
                    view: {
                        onResizeAdjustHeight: true,
                        enableReload: true,
                        template: function (args)
                        {
                        <!-- We will add code here in step 9 -->

                        },
                        args: [
                        {
                            key: "versions",
                            ajaxOptions: function ()
                            {
                                return {
                                    url: "/rest/tutorial-gadget/1.0/days-left-in-iteration/getVersions",
                                    data:  {
                                        projectId : gadgets.util.unescapeString(this.getPref("projectId")),
                                    }
                                };
                            }
                        }
                        ]

                    }
                });

            })();

        </script>

Здесь есть несколько важных замечаний:

Следующий раздел - это вызов Ajax, который извлекает список всех ваших проектов JIRA. Этот список будет отображаться в качестве опций в поле выбора проекта.


args: function()
    {
        return [
            {
                key: "projectOptions",
                ajaxOptions:  "/rest/gadget/1.0/filtersAndProjects?showFilters=false"

            }


        ];
    }()

Это строка, которая использует JSON, возвращенный вышеуказанным вызовом Ajax, и создает поле выбора проекта.


var projectPicker = AJS.gadget.fields.projectPicker(gadget, "projectId", args.projectOptions);

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

РИСУНОК

Шаг 8. Добавление CSS в наш гаджет

Теперь мы добавим CSS в наш гаджет. Это будет использоваться в компоненте view(просмотр) нашего гаджета.

Добавьте следующий код в раздел содержимого вашего кода (days-left-gadget.xml) кода css сразу после строки:


#includeResources()

Вот код css, для включения:


       <style type="text/css">
            #container {
                padding:15px;
            }
            #no-versions-warning {
                line-height: 1.4;
                font-size: 12px;
            }
            #days-box {
                text-align: center;
            }
            #days-value {
                text-align: center;
                font-size: 5em;
            }
            #days-text {
                padding-bottom: 15px;
            }
            #version-link {
                text-align: center;
            }
            #no-future-versions-warning {
                padding: 15px;
            }
            .view {
                padding:0.5em 1em;
            }
            .overdue {
                color: #cc0000;
            }
            .future-release {
                color: #00cc00;
            }
            .today {
                color: #cc0000;
            }
            #days-text .today {
                font-weight: bold;
            }

            .icon {
                padding-top: 3px;
                padding-right: 3px;
            }
            .disabled {
                color: #C0C0C0;
            }
        </style>

Шаг 9. Используйте Javascript, чтобы получить Версии в Гаджете

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

Добавьте код ниже в файл days-left-gadget.xml внутри шаблона:

function (args) {} section

(В том месте, где есть сообщение, в котором говорится <! - Мы добавим код здесь на шаге 9 ->)


                        var versionData = args.versions;
                        var currentVersion;
                        var gadget = this;
                        var baseUrl = AJS.$.ajaxSettings.baseUrl;
                        var optionSelected = false;
                        var projectVersionList;

                        if (!versionData)
                        {
                            projectVersionList = null;
                        }
                        else
                        {
                            projectVersionList = AJS.$(args.versions.versionsForProject);
                        }


                        var getContainer = function()
                        {
                            var container = AJS.$("<div/>").attr('id', 'container').appendTo(gadget.getView().empty());
                            return function()
                            {
                                return container;
                            }
                        }();
                        var hasVersionWithReleaseDate = function(projectVersionList)
                        {
                            var hasReleaseDate = false;
                            projectVersionList.each(function()
                            {
                                if (this.releaseDate != "")
                                {
                                    hasReleaseDate = true;
                                }
                            });
                            return hasReleaseDate;
                        };
                        var setTitle = function(projectVersionList)
                        {
                            if (!projectVersionList || !hasVersionWithReleaseDate(projectVersionList))
                            {
                                gadgets.window.setTitle(gadget.getMsg("gadget.days.left.title"));
                            }
                            else
                            {
                                gadgets.window.setTitle(AJS.format("__MSG_gadget.days.left.subtitle__", currentVersion.owningProject.name, currentVersion.name));

                            }
                        };

                        var versionSelector = function(projectVersionList)
                        {
                            var control = AJS.$("<select/>");
                            AJS.$("<option/>").attr({id:'next-release-option', value:'auto'}).text(gadget.getMsg('gadget.days.left.autoOption')).appendTo(control);

                            projectVersionList.each(function()
                            {
                                var option = AJS.$("<option/>").attr({ value:  this.id});
                                if (this.releaseDate == "")
                                {
                                    option.attr("disabled", "true");
                                    option.addClass('disabled');
                                    option.append(this.name + ' - ' + gadget.getMsg('gadget.days.left.noReleaseDate'));
                                }
                                else
                                {
                                    option.append(this.name + ' - ' + this.releaseDate);
                                }

                                if (this.id == gadget.getPref("version"))
                                {
                                    option.attr({selected: "selected"});
                                    currentVersion = this;
                                    optionSelected = true;

                                }
                                control.append(option);
                            });
                            control.change(function(event)
                            {
                                gadget.savePref("version", AJS.$(this).val());
                                gadget.showView(true);
                            });
                            //generate image on side of select bar
                            AJS.$("#selection").append(AJS.$("<img/>").attr({
                                src: baseUrl + "/images/icons/box_16.gif",
                                height: 16,
                                width: 16,
                                title: gadget.getMsg("gadget.roadmap.status.unreleased"),
                                alt: gadget.getMsg("gadget.roadmap.status.unreleased"),
                                class: "icon"
                            }));
                            AJS.$("#selection").append(control);
                            //try auto select option if no option is selected
                            if (!optionSelected)
                            {
                                AJS.$('#next-release-option').attr({selected: "selected"});
                                currentVersion = projectVersionList[0];
                            }
                        };
                        var daysLeftDisplay = function(projectVersionList, container)
                        {
                            var projectLink = baseUrl + "/browse/" + currentVersion.owningProject.key
                            var versionLink = projectLink + "/fixforversion/" + currentVersion.id

                            container.append("<div id ='days-box'/>");
                            AJS.$("<div/>").attr("id", "days-value").appendTo("#days-box");
                            AJS.$("<div/>").attr("id", "days-text").appendTo("#days-box");
                            AJS.$("<div/>").attr("id", "version-link").appendTo("#days-box");

                            AJS.$("<a/>").attr({
                                href: projectLink,
                                id:"projectLink"})
                                    .appendTo('#version-link');

                            AJS.$("#version-link").append(" : ");


                            AJS.$("<a/>").attr({
                                href: versionLink,
                                id: "versionLink"})
                                    .appendTo("#version-link");

                            if (hasVersionWithReleaseDate(projectVersionList))
                            {
                                //if the currentVersion has no release date find the next version due

                                AJS.$("<div/>").attr("id", "days-text").appendTo("#days-box");
                                AJS.$("<div/>").attr("id", "version-link").appendTo("#days-box");

                                AJS.$("#days-value").append(Math.abs(currentVersion.daysRemaining));

                                AJS.$('#projectLink').text(currentVersion.owningProject.name);
                                AJS.$('#versionLink').text(currentVersion.name);

                                AJS.$('<div/ >').attr('id', 'release-date').text(gadget.getMsg("gadget.days.left.releaseDate") + " : " + currentVersion.releaseDate).appendTo('#version-link')

                                if (currentVersion.daysRemaining < 0)
                                {
                                    AJS.$('#days-value').addClass('overdue');
                                    AJS.$('#release-date').addClass('overdue');

                                    AJS.$('#days-text').text(gadget.getMsg("gadget.days.left.daysAgo"))

                                }
                                else if (currentVersion.daysRemaining == 0)
                                {
                                    AJS.$('#days-value').addClass('today');
                                    AJS.$('#release-date').addClass('today');
                                    AJS.$('#days-text').addClass('today').text(gadget.getMsg("gadget.days.left.today"))
                                }
                                else
                                {
                                    AJS.$('#days-value').addClass('future-release');
                                    AJS.$('#release-date').addClass('future-release');

                                    AJS.$('#days-text').text(gadget.getMsg("gadget.days.left.daysRemaining"));

                                }

                            }
                            else
                            {
                                AJS.$('#days-box').empty();

                                var futureVersionsWarning = AJS.$("<div />")
                                        .attr('id', 'no-future-versions-warning')
                                        .text(" - " + gadget.getMsg("gadget.days.left.noReleaseDatesWarning"))
                                        .appendTo('#days-box');

                                AJS.$("<a/>")
                                        .attr({
                                    href: projectLink,
                                    id:"projectLink"})
                                        .text(currentVersion.owningProject.name)
                                        .prependTo(futureVersionsWarning)


                            }
                        };


                        if (!projectVersionList)
                        {
                            var noVersionMsg = gadget.getMsg("gadget.days.left.noVersionWarning");
                            gadget.getView().empty().append((noVersionMsg));

                        }
                        else
                        {

                            var container = getContainer().append("<div id='selection'/>");
                            versionSelector(projectVersionList);
                            daysLeftDisplay(projectVersionList, container);

                            setTitle(projectVersionList);
                        }

                    },

Элемент <Content> в спецификации вашего гаджета содержит рабочие части гаджета.

Элемент <Content> состоит из:

  • Объявление CDATA, чтобы предотвратить синтаксический анализатор XML проанализировать содержимое гаджета. Включите '<! [CDATA [GADGETDEV:' (без кавычек) в начале и ']]>' (без кавычек) в конце вашего элемента <Content>.
  • Необязательный статический HTML. Когда панель инструментов отображает гаджет, он отобразит этот HTML-код.
  • Дополнительный JavaScript. Вы можете объявить функции JavaScript и вызвать их обычным способом. Обратитесь к API-интерфейсу OpenSocial JavaScript для получения подробной информации о функциях API для конкретных гаджетов, которые должен поддерживать любой контейнер-носитель OpenSocial.
  • Дополнительные таблицы стилей CSS.

Поскольку ваш гаджет встроен в плагин, вы можете использовать  гаджеты Atlassian  фрейвока JavaScript  в дополнение к API OpenSocial JavaScript.

Есть несколько важных замечаний:

Функция versionSelector (показана ниже) создает раскрытие всех невыпущенных версий для указанного проекта:


                        var versionSelector = function(projectVersionList)
                        {
                            var control = AJS.$("<select/>");
                            AJS.$("<option/>").attr({id:'next-release-option', value:'auto'}).text(gadget.getMsg('gadget.days.left.autoOption')).appendTo(control);

                            projectVersionList.each(function()
                            {
                                var option = AJS.$("<option/>").attr({ value:  this.id});
                                if (this.releaseDate == "")
                                {
                                    option.attr("disabled", "true");
                                    option.addClass('disabled');
                                    option.append(this.name + ' - ' + gadget.getMsg('gadget.days.left.noReleaseDate'));
                                }
                                else
                                {
                                    option.append(this.name + ' - ' + this.releaseDate);
                                }

                                if (this.id == gadget.getPref("version"))
                                {
                                    option.attr({selected: "selected"});
                                    currentVersion = this;
                                    optionSelected = true;

                                }
                                control.append(option);
                            });
                            control.change(function(event)
                            {
                                gadget.savePref("version", AJS.$(this).val());
                                gadget.showView(true);
                            });
                            //generate image on side of select bar
                            AJS.$("#selection").append(AJS.$("<img/>").attr({
                                src: baseUrl + "/images/icons/box_16.gif",
                                height: 16,
                                width: 16,
                                title: gadget.getMsg("gadget.roadmap.status.unreleased"),
                                alt: gadget.getMsg("gadget.roadmap.status.unreleased"),
                                class: "icon"
                            }));
                            AJS.$("#selection").append(control);
                            //try auto select option if no option is selected
                            if (!optionSelected)
                            {
                                AJS.$('#next-release-option').attr({selected: "selected"});
                                currentVersion = projectVersionList[0];
                            }
                        };

Все части кода с AJS. $, на самом деле jQuery, который в основном используется для форматирования внешнего вида режима просмотра гаджета.

По существу функция versionSelector создает поле выбора, опции которого являются версиями, возвращаемыми вызовом Ajax, определенным в следующем коде.


                     args: [
                        {
                            key: "versions",
                            ajaxOptions: function ()
                            {
                                return {
                                    url: "/rest/tutorial-gadget/1.0/days-left-in-iteration/getVersions",
                                    data:  {
                                        projectId : gadgets.util.unescapeString(this.getPref("projectId")),
                                    }
                                };
                            }
                        }
                    ]

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

РИСУНОК

Функция displayDaysLeftGadget (как показано ниже) отображает количество оставшихся дней в выбранной версии. Дисплей немного меняется в зависимости от того, просрочена или не выпущена версия.


                        var daysLeftDisplay = function(projectVersionList, container)
                        {
                            var projectLink = baseUrl + "/browse/" + currentVersion.owningProject.key
                            var versionLink = projectLink + "/fixforversion/" + currentVersion.id

                            container.append("<div id ='days-box'/>");
                            AJS.$("<div/>").attr("id", "days-value").appendTo("#days-box");
                            AJS.$("<div/>").attr("id", "days-text").appendTo("#days-box");
                            AJS.$("<div/>").attr("id", "version-link").appendTo("#days-box");

                            AJS.$("<a/>").attr({
                                href: projectLink,
                                id:"projectLink"})
                                    .appendTo('#version-link');

                            AJS.$("#version-link").append(" : ");


                            AJS.$("<a/>").attr({
                                href: versionLink,
                                id: "versionLink"})
                                    .appendTo("#version-link");

                            if (hasVersionWithReleaseDate(projectVersionList))
                            {
                                //if the currentVersion has no release date find the next version due

                                AJS.$("<div/>").attr("id", "days-text").appendTo("#days-box");
                                AJS.$("<div/>").attr("id", "version-link").appendTo("#days-box");

                                AJS.$("#days-value").append(Math.abs(currentVersion.daysRemaining));

                                AJS.$('#projectLink').text(currentVersion.owningProject.name);
                                AJS.$('#versionLink').text(currentVersion.name);

                                AJS.$('<div/ >').attr('id', 'release-date').text(gadget.getMsg("gadget.days.left.releaseDate") + " : " + currentVersion.releaseDate).appendTo('#version-link')

                                if (currentVersion.daysRemaining < 0)
                                {
                                    AJS.$('#days-value').addClass('overdue');
                                    AJS.$('#release-date').addClass('overdue');

                                    AJS.$('#days-text').text(gadget.getMsg("gadget.days.left.daysAgo"))

                                }
                                else if (currentVersion.daysRemaining == 0)
                                {
                                    AJS.$('#days-value').addClass('today');
                                    AJS.$('#release-date').addClass('today');
                                    AJS.$('#days-text').addClass('today').text(gadget.getMsg("gadget.days.left.today"))
                                }
                                else
                                {
                                    AJS.$('#days-value').addClass('future-release');
                                    AJS.$('#release-date').addClass('future-release');

                                    AJS.$('#days-text').text(gadget.getMsg("gadget.days.left.daysRemaining"));

                                }

                            }
                            else
                            {
                                AJS.$('#days-box').empty();

                                var futureVersionsWarning = AJS.$("<div />")
                                        .attr('id', 'no-future-versions-warning')
                                        .text(" - " + gadget.getMsg("gadget.days.left.noReleaseDatesWarning"))
                                        .appendTo('#days-box');

                                AJS.$("<a/>")
                                        .attr({
                                    href: projectLink,
                                    id:"projectLink"})
                                        .text(currentVersion.owningProject.name)
                                        .prependTo(futureVersionsWarning)


                            }
                        };

Ваш последний файл days-left-gadget.xml должен выглядеть следующим образом:


<?xml version="1.0" encoding="UTF-8" ?>
<Module>
    <ModulePrefs title="__MSG_gadget.days.left.title__"
                 directory_title="__MSG_gadget.days.left.title__"
                 description="__MSG_gadget.days.left.description__">
        <Require feature="dynamic-height"/>
        <Require feature="oauthpopup"/>
        <Require feature="setprefs"/>
        <Require feature="settitle"/>
        <Require feature="views"/>
        <Optional feature="atlassian.util"/>
        #oauth
        #supportedLocales("gadget.common,gadget.days.left")
    </ModulePrefs>
    <UserPref name="isConfigured" datatype="hidden" default_value="false"/>
    <UserPref name="firstTime" datatype="hidden" default_value="true"/>
    <UserPref name="projectId" datatype="hidden"/>
    <UserPref name="version" datatype="hidden" default_value="auto"/>
    <Content type="html">
    <![CDATA[
        #requireResource("com.atlassian.jira.gadgets:jira-global")
        #includeResources()

        <style type="text/css">
            #container {
                padding:15px;
            }
            #no-versions-warning {
                line-height: 1.4;
                font-size: 12px;
            }
            #days-box {
                text-align: center;
            }
            #days-value {
                text-align: center;
                font-size: 5em;
            }
            #days-text {
                padding-bottom: 15px;
            }
            #version-link {
                text-align: center;
            }
            #no-future-versions-warning {
                padding: 15px;
            }
            .view {
                padding:0.5em 1em;
            }
            .overdue {
                color: #cc0000;
            }
            .future-release {
                color: #00cc00;
            }
            .today {
                color: #cc0000;
            }
            #days-text .today {
                font-weight: bold;
            }

            .icon {
                padding-top: 3px;
                padding-right: 3px;
            }
            .disabled {
                color: #C0C0C0;
            }
        </style>
        <script type="text/javascript">
            (function ()
            {

                var gadget = AJS.Gadget({
                    baseUrl: "__ATLASSIAN_BASE_URL__",
                    useOauth: "/rest/gadget/1.0/currentUser",
                    config: {
                        descriptor: function(args)
                        {

                            var gadget = this;
                            gadgets.window.setTitle("__MSG_gadget.days.left.configTitle__");
                            var projectPicker = AJS.gadget.fields.projectPicker(gadget, "projectId", args.projectOptions);

                            return {

                                theme : function()
                                {
                                    if (gadgets.window.getViewportDimensions().width < 450)
                                    {
                                        return "gdt top-label";
                                    }
                                    else
                                    {
                                        return "gdt";
                                    }
                                }(),
                                fields: [
                                    projectPicker,
                                    AJS.gadget.fields.nowConfigured()
                                ]
                            };
                        },
                        args: function()
                        {
                            return [
                                {
                                    key: "projectOptions",
                                    ajaxOptions:  "/rest/gadget/1.0/filtersAndProjects?showFilters=false"

                                },


                            ];
                        }()
                    },
                    view: {
                        onResizeAdjustHeight: true,
                        enableReload: true,
                        template: function (args)
                        {
                            var versionData = args.versions
                            var currentVersion;
                            var gadget = this;
                            var baseUrl = AJS.$.ajaxSettings.baseUrl;
                            var optionSelected = false;
                            var projectVersionList;

                            if(!versionData) {
                                projectVersionList = null;
                            } else {
                                projectVersionList = AJS.$(args.versions.versionsForProject);
                            }


                            var getContainer = function() {
                                var container = AJS.$("<div/>").attr('id', 'container').appendTo(gadget.getView().empty());
                                return function() {
                                    return container;
                                }
                            }();
                            var hasVersionWithReleaseDate = function(projectVersionList) {
                                var hasReleaseDate = false;
                                projectVersionList.each(function()
                                {
                                    if(this.releaseDate != "") {
                                        hasReleaseDate = true;
                                    }
                                });
                                return hasReleaseDate;
                            };
                            var setTitle = function(projectVersionList) {
                                if(!projectVersionList  || !hasVersionWithReleaseDate(projectVersionList))
                                {
                                    gadgets.window.setTitle(gadget.getMsg("gadget.days.left.title"));
                                }
                                else
                                {
                                    gadgets.window.setTitle(AJS.format("__MSG_gadget.days.left.subtitle__", currentVersion.owningProject.name, currentVersion.name));

                                }
                            };

                            var versionSelector = function(projectVersionList)
                            {
                                var control = AJS.$("<select/>");
                                AJS.$("<option/>").attr({id:'next-release-option', value:'auto'}).text(gadget.getMsg('gadget.days.left.autoOption')).appendTo(control);

                                projectVersionList.each(function()
                                {
                                    var option = AJS.$("<option/>").attr({ value:  this.id});
                                    if (this.releaseDate == "")
                                    {
                                        option.attr("disabled", "true");
                                        option.addClass('disabled');
                                        option.append(this.name + ' - ' + gadget.getMsg('gadget.days.left.noReleaseDate'));
                                    }
                                    else
                                    {
                                        option.append(this.name + ' - ' + this.releaseDate);
                                    }

                                    if (this.id == gadget.getPref("version"))
                                    {
                                        option.attr({selected: "selected"});
                                        currentVersion = this;
                                        optionSelected = true;

                                    }
                                    control.append(option);
                                });
                                control.change(function(event)
                                {
                                    gadget.savePref("version", AJS.$(this).val());
                                    gadget.showView(true);
                                });
                                //generate image on side of select bar
                                AJS.$("#selection").append(AJS.$("<img/>").attr({
                                    src: baseUrl + "/images/icons/box_16.gif",
                                    height: 16,
                                    width: 16,
                                    title: gadget.getMsg("gadget.roadmap.status.unreleased"),
                                    alt: gadget.getMsg("gadget.roadmap.status.unreleased"),
                                    class: "icon"
                                }));
                                AJS.$("#selection").append(control);
                                //try auto select option if no option is selected
                                if(!optionSelected) {
                                    AJS.$('#next-release-option').attr({selected: "selected"});
                                    currentVersion = projectVersionList[0];
                                }
                            };
                            var daysLeftDisplay = function(projectVersionList, container)
                            {
                                var projectLink = baseUrl + "/browse/" + currentVersion.owningProject.key
                                var versionLink = projectLink + "/fixforversion/" + currentVersion.id

                                container.append("<div id ='days-box'/>");
                                AJS.$("<div/>").attr("id", "days-value").appendTo("#days-box");
                                AJS.$("<div/>").attr("id", "days-text").appendTo("#days-box");
                                AJS.$("<div/>").attr("id", "version-link").appendTo("#days-box");

                                AJS.$("<a/>").attr({
                                        href: projectLink,
                                        id:"projectLink"})
                                    .appendTo('#version-link');

                                AJS.$("#version-link").append(" : ");


                                AJS.$("<a/>").attr({
                                        href: versionLink,
                                        id: "versionLink"})
                                    .appendTo("#version-link");

                                if(hasVersionWithReleaseDate(projectVersionList)) {
                                    //if the currentVersion has no release date find the next version due

                                    AJS.$("<div/>").attr("id", "days-text").appendTo("#days-box");
                                    AJS.$("<div/>").attr("id", "version-link").appendTo("#days-box");

                                    AJS.$("#days-value").append(Math.abs(currentVersion.daysRemaining));

                                    AJS.$('#projectLink').text(currentVersion.owningProject.name);
                                    AJS.$('#versionLink').text(currentVersion.name);

                                    AJS.$('<div/ >').attr('id', 'release-date').text(gadget.getMsg("gadget.days.left.releaseDate") + " : " + currentVersion.releaseDate).appendTo('#version-link')

                                    if (currentVersion.daysRemaining < 0)
                                    {
                                        AJS.$('#days-value').addClass('overdue');
                                        AJS.$('#release-date').addClass('overdue');

                                        AJS.$('#days-text').text(gadget.getMsg("gadget.days.left.daysAgo"))

                                    }
                                    else if (currentVersion.daysRemaining == 0 )
                                    {
                                        AJS.$('#days-value').addClass('today');
                                        AJS.$('#release-date').addClass('today');
                                        AJS.$('#days-text').addClass('today').text(gadget.getMsg("gadget.days.left.today"))
                                    }
                                    else
                                    {
                                        AJS.$('#days-value').addClass('future-release');
                                        AJS.$('#release-date').addClass('future-release');

                                        AJS.$('#days-text').text(gadget.getMsg("gadget.days.left.daysRemaining"));

                                    }

                                }
                                else {
                                    AJS.$('#days-box').empty();

                                    var futureVersionsWarning = AJS.$("<div />")
                                        .attr('id', 'no-future-versions-warning')
                                        .text(" - " + gadget.getMsg("gadget.days.left.noReleaseDatesWarning"))
                                        .appendTo('#days-box');

                                    AJS.$("<a/>")
                                        .attr({
                                    href: projectLink,
                                    id:"projectLink"})
                                        .text(currentVersion.owningProject.name)
                                        .prependTo(futureVersionsWarning)


                                }
                           };


                            if(!projectVersionList)
                            {
                                var noVersionMsg = gadget.getMsg("gadget.days.left.noVersionWarning");
                                gadget.getView().empty().append((noVersionMsg));

                            }
                            else
                            {

                                var container = getContainer().append("<div id='selection'/>");
                                versionSelector(projectVersionList);
                                 daysLeftDisplay(projectVersionList, container);

                                setTitle(projectVersionList);
                            }

                        },
                        args: [
                            {
                                key: "versions",
                                ajaxOptions: function ()
                                {
                                    return {
                                        url: "/rest/tutorial-gadget/1.0/days-left-in-iteration/getVersions",
                                        data:  {
                                            projectId : gadgets.util.unescapeString(this.getPref("projectId")),
                                        }
                                    };
                                }
                            }
                        ]

                    }
                });
            })();

        </script>
  ]]>
  </Content>
</Module>

Шаг 10. Создание, установка и запуск плагина.

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

  • Откройте командное окно и перейдите в корневую папку плагина (где находится pom.xml).
  • Запустите atlas-run (или atlas-debug, если вы захотите запустить отладчик в своей среде IDE).

С этого момента вы можете использовать QuickReload для переустановки своего плагина за сценой, когда работаете, просто перестроив свой плагин.

 

FastDev и atlas-cli устарели. Вместо этого используйте автоматическую переустановку плагинов с помощью QuickReload.

Чтобы запустить переустановку вашего плагина:

  1. Внесите изменения в модуль плагина.
  2. Откройте панель инструментов разработчика.

РИСУНОК

  1. Нажмите значок FastDev.

Система перестраивает и перезагружает ваш плагин:

РИСУНОК

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

  1. Откройте панель инструментов разработчика.
  2. Нажмите значок живой перезагрузки.

Значок начинает вращаться, показывая, что он включен.

  1. Отредактируйте ресурсы проекта.
  2. Сохраните изменения:

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

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

Полные инструкции приведены в руководстве SDK.

Шаг 11. Написание модуля тестов

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

Шаг 12. Проверьте свои обновления на панели инструментов.

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

Если выбранная версия не была выпущена(релиз) и просрочена, в гаджете отображается количество дней, когда версия просрочена красным цветом. Текст под номером - «Дни назад».

РИСУНОК

Если выбранная версия должна появиться сегодня, тогда число отображается, когда количество дней отображается красным цветом, а текст под номером «Сегодня!».

РИСУНОК

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

РИСУНОК

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

РИСУНОК

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

РИСУНОК

Поздравляем, вы завершили этот урок.

 

ПОХОЖИЕ ТЕМЫ

Упаковка вашего гаджета в качестве  плагина Atlassian

По материалам Atlassian JIRA  Server Developer Writing a gadget that shows days left in a version