issue_filters-jql

JIRA Plugins: JQL Functions in JIRA

JIRA is a complete project management tool that support complex queries and filters for tracking and reporting. In this blog entry we will show how to develop your custom JQL functions to extend the long list of built-in functions included from version 4.x.

¿What is JQL?

JQL (JIRA Query Language) is a query language for JIRA’s data model very similar to SQL that allows complex searching from version 4.0. A JQL query is made up of a logic combination (OR, AND) of conditions in the form field + operator + operands. The operands can be one or more values or a JQL function (returning one or a list of values). For instance:

project = "New office" AND status = "open"

This JQL query returns all issues in project “New office” in status “open”. The next exaple shows the use of a JQL function:

assignee != currentUser()

Which returns all issues assigned to a user different from the one executing the query, returned by the JQL function currentUser().

¿Can I extend the JQL sintax?

It is not possible to create new operators. JQL can use any custom field, though. And we can create new JQL functions that provide complex operands that cannot be expressed with the built-in logic. In the next lines we will try to show you how to create a JQL function to display all blocked issues (identified in JIRA by the ‘blocks‘ issue linking type).

JQL Functions in a plugin

In order to use our JQL function in JIRA we will create a plugin as described in our previuos entry: Atlassian plugins with eclipse & maven.

1. Create base class BlockedIssuesJqlFunction.java

Every JQL function must implement the interface JqlFunction provided by JIRA’s API. For the sake of simplicity we will use an abstract class that provides common functionality: AbstractJqlFunction. The following methods are abstract and needs to be implemented:

  • getMinimumNumberOfExpectedArguments(): returns the number of arguments our JQL function needs, in out example this is 0.
  • getDataType(): returns the function’s return type, for issues this should be JiraDataTypes.ISSUE.
  • validate(…): validates arguments passed to our JQL function.
  • getValues(): returns a list of values as objects of class QueryLiteral.

So now we create the class BlockedIssuesJqlFunction.java to extend AbstractJqlFunction:

public class BlockedIssuesJqlFunction extends AbstractJqlFunction {

    @Override
    public JiraDataType getDataType() {
        return JiraDataTypes.ISSUE;
    }

    @Override
    public int getMinimumNumberOfExpectedArguments() {
        return 0;
    }

    @Override
    public List<QueryLiteral> getValues(
        QueryCreationContext queryCreationContext, FunctionOperand operand,
	TerminalClause terminalClause) {
	// TODO Auto-generated method stub
	return null;
    }

    @Override
    public MessageSet validate(User searcher, FunctionOperand operand,
	TerminalClause terminalClause) {
	// TODO Auto-generated method stub
	return null;
    }
}

 2. Now Implement validate(…)

In the method validate(…) we need to check arguments passed to our Jql function, generate error messages (using an object of class MessageSet) if needed and store the values of the arguments to parameterize our search. In our example we will check that the following conditions apply:

  1. JIRA’s issue linking is enabled.
  2. There is at least one issue using ‘is blocked by’ linking type. We will use object of class IssueLinkTypeManager and IssueLinkManager provided during construction.
public class BlockedIssuesJqlFunction extends AbstractJqlFunction {

    private final IssueLinkTypeManager issueLinkTypeManager;
    private final IssueLinkManager issueLinkManager;
    private final static String LINK_NAME = "is blocked by";

    public BlockedIssuesJqlFunction(IssueLinkManager issueLinkManager,
    		IssueLinkTypeManager issueLinkTypeManager) {
		super();
		this.issueLinkTypeManager = issueLinkTypeManager;
		this.issueLinkManager = issueLinkManager;
	}

	@Override
	public MessageSet validate(User searcher, FunctionOperand operand,
			TerminalClause terminalClause) {
        MessageSet messageSet = new MessageSetImpl();
        if (!issueLinkManager.isLinkingEnabled())
        {
            messageSet.addErrorMessage("Invalid argument for " + getFunctionName());
            return messageSet;
        }

        final List<String> args = operand.getArgs();
        if (args.isEmpty())
        {
            messageSet.addErrorMessage("Invalid argument for " + getFunctionName());
            return messageSet;
        }

        return messageSet;
    }
...
}

 3. getValues(…): Return candidates as literals

Using a similar code we will retrieve all issues with a linking of type “is blocked by”. Finally, convert issue objects into literals (objects of class QueryLiteral).

