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

It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

Error on custom JQL creation

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

0 votes
Answer accepted
Hana Kučerová Community Leader Mar 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:

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"
}
}



Hana Kučerová Community Leader Mar 30, 2020

Hi @Kevin Bouman ,

that's great! Thanks for sharing your code!

Suggest an answer

Log in or Sign up to answer
TAGS
Community showcase
Published in Apps & Integrations

🍻🍂Apptoberfest Update: Upcoming Virtual Events 🎉

Hello Community! I hope you've been enjoying the 🍂Apptoberfestivities🍂 (I know I have!) The event is heating up next week with a series of virtual events that we're calling the 🍻🍂Partner App ...

102 views 1 7
Read article

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you