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

Creating custom JQL Function to find issues written with certain text from any member of group

Eugeny Chernyavsky
Contributor
September 18, 2024

Hi Community,

I’m trying to write custom JQL Function to be able to find all issues with any comment with defined text from any member of defined group.

Never done something like this before, but I tried using examples from Adaptavist documentation  to write some code, but it fails each time to get any result (Error occurred communicating with the server. Please reload the page and try again.)

I'd 
appriciate any help with troubleshooting 

 

 

package com.onresolve.jira.groovy.jql

import com.atlassian.jira.JiraDataTypes

import com.atlassian.jira.JiraDataType

import com.atlassian.jira.jql.query.QueryCreationContext

import com.atlassian.jira.component.ComponentAccessor

import com.atlassian.jira.issue.Issue

import com.atlassian.jira.issue.comments.CommentManager

import com.atlassian.jira.user.util.UserManager

import com.atlassian.jira.security.groups.GroupManager

import com.onresolve.jira.groovy.jql.AbstractScriptedJqlFunction

import com.atlassian.query.clause.TerminalClause

import com.atlassian.query.operand.FunctionOperand

import com.atlassian.query.operand.Operand

import com.atlassian.query.Query

import com.atlassian.query.clause.Clause

class CommentsByGroupCopy extends AbstractScriptedJqlFunction implements JqlFunction {

@Override

String getDescription() {

return "testing function"

}

@Override

String getFunctionName() {

return "commentsByGroup"

}

@Override

List<Map> getArguments() {

return [

[name: "group", description: "The group to search for"],

[name: "text", description: "The text to search for in comments"]

]

}

@Override

JiraDataType getDataType() {

JiraDataTypes.TEXT

}

@Override

List<Issue> getValues(QueryCreationContext queryCreationContext, FunctionOperand operand, TerminalClause terminalClause) {

def groupManager = ComponentAccessor.getGroupManager()

def commentManager = ComponentAccessor.getCommentManager()

def userManager = ComponentAccessor.getUserManager()

def issueManager = ComponentAccessor.getIssueManager()

def groupName = operand.args[0]

def searchText = operand.args[1]

def group = groupManager.getGroup(groupName)

if (!group) {

return []

}

def usersInGroup = groupManager.getUsersInGroup(group)

def userKeys = usersInGroup*.key

def issuesIDs = issueManager.getIssueIdsForProject(24304)

def matchingIssues = []

issuesIDs.each { id ->

def issue = issueManager.getIssueObject(id)

def comments = commentManager.getComments(issue)

comments.each { comment ->

if (userKeys.contains(comment.authorApplicationUser.key) && comment.body.contains(searchText)) {

log.error(issue)

matchingIssues.add(issue)

return

}

}

}

log.error(matchingIssues)

return matchingIssues

}

}
 

 

3 answers

1 accepted

Suggest an answer

Log in or Sign up to answer
1 vote
Answer accepted
Eugeny Chernyavsky
Contributor
September 23, 2024

Managed also to provide not only a project, but subquery also:

JQL looks like
issuekey in commentsByGroupJQL("project = TSD AND Resolution = Unresolved", Jira-custom-group, test-text-to-search)

code is:

 

 

package com.onresolve.jira.groovy.jql
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.jql.operand.QueryLiteral
import com.atlassian.jira.JiraDataTypes
import com.atlassian.jira.JiraDataType
import com.atlassian.jira.jql.query.QueryCreationContext
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.comments.CommentManager
import com.atlassian.jira.user.util.UserManager
import com.atlassian.jira.security.groups.GroupManager
import com.onresolve.jira.groovy.jql.AbstractScriptedJqlFunction
import com.atlassian.query.clause.TerminalClause
import com.atlassian.query.operand.FunctionOperand
import com.atlassian.query.operand.Operand
import com.atlassian.query.Query
import com.atlassian.query.clause.Clause

