I am always missing a Custom Field that can handle Key=Value entried; combined with multiple entries, so I use a multiline Text Field:
Service Level=AppDrift CustomerNumber=1043 Othername=Myhost
Lets call the field "VC Tags". Issues with a Key=Value can be found with a JQL like:
"VC Tags"~"Service Level=AppDrift"
But is quite volatile and the fuzzy search ~ will also find:
Service Level=AppDrift+ CustomerNumber=1043 Othername=Myhost
So, this JQL Function (For Adaptavist ScriptRunner) can cope with that:
This is working sample code, there is room for better Error Handling
package com.onresolve.jira.groovy.jql import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.Issue import com.atlassian.query.clause.TerminalClause import com.atlassian.jira.jql.query.QueryCreationContext import org.apache.lucene.index.Term import com.onresolve.jira.groovy.jql.AbstractScriptedJqlFunction import org.apache.lucene.search.BooleanClause import org.apache.lucene.search.BooleanQuery import org.apache.lucene.search.Query import org.apache.lucene.search.TermQuery import com.atlassian.jira.issue.fields.CustomField import com.atlassian.jira.issue.CustomFieldManager import com.atlassian.jira.user.ApplicationUser import com.atlassian.jira.security.JiraAuthenticationContext import com.atlassian.query.operand.FunctionOperand import com.atlassian.jira.issue.Issue class keyValueInField extends AbstractScriptedJqlFunction implements JqlQueryFunction { @Override String getDescription() { "Function to find a key value pair in a Custom Field" } @Override List<Map> getArguments() { [ [ "subquery": "Subquery", "optional": false], [ "fieldname": "Custom Fieldname", "optional": false], [ "key": "", "The Key to find": false], [ "value": "", "The value to check for in the key": false], [ "usecontains": "", "Use contains instead of equal": false] ] } @Override String getFunctionName() { "keyValueInField" } @Override Query getQuery(QueryCreationContext queryCreationContext, FunctionOperand operand, TerminalClause terminalClause) { CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager() final JiraAuthenticationContext context = ComponentAccessor.getJiraAuthenticationContext() final ApplicationUser applicationUser = context.getLoggedInUser() final BooleanQuery.Builder boolQueryBuilder = new BooleanQuery.Builder() String jql = operand.args[0] String fieldname = operand.args[1] String key = operand.args[2].toLowerCase() String value = operand.args[3].toLowerCase() String usecontains = operand.args[4].toLowerCase() issues = getIssues(jql, applicationUser) issues.each { Issue issue -> //Get Field def field = customFieldManager.getCustomFieldObjectsByName(fieldname)[0] if (field) { //Get the Value def fieldValue = issue.getCustomFieldValue(field)?:"" if (fieldValue) { //Loop each Value fieldValue.toString().split("\n").each { theLine -> theLine = theLine.toLowerCase() if (usecontains == "1" || usecontains == "true") { if (theLine.split("=")[0] == key && theLine.split("=")[1].contains(value)) { boolQueryBuilder.add(new TermQuery(new Term("issue_id", issue.id as String)), BooleanClause.Occur.SHOULD) } } else { if (theLine.split("=")[0] == key && theLine.split("=")[1] == value) { boolQueryBuilder.add(new TermQuery(new Term("issue_id", issue.id as String)), BooleanClause.Occur.SHOULD) } } } } } } return boolQueryBuilder.build() } }
Usage:
iossuefunction in keyValueInField("Project=TEST","VC Tags","Service Level","AppDrift",0)
for an equal search (0). For a contains (fuzzy ~) search:
issuefunction in keyValueInField("Project=TEST and IssueType=Task","VC Tags","Service Level","AppDrift",1)