How to create a custom script JQL function with two parameters?

SWAPNIL SRIVASTAV
Contributor
January 2, 2022

I want to create a custom script JQL function with two parameters

name of function: childFeaturesOf

first parameter: JQL subquery

second parameter: a list of issuetypes

and I want that the 2nd parameter is not mandatory. If we just give the first parameter, it should return issues of all issuetypes

I want to use it as below:

issueFunction in childFeaturesOf("'parent business idea' = ABC-1 and issuetype = Package", (Feature, 'Package Unit'))

Kindly help me how to achieve this. This is the existing script:

Main script: 

package com.onresolve.jira.groovy.jql

import com.atlassian.jira.jql.query.QueryCreationContext
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.util.MessageSet
import com.atlassian.query.clause.TerminalClause
import com.atlassian.query.operand.FunctionOperand
import groovy.util.logging.Log4j
import org.apache.lucene.search.Query

@Log4j
class P3_ChildFeaturesOfJqlFunction extends AbstractScriptedJqlFunction implements JqlQueryFunction {

    P3_ChildIssueJqlFunctionHelper jqlFunctionHelper = new P3_ChildIssueJqlFunctionHelper(log)

    public static final String TEMPLATE_QUERY =
        "\"Parent Package Picker\" in ({0}) and issuetype in ({1})"

    @Override
    String getDescription() {
        "Returns the Features that have the issue(s) returned by the provided JQL-query in field 'Parent Package Picker'"
    }

    @Override
    MessageSet validate(ApplicationUser user, FunctionOperand operand, TerminalClause terminalClause) {
        log.error("Validating the JQL-function 'childFeaturesOf()' with input parameter(s) '" + operand.args + "'")
        jqlFunctionHelper.validate(user, operand, terminalClause, TEMPLATE_QUERY, getI18n())
    }

    @Override
    List<Map> getArguments() {
        [
            [
                description: "JQL-query for which the child Features should be returned",
                optional   : false,
            ],
            [
                description: "Issuetype which should be returned",
                optional   : false,
            ]
        ]
    }

    @Override
    String getFunctionName() {
        "childFeaturesOf"
    }

    @Override
    Query getQuery(QueryCreationContext queryCreationContext, FunctionOperand operand, TerminalClause terminalClause) {
        log.debug("Expanding the JQL-function 'childFeaturesOf()' with input parameter(s) '" + operand.args + "'")
        jqlFunctionHelper.getQuery(queryCreationContext, operand, terminalClause, TEMPLATE_QUERY)
    }
}
Script for file P3_ChildIssueJQLFunctionHelper.groovy:
package com.onresolve.jira.groovy.jql

import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.jql.query.LuceneQueryBuilder
import com.atlassian.jira.jql.query.QueryCreationContext
import com.atlassian.jira.jql.validator.NumberOfArgumentsValidator
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.util.I18nHelper
import com.atlassian.jira.util.MessageSet
import com.atlassian.jira.util.MessageSetImpl
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.query.clause.TerminalClause
import com.atlassian.query.operand.FunctionOperand
import org.apache.log4j.Category
import org.apache.lucene.search.Query

import java.text.MessageFormat

class P3_ChildIssueJqlFunctionHelper {

    Category log

    P3_ChildIssueJqlFunctionHelper(Category log) {
        this.log = log
    }

    JqlQueryParser queryParser = ComponentAccessor.getComponent(JqlQueryParser)
    LuceneQueryBuilder luceneQueryBuilder = ComponentAccessor.getComponent(LuceneQueryBuilder)
    SearchService searchService = ComponentAccessor.getComponent(SearchService)

    MessageSet validate(ApplicationUser user, FunctionOperand operand, TerminalClause terminalClause, String templateQuery, I18nHelper i18Helper) {
        def messageSet = new NumberOfArgumentsValidator(1, 1, i18Helper).validate(operand)
        if (messageSet.hasAnyErrors()) {
            return messageSet
        }

        def subqueryStr = operand.args.first()
        log.error "subster :"+subqueryStr
        def issueTypeList = operand.args.second()
        log.error "issueTypeList = "+issueTypeList

        if (! subqueryStr.trim()) {
            messageSet.addErrorMessage("Error in subquery: Query may not be empty")
            return messageSet
        }
        log.error "111"
        SearchService.ParseResult subQueryParseResult = searchService.parseQuery(user, subqueryStr)
        log.error "subQueryParseResult---"+subQueryParseResult
        def resultString
        if ( subQueryParseResult.isValid( ) ) {
            messageSet = searchService.validateQuery(user, subQueryParseResult.getQuery())
            log.error "messageSet---"+messageSet
            if (!messageSet.hasAnyErrors()) {
                resultString = executeSubquery(subQueryParseResult.getQuery(), user)
                log.error "resultString----"+resultString
            }
        } else {
            messageSet = subQueryParseResult.getErrors()
        }
        if (messageSet.hasAnyErrors()) {
            log.error "message set has errors"
            Set<String> errorSet = messageSet.getErrorMessages()
            MessageSet newMessageSet =  new MessageSetImpl()
            errorSet.each( { newMessageSet.addErrorMessage("Error in subquery: " + it)})
            log.debug("Error in subquery '" + subqueryStr + "': " + errorSet)
            return newMessageSet
        }

        def mainQuery = MessageFormat.format(templateQuery, resultString)
        log.error "mainQuery----"+mainQuery
        SearchService.ParseResult mainQueryParseResult = searchService.parseQuery(user, mainQuery)
        log.error "mainQueryParseResult--"+mainQueryParseResult
        if ( ! mainQueryParseResult.isValid( ) ) {
            messageSet = mainQueryParseResult.getErrors()
            log.debug("Error in subquery '" + subqueryStr + "': " + messageSet.getErrorMessages())
        }
        return messageSet
    }

    Query getQuery(QueryCreationContext queryCreationContext, FunctionOperand operand, TerminalClause terminalClause, String templateQuery) {
        def subquery = queryParser.parseQuery(operand.args.first())
        def resultStr = executeSubquery(subquery, queryCreationContext.getApplicationUser())
        if (resultStr == "0-0"){
            log.debug("Replaced empty list of results by '0-0' to produce a valid query")
        }
        def queryStr = MessageFormat.format(templateQuery, resultStr)
        log.debug("Final query: " + queryStr)
        log.error("Final query: " + queryStr)
        def query = queryParser.parseQuery(queryStr)
        luceneQueryBuilder.createLuceneQuery(queryCreationContext, query.whereClause)
    }

    String executeSubquery(com.atlassian.query.Query subquery, ApplicationUser user) {
        def searchResult = searchService.search(user, subquery, PagerFilter.getUnlimitedFilter())
        List<Issue> issuesFromQuery = searchResult.results
        if (! issuesFromQuery) {
            return "0-0"
        } else {
            log.error "issuesFromQuery----"+issuesFromQuery.collect({ it.getKey()}).join(",")
            return issuesFromQuery.collect({ it.getKey()}).join(",")
        }
    }
}

1 answer

0 votes
Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 5, 2022

Hi @SWAPNIL SRIVASTAV

To clarify, are you trying to get only all the child issues of a Feature issue type?

For example, if the Feature issue has multiple Epics, do you want all the issues that are in the Epics also to be returned?

Thank you and Kind Regards,
Ram

Suggest an answer

Log in or Sign up to answer