class CommentsByGroupJQL extends AbstractScriptedJqlFunction implements JqlFunction {

@Override
String getDescription() {
return "Function to get issues from JQL commented with text by any member of the group"
}

@Override
String getFunctionName() {
return "commentsByGroupJQL"
}

@Override
List<Map> getArguments() {
return [
[name: "Querry", description: "JQL to search in"],
[name: "Group", description: "The group to search for"],
[name: "Text", description: "The text to search for in comments"]
]
}

@Override
boolean isList() {
true
}

@Override
JiraDataType getDataType() {
JiraDataTypes.TEXT
}

@Override
List<QueryLiteral> getValues(QueryCreationContext queryCreationContext, FunctionOperand operand, TerminalClause terminalClause) {
def groupManager = ComponentAccessor.getGroupManager()
def commentManager = ComponentAccessor.getCommentManager()
def userManager = ComponentAccessor.getUserManager()
def issueManager = ComponentAccessor.getIssueManager()
def projectManager = ComponentAccessor.getProjectManager()
def searchservice = ComponentAccessor.getComponentOfType(SearchService)

def query = operand.args[0]
def groupName = operand.args[1]
def searchText = operand.args[2]

def group = groupManager.getGroup(groupName)
if (!group) {
return []
}

def usersInGroup = groupManager.getUsersInGroup(group)
def userKeys = usersInGroup*.key
def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def parsequery = searchservice.parseQuery(currentUser,query)

List<Issues> matchingIssues = []
// if(parsequery.valid) {
def queryResult = Issues.search(parsequery.query)
queryResult.each {
def comments = commentManager.getComments(it)
comments.find { comment ->
if (userKeys.contains(comment.authorApplicationUser.key) && comment.body.contains(searchText)) {
matchingIssues.add(it)
return true
}
return false
}
}
// }

log.error("commentsByGroupJQL JQL function expected result: " + matchingIssues.key)
matchingIssues.collect {
log.error(it.key)
new QueryLiteral(operand, it.key.toString())
}

}
}

 

 

 

0 votes
Eugeny Chernyavsky
Contributor
October 15, 2024

Just to update solution: In case of big Projects: there is possibility that custom JQL Function can take more then 5min to get all issues. I've explored Jira's Lucene index search where I can limit number of result:
here is a code

 

package com.onresolve.jira.groovy.jql
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.issue.search.SearchProviderFactory
import org.apache.lucene.search.BooleanClause
import com.atlassian.jira.issue.index.DocumentConstants
import org.apache.lucene.search.TermQuery
import org.apache.lucene.index.Term
import org.apache.lucene.search.BooleanQuery
import com.atlassian.jira.jql.operand.QueryLiteral
import com.atlassian.jira.JiraDataTypes
import com.atlassian.jira.JiraDataType
import com.atlassian.jira.jql.query.QueryCreationContext
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.security.groups.GroupManager
import com.onresolve.jira.groovy.jql.AbstractScriptedJqlFunction
import com.atlassian.query.clause.TerminalClause
import com.atlassian.query.operand.FunctionOperand
import com.atlassian.query.operand.Operand
import com.atlassian.query.Query
import com.atlassian.query.clause.Clause

class CommentedByMemberOfGroupQJL extends AbstractScriptedJqlFunction implements JqlFunction {

@Override
String getDescription() {
return "Function return list of Issues where Member of Defined group Commented with defined text"
}

@Override
String getFunctionName() {
return "CommentedByMemberOfGroupQJL"
}

@Override
List<Map> getArguments() {
return [
[name: "project", description: "Project Key where to search"],
[name: "Group", description: "The group to search for"],
[name: "Text", description: "The text to search for in comments"]
]
}

@Override
boolean isList() {
true
}

@Override
JiraDataType getDataType() {
JiraDataTypes.TEXT
}

@Override
List<QueryLiteral> getValues(QueryCreationContext queryCreationContext, FunctionOperand operand, TerminalClause terminalClause) {
def groupManager = ComponentAccessor.getGroupManager()
def issueManager = ComponentAccessor.getIssueManager()
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchProvider = ComponentAccessor.getComponentOfType(SearchProvider)
def searchProviderFactory = ComponentAccessor.getComponentOfType(SearchProviderFactory)
def issueSearcher = searchProviderFactory.getSearcher(SearchProviderFactory.ISSUE_INDEX)
def commentSearcher = searchProviderFactory.getSearcher(SearchProviderFactory.COMMENT_INDEX)


def projectKey = operand.args[0]
def groupName = operand.args[1]
def searchText = operand.args[2]

def group = groupManager.getGroup(groupName)
if (!group) {
return []
}

def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def usersInGroup = groupManager.getUsersInGroup(group)
def userKeys = usersInGroup*.key
def project = Projects.getByKey(projectKey)

List<Issues> matchingIssues = []

def issueQuery = new BooleanQuery.Builder()
.add(new TermQuery(new Term(DocumentConstants.PROJECT_ID, project.id.toString())), BooleanClause.Occur.MUST)
.build()
def issueDocs = issueSearcher.search(issueQuery,25000).scoreDocs

issueDocs.each {

def issueDocument = issueSearcher.doc(it.doc)
def issueId = issueDocument.getAt('issue_id').toInteger()
def targetIssue = issueManager.getIssueObject(issueId)

def commentQuery = new BooleanQuery.Builder()
.add(new TermQuery(new Term(DocumentConstants.ISSUE_ID, targetIssue.id.toString())), BooleanClause.Occur.MUST)
.build()

def docId = commentSearcher.search(commentQuery,100).scoreDocs
docId.each {
def document = commentSearcher.doc(it.doc)
def comment = document.getField('body').stringValue()
def commentAuthor = document.getField('comment_author').stringValue()
if (userKeys.contains(commentAuthor) && comment.contains(searchText)) {
matchingIssues.add(targetIssue)
return true
}
return false
}

}
log.info("CommentedByMemberOfGroupQJL JQL function expected result: " + matchingIssues.key)
matchingIssues.collect {
new QueryLiteral(operand, it.toString())
}

}
}

 

