Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Error on custom JQL creation

Kevin Bouman March 17, 2020

I am trying to create a custom JQL using Scriptrunner. I need to find all issues that have at least one attachment in an attachment category in the Smart Attachments add-on. I have the code working as just a groovy script. However, I want to turn it into a custom JQL function so the users don't need an admin to execute it. I place the working code into the custom JQL template that the ScriptRunner documentation states. The Issue Navigator is throwing the following error.

Scripted function "customJQL" compilation failure. Contact administrator and check logs. Message: Cannot cast object '[]' with class 'java.util.ArrayList' to class 'com.atlassian.query.Query' due to: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: com.atlassian.query.Query()

Here is the code:

/*********************
Description: This script is a template to create custom JQL functions using scriptRunner

Author: Kevin Bouman
Created: 3/4/2020
Updated:

**********************/

package com.onresolve.jira.groovy.jql

import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
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.MessageSet
import com.atlassian.query.clause.TerminalClause
import com.atlassian.query.operand.FunctionOperand
import org.apache.lucene.search.Query

import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.user.util.UserManager
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.stiltsoft.jira.attachcategory.facade.SmartAttachmentsFacade
import com.stiltsoft.jira.attachcategory.facade.entity.issue.AttachmentCategories
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.bc.issue.search.SearchService;
import com.atlassian.jira.web.bean.PagerFilter;
import com.atlassian.jira.issue.MutableIssue
import org.apache.lucene.search.BooleanClause
import org.apache.lucene.search.BooleanQuery
import org.apache.lucene.index.Term
import org.apache.lucene.search.Query
import org.apache.lucene.search.TermQuery

import java.text.MessageFormat

class customJQLFunction extends AbstractScriptedJqlFunction implements JqlQueryFunction {

/**
* Modify this query as appropriate.
*
* See {@link java.text.MessageFormat} for details
*/

public static final String TEMPLATE_QUERY = {0}

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

@Override
String getDescription() {
"Search for issues with no attachments in specific Smart Attachment category."
}

@Override
MessageSet validate(ApplicationUser user, FunctionOperand operand, TerminalClause terminalClause) {
def messageSet = new NumberOfArgumentsValidator(2, 2, getI18n()).validate(operand)

if (messageSet.hasAnyErrors()) {
return messageSet
}

def query = mergeQuery(operand)
messageSet = searchService.validateQuery(user, query)
messageSet
}

@Override
List<Map> getArguments() {
[
[
description: "Subquery to limit results, Example: (project = abc)",
optional : false,
],
[
description: "Smart Attachment category name.",
optional : false,
]
]
}

@Override
String getFunctionName() {
"customJQL"
}

@Override
Query getQuery(QueryCreationContext queryCreationContext, FunctionOperand operand, TerminalClause terminalClause) {

def query = mergeQuery(operand)
luceneQueryBuilder.createLuceneQuery(queryCreationContext, query)
}

private com.atlassian.query.Query mergeQuery(FunctionOperand operand) {
// Initializing the app components
@WithPlugin("com.stiltsoft.jira.smart-attachments")
SmartAttachmentsFacade facade = ScriptRunnerImpl.getPluginComponent(SmartAttachmentsFacade)
UserManager userManager = ComponentAccessor.getUserManager()
IssueManager issueManager = ComponentAccessor.getIssueManager()
def searchService = ComponentAccessor.getComponent(SearchService.class);

//def firstQuery = MessageFormat.format(TEMPLATE_QUERY, operand.args.first());
//def query = queryParser.parseQuery(firstQuery);
def returnArray = []
def myCategory = {1}

//Get the current user
JiraAuthenticationContext authContext = ComponentAccessor.getJiraAuthenticationContext();
def user = authContext.getLoggedInUser();

//Run the JQL
SearchService.ParseResult parseResult = searchService.parseQuery(user, TEMPLATE_QUERY);
def searchResult = searchService.search(user, parseResult.getQuery(), PagerFilter.getUnlimitedFilter());

//create the list that contains all the issue keys
def issues = searchResult.results.collect {issueManager.getIssueObject(it.id)};

//Loop through all issues and see if there are attachments in the requested category
for (MutableIssue i in issues){

AttachmentCategories attachmentCategories = facade.getAttachmentCategories(i)

//Specify the category in question to search for attachments
def selectedCategory = attachmentCategories.categories.find { category -> category.name.equalsIgnoreCase(myCategory) }
def attachments = selectedCategory.getAttachments(false)
if(attachments.size() > 0){
returnArray = returnArray + i
}

}

return returnArray

/***********************/

}

// private com.atlassian.query.Query mergeQuery(FunctionOperand operand) {
// def queryStr = MessageFormat.format(TEMPLATE_QUERY, operand.args.first())
// queryParser.parseQuery(queryStr)
// }

}

What am I missing?

I am new to creating custom JQL functions so please forgive my ignorance. 

1 answer

1 accepted

Suggest an answer

Log in or Sign up to answer
0 votes
Answer accepted
Hana Kučerová
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
March 17, 2020

Hi @Kevin Bouman ,

the problem is in the mergeQuery method - it expects com.atlassian.query.Query to be returned, but you variable returnArray has some list type.

I believe instead of modify mergeQuery method you should create getValues method and put your code there (the part with searching issues and filtering based on the attachment categories) - please look at these two examples, which are I believe more suitable to your situation:

