I often can see questions about developing a Jira plugin with Jira Software functionality. Like other people I can not find much information about it in the Internet. That is why I decided to make a short tutorial, where I would get all issues in an Epic and add issues to an Epic using Jira Software services.
Open teminal and run the following command:
atlas-create-jira-plugin
Terminal will ask you a couple of questions. Answer the following way:
Define value for groupId: : ru.matveev.alexey.sw.tutorial
Define value for artifactId: : sw-tutorial
Define value for version: 1.0.0-SNAPSHOT: :
Define value for package: ru.matveev.alexey.sw.tutorial: :
Confirm properties configuration:
groupId: ru.matveev.alexey.sw.tutorial
artifactId: sw-tutorial
version: 1.0.0-SNAPSHOT
package: ru.matveev.alexey.sw.tutorial
Y: : Y
Change jira.version property to 7.9.0
<jira.version>7.9.0</jira.version>
Add lines below to the maven-jira-plugin in the configuration tag:
<applications>
<application>
<applicationKey>jira-software</applicationKey>
<version>${jira.software.application.version}</version>
</application>
</applications>
Add jira.software.application.version property to the property tag:
<jira.software.application.version>7.9.0</jira.software.application.version>
These lines will let us launch Jira Software with the atlas-run command.
Increase JVM memory adding to the maven-jira-plugin in the configuration tag:
<jvmArgs>-Xms512M -Xmx1g</jvmArgs>
In my runtime environment Jira did not start without adding more memory to Jira.
Open terminal and run
atlas-create-jira-plugin-module
Answer the following way to the questions:
Choose a number (1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34): 14
Enter New Classname MyRestResource: :
Enter Package Name ru.matveev.alexey.sw.tutorial.rest: :
Enter REST Path /myrestresource: :
Enter Version 1.0: :
Show Advanced Setup? (Y/y/N/n) N: : NAdd Another Plugin Module? (Y/y/N/n) N: : N
Open terminal and run:
atlas-run
After Jira started, you will be able to see the target folder in your plugin directory. Go to the target/jira/home/plugins/installed-plugins and look for jira-greenhopper-plugin. In my case the name of the file looks like this:
jira-greenhopper-plugin-7.9.0-DAILY20180326142825.jar
It means, that the version of the jira-greenhopper-plugin is 7.9.0-DAILY20180326142825. Let's add this dependency to the pom.xml. Add to the dependencies tags the following lines:
<dependency>
<groupId>com.atlassian.jira.plugins</groupId>
<artifactId>jira-greenhopper-plugin</artifactId>
<version>7.9.0-DAILY20180326142825</version>
<scope>provided</scope>
</dependency>
Ok. we added the required dependency. Now we have to find out how we can work with Jira Software Services.
Jira uses OSGI, that is why each plugin exports services, which can be used by other plugins. Let's see what Jira Software exports. Open the following link in your browser:
http://localhost:2990/jira/plugins/servlet/upm#osgi
My screen looks like this:
Then find the Atlassian Greenhopper plugin and open the Registered Services menu:
Look at the services and you will find the required by us services:
Service 1477 com.atlassian.greenhopper.service.issue.RapidViewIssueService
Service 1476 com.atlassian.greenhopper.service.issuelink.EpicService
Delete the sw-tutorial/src/test/java/ut/ru/matveev/alexey/sw/tutorial/rest/MyRestResourceTest.java file.
Change sw-tutorial/src/main/java/ru/matveev/alexey/sw/tutorial/rest/MyRestResource.java file to:
package ru.matveev.alexey.sw.tutorial.rest;
import com.atlassian.greenhopper.model.Epic;
import com.atlassian.greenhopper.service.Page;
import com.atlassian.greenhopper.service.PageRequests;
import com.atlassian.greenhopper.service.ServiceOutcome;
import com.atlassian.greenhopper.service.issue.RapidViewIssue;
import com.atlassian.greenhopper.service.issue.RapidViewIssueService;
import com.atlassian.greenhopper.service.issuelink.EpicService;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.jql.builder.JqlQueryBuilder;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.user.ApplicationUser;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
import com.atlassian.query.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* A resource of message.
*/
@Path("/message")
public class MyRestResource {
private static final Logger LOG = LoggerFactory.getLogger(MyRestResource.class);
private final EpicService epicService;
private final IssueManager issueManager;
private final JiraAuthenticationContext jiraAuthenticationContext;
private final RapidViewIssueService rapidViewIssueService;
@Inject
public MyRestResource(@ComponentImport EpicService epicService,
@ComponentImport IssueManager issueManager,
@ComponentImport JiraAuthenticationContext jiraAuthenticationContext,
@ComponentImport RapidViewIssueService rapidViewIssueService) {
this.epicService = epicService;
this.issueManager = issueManager;
this.jiraAuthenticationContext = jiraAuthenticationContext;
this.rapidViewIssueService = rapidViewIssueService;
}
@Path("/hello")
@GET
@AnonymousAllowed
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response getMessage()
{
return Response.ok(new MyRestResourceModel("Hello World")).build();
}
@Path("/epictasks")
@GET
@AnonymousAllowed
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response getEpicTasks(@QueryParam("epic key") String epicKey)
{
ApplicationUser user = jiraAuthenticationContext.getLoggedInUser();
ServiceOutcome<Epic> epic = epicService.getEpic(user, epicKey);
Query query = JqlQueryBuilder.newBuilder().where().buildQuery();
ServiceOutcome<Page<RapidViewIssue>> issues = rapidViewIssueService.getIssuesForEpic(user, epic.getValue(), PageRequests.request(0L, 100), query);
List<String> issueList = issues.getValue().getValues().stream().map(el -> el.getIssue().getSummary()).collect(Collectors.toList());
return Response.ok(new MyRestResourceModel(issueList.toString())).build();
}
@Path("/epictasks")
@POST
@AnonymousAllowed
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response addTaskToEpic(@QueryParam("epic key") String epicKey,
@QueryParam("issue key") String issueKey)
{
ApplicationUser user = jiraAuthenticationContext.getLoggedInUser();
ServiceOutcome<Epic> epic = epicService.getEpic(user, epicKey);
Set<Issue> issueSet = new HashSet<>();
issueSet.add(issueManager.getIssueByCurrentKey(issueKey));
epicService.addIssuesToEpic(user, epic.getValue(), issueSet);
return Response.ok(new MyRestResourceModel(issueKey + " added to " + epicKey)).build();
}
}
The idea of this code to show how you can use Jira Software services, that is why the code is very raw and prone to NPE. Kindly do not pay attention to it.
I added a rest call called epictasks. If you execute epictasks with the GET method and pass the issue key of an epic, then you will see all issues, which are linked to the epic. If you execute epictaks with the POST method and pass an epic key and an issue key, then the issue will be linked to the epic.
You can create a Jira Software project, create an epic and create a task and then link them, using the REST calls.
For example, let 's say I have an epic with SW-1 key and an issue with SW-5 key. First I execute the GET method in the REST Browser and I can see the following result:
You can see that there is no issues linked to the epic yet. Then I execute the POST method:
You can see that the issue with the SW-5 key was added to the epic. Now we can execute the GET method and see the result:
We can see that the issue was added to the epic.
We accomplished our goals.
You can find the code of this tutorial here:
Alexey Matveev
software developer
MagicButtonLabs
Philippines
1,574 accepted answers
2 comments