0 votes
Dave Rosenlund _Trundl_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
September 18, 2024

Hi, @Eugeny Chernyavsky 👋  I moved your post here to App Central because it's the best place for questions about Atlassian Marketplace apps like Scriptrunner. 

That said, I suggest you contact the Scriptrunner support folks at Adapatavist directly. For one thing, AFAIK there is no JQL for retrieving text from the comments in a Jira issue, which is probably why you are trying to use Scriptrunner.

However, I'm guessing there might be some way around the missing JQL feature one can use in Scriptrunner to do what you want to do.

Most likely, you'll get a faster answer contacting them directly.

Hope this helps,

-dave

Eugeny Chernyavsky
Contributor
September 20, 2024

Hi @Dave Rosenlund _Trundl_ ,
Thanks for your suggestion, I've contacted Adaptavist support, but managed to get result from custom JQL functions as string of issue keys:

 

package com.onresolve.jira.groovy.jql

import com.atlassian.jira.jql.operand.QueryLiteral

import com.atlassian.jira.JiraDataTypes

import com.atlassian.jira.JiraDataType

import com.atlassian.jira.jql.query.QueryCreationContext

import com.atlassian.jira.component.ComponentAccessor

import com.atlassian.jira.issue.Issue

import com.atlassian.jira.issue.comments.CommentManager

import com.atlassian.jira.user.util.UserManager

import com.atlassian.jira.security.groups.GroupManager

import com.onresolve.jira.groovy.jql.AbstractScriptedJqlFunction

import com.atlassian.query.clause.TerminalClause

import com.atlassian.query.operand.FunctionOperand

import com.atlassian.query.operand.Operand

import com.atlassian.query.Query

import com.atlassian.query.clause.Clause

class CommentsByGroupCopy extends AbstractScriptedJqlFunction implements JqlFunction {

@Override

String getDescription() {

return "testing function"

}

@Override

String getFunctionName() {

return "commentsByGroup"

}

@Override

List<Map> getArguments() {

return [

[name: "Project Key", description: "Project to search in"],

[name: "Group", description: "The group to search for"],

[name: "Text", description: "The text to search for in comments"]

]

}

@Override

boolean isList() {

true

}

@Override

JiraDataType getDataType() {

JiraDataTypes.TEXT

}

@Override

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

def groupManager = ComponentAccessor.getGroupManager()

def commentManager = ComponentAccessor.getCommentManager()

def userManager = ComponentAccessor.getUserManager()

def issueManager = ComponentAccessor.getIssueManager()

def projectManager = ComponentAccessor.getProjectManager()

def projectKey = operand.args[0]

def groupName = operand.args[1]

def searchText = operand.args[2]

def group = groupManager.getGroup(groupName)

if (!group) {

return []

}

def usersInGroup = groupManager.getUsersInGroup(group)

def userKeys = usersInGroup*.key

def projectId = projectManager.getProjectObjByKeyIgnoreCase(projectKey).id

def issuesIDs = issueManager.getIssueIdsForProject(projectId)

List<Issues> matchingIssues = []

issuesIDs.each { id ->

def issue = issueManager.getIssueObject(id)

def comments = commentManager.getComments(issue)

comments.each { comment ->

if (userKeys.contains(comment.authorApplicationUser.key) && comment.body.contains(searchText)) {

log.error(issue)

matchingIssues.add(issue)

return

}

}

}

matchingIssues.collect {

new QueryLiteral(operand, it.toString())

}

}

}
Like Dave Rosenlund _Trundl_ likes this
Dave Rosenlund _Trundl_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
September 20, 2024

Nice 👍  Thanks for sharing the solution, @Eugeny Chernyavsky

 

TAGS
AUG Leaders

Atlassian Community Events