@Override
public List<QueryLiteral> getValues(
        QueryCreationContext queryCreationContext, FunctionOperand operand,
	TerminalClause terminalClause) {
    notNull("queryCreationContext", queryCreationContext);
    final List<QueryLiteral> literals = new LinkedList<QueryLiteral>();

    Collection<IssueLinkType> linkTypes = issueLinkTypeManager.getIssueLinkTypesByOutwardDescription(LINK_NAME);
    if (
         null == linkTypes ||
         linkTypes.isEmpty() ||
         null == issueLinkManager.getIssueLinks(linkTypes.iterator().next().getId())
       ) {
           return null;
    }

    Set<Issue> linkedIssues = new LinkedHashSet<Issue>();
    for (IssueLinkType linkType : linkTypes)
    {
        List<Issue> issues = new LinkedList<Issue>();
        Collection<IssueLink> links = issueLinkManager.getIssueLinks(linkType.getId());
        for (IssueLink link: links) {
            issues.add(link.getSourceObject());
        }

        if (issues != null)
        {
            linkedIssues.addAll(issues);
        }
    }
    for (Issue issue : linkedIssues)
    {
        literals.add(new QueryLiteral(operand, issue.getId()));
    }

    return literals;
}

4. Plugin descriptor

In order for our class to be available in JIRA we must package it in a JIRA plugin with the appropiate OSGi descriptor. The following lines should be included in the file src/main/resources/atlassian-plugin.xml.

<jql-function key="blocked-issues" name="Blocked Issues Function"
  class="com.novagenia.jira.plugins.jql.function.BlockedIssuesJqlFunction">
  <!--The name of the function-->
  <fname>blockedIssues</fname>
  <description>Provides a JQL function to returned issues with is blocked by link.</description>
  <!--Whether this function returns a list or a single value-->
  <list>true</list>
</jql-function>

Now you can deploy the plugin in JIRA and use the JQL function as follows:

issue in blockedIssues()

And you should see some like…

The final touch

Some suggestions to enhance your code.

1. Add log traces

There are several ways to debug JIRA plugins but you should, at least, provide runtime log traces to easy debugging and maintenance. By adding the right entries in the file log4j.properties we can adjust the verbose level of our our plugins from JIRA administration interface.

This is how we create an object to use logs from code:

private final Logger log = Logger.getLogger(BlockedIssuesJqlFunction.class);

Calls to this object should specify the severity of the trace:

log.warn("Linking is not enabled!");

2. Refactor to use more linking types

With little effort we can extend the functionality in our previous example to make it reusable. By using eclipse’s built-in refactoring tecnique “Extract Superclass” we will create a superclass with the linking type as a parameter.

Now we add the new JQL function in the plugin descriptor:

<jql-function key="linked-issues" name="Linked Issues With Function">
    <!--The name of the function-->
    <fname>linkedIssuesWith</fname>
    <description>Provides a JQL function to get all issues with an outward link.</description>
    <!--Whether this function returns a list or a single value-->
    <list>true</list>
</jql-function>

3. Keep it private (sanitize)

To end with, we would like to talk about a mechanism (sanitising) that ensures that parameterized JQL functions keep information private. Although not mandatory, it is highly convenient if your JQL function is accesible from different projects. The goal is to keep all information regarding fields and values private. This is important when using a JQL function is filters that might be shared with people in different projects and, probably, with different security or visibility levels. If you think this is the case with your Jql fucntion then you may need to implement the interface ClauseSanitisingJqlFunction.

You will find more details in the following link.

Share

The code included in this entry (as well as examples for all entries in The manifest), can be accessed from bitbucket.org: http://git.novagenia.com.

Any comment that helps us improve would be hilghly appreciated.

Best regards.

Author: Eduardo Mayor

Eduardo Mayor is a software engineer with 20+ years of experience. He is also the founder of Novagenia Information Technologies, a company focused on introducing agile methods and tools to companies worldwide.

Visit us in: http://www.novagenia.com
Share this...

7 thoughts on “JIRA Plugins: JQL Functions in JIRA

  1. Admiring the persistence you put into your site and in depth information you provide. It’s good to come across a blog every once in a while that isn’t the same outdated rehashed information. Wonderful read! I’ve bookmarked your site and I’m including your RSS feeds to my Google account.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>