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
}
}
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())
}
}
}
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())
}
}
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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())
}
}
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Nice 👍 Thanks for sharing the solution, @Eugeny Chernyavsky
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.