Category Archives: Atlassian

Creating a dynamic menu in an Atlassian plugin

When you want to generate a menu item for your Jira plugin you can use the following sample. This enables you to group menu item [this separates them with a horizontal line] and enables you to specify conditions on when menu items should be shown.

The below code will present the following menu item.

Schermafbeelding 2014-05-11 om 19.03.02

Step 1: Specify web-section, web-item and simple-link-factory in the atlassian-plugin.xml
The web-item on the root level gets the location “system.top.navigation.bar”. This places the menu item in the top menu bar of Jira. In this example it is called “Planning”.

This root level web-item consists of web-section items and web-items, and are referred to via the attribute “location”. A web-section can refer to a simple-link-factory, which is a Java class in which logic can be entered when menu item should or should not be shown.

    <web-section key="menu_section_recent" name="Menu Section" location="menu_link" weight="10">
        <label key="menu.forecast.recent"/>
    </web-section>

    <web-section key="menu_section_favourite" name="Menu Section" location="menu_link" weight="10">
        <label key="menu.forecast.favourite"/>
    </web-section>

    <web-section key="menu_section_general" name="Menu Section" location="menu_link" weight="10"/>

    <web-item key="menu_link" name="Instances" section="system.top.navigation.bar" weight="47">
        <label key="menu.forecast.planning"/>
        <link linkId="menu_link">http://www.gjdb.nl</link>
    </web-item>

    <web-item key="manage_forecasts_link" name="Overview of all forecasts" section="menu_link/menu_section_general" weight="10">
        <label key="menu.forecast.manage"/>
        <link linkId="manage_forecasts_link">/plugins/servlet/planning-forecast/viewForecasts</link>
    </web-item>

    <web-item key="started_link" name="Getting started" section="menu_link/menu_section_general" weight="10">
        <label key="menu.forecast.start"/>
        <link linkId="started_link">/plugins/servlet/planning-forecast/start</link>
    </web-item>

    <!-- dynamic web item -->
    <simple-link-factory key="dynamic-factory-current" name="Dynamic Link Factory" section="menu_link/menu_section_current"
                         i18n-name-key="Dynamic menu" weight="30" lazy="true"
                         class="com.gjdb.plugins.jira.factory.DynamicLinkFactory"/>

    <simple-link-factory key="dynamic-factory-recent" name="Dynamic Link Factory" section="menu_link/menu_section_recent"
                         i18n-name-key="Dynamic menu" weight="30" lazy="true"
                         class="com.gjdb.plugins.jira.factory.DynamicLinkFactory"/>

    <simple-link-factory key="dynamic-factory-favourite" name="Dynamic Link Factory" section="menu_link/menu_section_favourite"
                         i18n-name-key="Dynamic menu" weight="30" lazy="true"
                         class="com.gjdb.plugins.jira.factory.DynamicLinkFactory"/>

Step 2: Implement the simple-link-factory

package com.gjdb.plugins.jira.factory;

import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.plugin.webfragment.SimpleLinkFactory;
import com.atlassian.jira.plugin.webfragment.descriptors.SimpleLinkFactoryModuleDescriptor;
import com.atlassian.jira.plugin.webfragment.model.SimpleLink;
import com.atlassian.jira.plugin.webfragment.model.SimpleLinkImpl;
import com.gjdb.plugins.jira.model.Forecast;
import com.gjdb.plugins.jira.repository.ForecastRepository;
import com.gjdb.plugins.jira.util.UserUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author GJDB
 */
public class DynamicLinkFactory implements SimpleLinkFactory {

    private String section;

    public DynamicLinkFactory() {

    }

    /* (non-Javadoc)
         * @see com.atlassian.jira.plugin.webfragment.SimpleLinkFactory#getLinks(com.atlassian.crowd.embedded.api.User, java.util.Map)
         */
    @Override
    public List<SimpleLink> getLinks(User user, Map<String, Object> map) {
        List<SimpleLink> links = new ArrayList<SimpleLink>();

        if("menu_link/menu_section_recent".equals(section)) {
            links.add(new SimpleLinkImpl("recent-" + key, forecast.getDescription(), forecast.getDescription(), null, null, "/jira/plugins/servlet/planning-forecast/instance?key=" + key, null));
            }

        <logic for other menu sections removed>

        return links;
    }

    /* (non-Javadoc)
     * @see com.atlassian.jira.plugin.webfragment.SimpleLinkFactory#init(com.atlassian.jira.plugin.webfragment.descriptors.SimpleLinkFactoryModuleDescriptor)
     */
    @Override
    public void init(SimpleLinkFactoryModuleDescriptor simDescriptor) {
        section = simDescriptor.getSection();
    }

}

Create a Rest service in a Jira Atlassian plugin

Traditionally a user fills in a form in a webpage and has to actively press a save button in order to have the data stored. This has two main disadvantages: 1) this communication is normally done via http, which is much slower then a rest service cause it has much more overhead 2) a user nowadays expects direct feedback and expects a website to store data actively by itself.

Herefore a restfull service can be used, the following article describes how to enable a rest service within your own Jira Atlassian plugin.

Step 1: add a rest declaration to the atlassian-plugin.xml
The path element sets the context root to which all rest resources for your plugin will listen.

    <rest key="rest" path="/planning-forecast-overview" version="1.0">
        <description>Provides REST resources for the overview UI.</description>
    </rest>

So, by the end of this example the rest service can be called as followed:
Schermafbeelding 2014-05-11 om 19.49.36

Step 2: Have a script trigger a rest call to the server
The following script is normally triggered by a click event, and sends a rest call to the server.

    function upsertClaim(value, span) {
        var spanId = span.getAttribute("id");
        var ids = spanId.split("-+-");

        AJS.$.ajax({
            url: baseUrl + "/rest/planning-forecast-overview/1.0/upsertClaim?issueId=" + ids[0] + "&periodId="+ ids[1]
                + "&value=" + value + "&forecastKey=" + getRequestParam("key"),
            dataType: "json",
            success: function(filter) {
                setSpanValue(span, value);
            }
        });

    }

Step 3: Add a Java class to process the restfull calls.
This particular example does not sent a response object, but this is also possible. This response would be in the “success” variable as a parameter.

package com.gjdb.plugins.jira.resources;

import com.atlassian.sal.api.transaction.TransactionCallback;
import com.atlassian.sal.api.transaction.TransactionTemplate;
import com.gjdb.plugins.jira.model.Claim;
import com.gjdb.plugins.jira.model.ClaimStatus;
import com.gjdb.plugins.jira.model.Forecast;
import com.gjdb.plugins.jira.repository.ForecastRepository;
import com.gjdb.plugins.jira.util.UserUtil;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

/**
 * @author GJDB
 */
@Path("/upsertClaim")
public class UpsertClaimResource {

    private final TransactionTemplate transactionTemplate;

    public UpsertClaimResource(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response get(@Context final HttpServletRequest request) {
        if(!UserUtil.allowAccess(request, UserUtil.getAdminUserGroups())) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        return Response.ok(transactionTemplate.execute(new TransactionCallback() {
            public Object doInTransaction() {
            final String issueId = request.getParameter("issueId");
           
            <logic removed for this blog>
 
            return null;
            }
        })).build();
    }
  
}

Result
When you monitor the traffic between the browser and the server, you should see something like the following when on a [click] event a rest call is being performed. Note that a 200 response means it is processed without errors.

Schermafbeelding 2014-05-11 om 19.50.04