Kevin Bouman March 30, 2020

Thanks for your direction. You are correct that I needed to create a new method. I also changed the query type to the same as the "Project Versions" query in the ScriptRunner documentation. Here is the working code.

/*********************
Description: This script is a custom JQL query that returns issues that have at least 1
attachment in the associated SmartAttachments category. This query requires the SmartAttachments plugin.

JQL structure: key in attachmentsInCategory(arg1, arg2)
- arg1: This needs to be a subquery to limit the result set. An example is, "project = ABC"
- arg2: This needs to be the name of the Smart Attachments category that we are looking for. An Example is "Source"

Author: Kevin Bouman
Created: 3/4/2020
Updated:

**********************/

package com.onresolve.jira.groovy.jql

import com.atlassian.jira.JiraDataType
import com.atlassian.jira.JiraDataTypes
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.issue.search.SearchQuery
import com.atlassian.jira.jql.operand.QueryLiteral
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.jql.query.IssueIdCollector
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.MessageSet
import com.atlassian.jira.util.MessageSetImpl
import com.atlassian.query.clause.TerminalClause
import com.atlassian.query.operand.FunctionOperand
import groovy.util.logging.Log4j
import org.apache.log4j.Category

//Smart Attachments specific imports
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.stiltsoft.jira.attachcategory.facade.SmartAttachmentsFacade
import com.stiltsoft.jira.attachcategory.facade.entity.issue.AttachmentCategories

@Log4j
class attachmentsInCategory extends AbstractScriptedJqlFunction implements JqlFunction {

@Override
public MessageSet validate(ApplicationUser searcher, FunctionOperand operand, TerminalClause terminalClause) {
def i18n = ComponentAccessor.getI18nHelperFactory().getInstance(searcher)
def numberValidMessage = new NumberOfArgumentsValidator(2, i18n).validate(operand);
if (numberValidMessage.hasAnyErrors()) {
return numberValidMessage
}
def messageSet = new MessageSetImpl();
JqlQueryParser jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
try {
def query = jqlQueryParser.parseQuery(operand.getArgs().get(0))
} catch (any) {
messageSet.addErrorMessage("not valid jql:${operand.getArgs().get(0)}");
messageSet.addErrorMessage("${any}");
}
return messageSet
}

@Override
List<QueryLiteral> getValues(QueryCreationContext queryCreationContext, FunctionOperand operand, TerminalClause terminalClause) {

final List<QueryLiteral> literals = new LinkedList<>();

//Define the JQL to get the issues you want to affect. This is the first argument from the JQL
String jql = operand.getArgs().get(0);

getIssuesByJQL(jql, operand, queryCreationContext.getApplicationUser()).each { issue ->
literals << new QueryLiteral(operand, issue.key)
issue.getSubTaskObjects().each { subTask ->
literals << new QueryLiteral(operand, subTask.key)
}
}

return literals

}

private Collection<MutableIssue> getIssuesByJQL(String jql, FunctionOperand operand, ApplicationUser user) {
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchProvider = ComponentAccessor.getComponent(SearchProvider)

def query = jqlQueryParser.parseQuery(jql)
SearchQuery searchQuery = SearchQuery.create(query, user)
IssueIdCollector collector = new IssueIdCollector()
searchProvider.search(searchQuery, collector)

// Initializing the SmartAttachments app components
@WithPlugin("com.stiltsoft.jira.smart-attachments")
SmartAttachmentsFacade facade = ScriptRunnerImpl.getPluginComponent(SmartAttachmentsFacade)
IssueManager issueManager = ComponentAccessor.getIssueManager()

//Second Argument from the JQL. Needs to be the name of a SmartAttachments Category
def myCategory = operand.getArgs().get(1)

def returnMe = []

//Loop through the issues and only return the ones that have at least 1 attachment in the provided category
for (def i in collector.getIssueIds()){
def iss = ComponentAccessor.issueManager.getIssueObject(i as Long) //getIssue(i as Long)
MutableIssue myIssue = issueManager.getIssueObject(iss.key);

//Get the categories for the current issue
AttachmentCategories attachmentCategories = facade.getAttachmentCategories(myIssue)
def selectedCategory = attachmentCategories.categories.find { category -> category.name.equalsIgnoreCase(myCategory) }
def attachments = selectedCategory.getAttachments(true)

//Check if there are any attachments in the category
if(attachments.size() > 0){
//Add issue to the return array
returnMe.add(myIssue)
}
}

return returnMe

}

@Override
Integer getMinimumNumberOfExpectedArguments() {
0
}

@Override
JiraDataType getDataType() {
JiraDataTypes.ISSUE
}

@Override
String getDescription() {
"Search for issues with no attachments in specific Smart Attachment category."
}

@Override
List<Map> getArguments() {
[
[
description: "Subquery to limit results, Example: (project = abc)",
optional : false,
],
[
description: "Smart Attachment category name.",
optional : false,
]
]
}

@Override
boolean isList() {
true
}

@Override
String getFunctionName() {
"attachmentsInCategory"
}
}



Like artemiy330 likes this
Hana Kučerová
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
March 30, 2020

Hi @Kevin Bouman ,

that's great! Thanks for sharing your code!

TAGS
AUG Leaders

Atlassian Community Events