JIRA ScriptRunner常用脚本
ScriptRunner for JIRA 是一款强大的工具, 除了内置的一些功能之外,提供了让用户自己编写Groovy脚本来直接调用JIRA内部的API, 极大的提升了使用的灵活度和JIRA的扩展性
编写Groovy脚本除了对Groovy本身的语法了解之外,还要了解JIRA的API,这些详细的API通常需要看JIRA 的源码才能了解,本文列举了Script Runner一些常见的使用脚本,如更新字段、创建Issue、自动更改Issue状态、添加链接等内容,帮助用户快速的使用ScriptRunner , 希望对您平时使用Groovy 脚本有所帮助
注意:Groovy 脚本虽强大,为了系统的易维护性,还请慎用,不然可能过上一段日子,连自己写的脚本都看不明白了
工作流中更新JIRA字段值
常见的是工作流的Post Function 中更新字段
如果您不是在工作流中更新,需要使用IssueService 来更新,示例代码见下一章
常见文本日期等字段
import com.atlassian.jira.component.ComponentAccessor
import java.sql.Timestamp
def versionManager = ComponentAccessor.getVersionManager()
def projectComponentManager = ComponentAccessor.getProjectComponentManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def userUtil = ComponentAccessor.getUserUtil()
def project = issue.getProjectObject()
def version = versionManager.getVersion(project.getId(), "1.1")
def component = projectComponentManager.findByComponentName(project.getId(), "MyComponent")
if (version) {
issue.setFixVersions([version])
}
if (component) {
issue.setComponent([component])
}
// a text field
def textCf = customFieldManager.getCustomFieldObjectByName("TextFieldA") // <1>
issue.setCustomFieldValue(textCf, "Some text value")
// a date time field - add 7 days to current datetime
def dateCf = customFieldManager.getCustomFieldObjectByName("First Date") // Date time fields require a Timestamp
issue.setCustomFieldValue(dateCf, new Timestamp((new Date() + 7).time))
// a user custom field
def userCf = customFieldManager.getCustomFieldObjectByName("UserPicker")
issue.setCustomFieldValue(userCf, userUtil.getUserByName("admin")) // User CFs require an ApplicationUser
// system fields
issue.setDescription("A generated description")
issue.setDueDate(new Timestamp((new Date() + 1).time)) // set due date to tomorrow
GROOVY
checkbox字段
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.customfields.manager.OptionsManager
def optionsManager = ComponentAccessor.getComponent(OptionsManager)
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def cf = customFieldManager.getCustomFieldObjectByName("Checkboxes") // Checkboxes is the NAME of my custom field
def fieldConfig = cf.getRelevantConfig(issue)
def option = optionsManager.getOptions(fieldConfig).getOptionForValue("Yes", null)
issue.setCustomFieldValue(cf, [option]) // <1>
GROOVY
单选列表字段
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
// the name of the custom field (single select list type)
final customFieldName = 'Single Select List'
// the value of the new option to set
final newValue = 'Option C'
// the issue key to update
final issueKey = 'TEST-1'
def issue = ComponentAccessor.issueManager.getIssueByCurrentKey(issueKey)
assert issue: "Could not find issue with key $issueKey"
def customField = ComponentAccessor.customFieldManager.getCustomFieldObjects(issue).findByName(customFieldName)
assert customField: "Could not find custom field with name $customFieldName"
def availableOptions = ComponentAccessor.optionsManager.getOptions(customField.getRelevantConfig(issue))
def optionToSet = availableOptions.find { it.value == newValue }
assert optionToSet: "Could not find option with value $newValue. Available options are ${availableOptions*.value.join(",")}"
customField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(customField), optionToSet), new DefaultIssueChangeHolder())
GROOVY
添加Issue Link
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLinkTypeManager
// the issue key to create the link from
final String sourceIssueKey = "JRA-1"
// the issue key to create the link to
final String destinationIssueKey = "SSPA-1"
// the name of the issue link
final String issueLinkName = "Blocks"
// the sequence of the link
final Long sequence = 1L
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def issueLinkTypeManager = ComponentAccessor.getComponent(IssueLinkTypeManager)
def issueManager = ComponentAccessor.issueManager
def sourceIssue = issueManager.getIssueByCurrentKey(sourceIssueKey)
def destinationIssue = issueManager.getIssueByCurrentKey(destinationIssueKey)
assert sourceIssue && destinationIssue: "One ore more issues do not exist"
def availableIssueLinkTypes = issueLinkTypeManager.issueLinkTypes
def linkType = availableIssueLinkTypes.findByName(issueLinkName)
assert linkType : "Could not find link type with name $issueLinkName. Available issue link types are ${availableIssueLinkTypes*.name.join(", ")}"
ComponentAccessor.issueLinkManager.createIssueLink(sourceIssue.id, destinationIssue.id, linkType.id, sequence, loggedInUser)
GROOVY
在Console中更新JIRA字段值
JIRA的字段有不同的类型,如数字、文本、日期、人员、单选、多选、列表等, 这些不同的字段,更新的方式也不同,下面列举了常用字段的更新方式
您可以在Script Runner提供的Console中调试
Console 中更新Issue 的字段和 在工作流中不一样, 工作流中只需要设置字段的值就可以,因为工作流中还有后续的event和索引的处理
Console 中更新需要使用 issueService, issueService会验证输入值、设置完字段后会更新字段值
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.fields.CustomField
import groovy.transform.Field
// 更新的JIRA Issue Key
final String issueKey = "JRA-1"
// 以下定义为字段名称
// 单选列表字段
final String selectList = "SelectListA"
// 多选列表字段
final String multiSelectList = "MultiSelectA"
// 单选字段 'radio button'
final String radioButtonField = "RadioButtons"
// 多选字段 check box
final String checkboxField = "Checkboxes"
// 文本字段
final String textField = "TextFieldA"
// 用户选择字段 'user picker'
final String userPicker = "UserPicker"
// 多用选择字段 'multi user picker'
final String multiUserPicker = "MultiUserPickerA"
// 组选组字段'group picker'
final String groupPicker = "GroupPicker"
// 多组选择字段'multi group picker'
final String multiGroupPicker = "MultiGroupPicker"
// 日期和时间字段 'date and time'
final String dateTimeField = "First DateTime"
// 日期字段'date'
final String dateField = "Date"
// 项目选择字段 'project picker'
final String projectPickerField = "ProjectPicker"
// lable字段 'label'
final String labelField = "LabelField"
// 版本字段 'single version picker'
final String versionField = "VersionPicker"
// 如果希望更新后发送通知,请设置这个开关为 true
final boolean sendMail = false
def issueService = ComponentAccessor.issueService
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def issue = ComponentAccessor.issueManager.getIssueByCurrentKey(issueKey)
assert issue: "Could not find issue with key $issueKey"
def issueInputParameters = issueService.newIssueInputParameters().with {
// 有选项的字段, 需要先取出选项的option对象再设置 (select lists, checkboxes, radio buttons)
addCustomFieldValue(getSingleCustomFieldByName(selectList).id, *getOptionIdsForFieldByValue(selectList, "BBB"))
addCustomFieldValue(getSingleCustomFieldByName(multiSelectList).id, *getOptionIdsForFieldByValue(multiSelectList, "BBB", "CCC"))
addCustomFieldValue(getSingleCustomFieldByName(radioButtonField).id, *getOptionIdsForFieldByValue(radioButtonField, "Yes"))
addCustomFieldValue(getSingleCustomFieldByName(checkboxField).id, *getOptionIdsForFieldByValue(checkboxField, "Maybe", "Yes"))
// 文本字段 设置
addCustomFieldValue(getSingleCustomFieldByName(textField).id, "New Value")
// 单用户字段 设置
addCustomFieldValue(getSingleCustomFieldByName(userPicker).id, "bob")
// 多用户字段 设置
addCustomFieldValue(getSingleCustomFieldByName(multiUserPicker).id, "bob", "alice")
// 单组字段 设置
addCustomFieldValue(getSingleCustomFieldByName(groupPicker).id, "jira-users")
// 多组字段 设置
addCustomFieldValue(getSingleCustomFieldByName(multiGroupPicker).id, "jira-users", "jira-administrators")
// 时间和日期选择期字段设置
addCustomFieldValue(getSingleCustomFieldByName(dateTimeField).id, "04/Feb/12 8:47 PM")
// 日期选择器字段设置
addCustomFieldValue(getSingleCustomFieldByName(dateField).id, "04/Feb/12")
}
// 项目选择器设置
def project = ComponentAccessor.projectManager.getProjectObjByKey("SSPA")
assert project: "Could not find project"
issueInputParameters.addCustomFieldValue(getSingleCustomFieldByName(projectPickerField).id, project.id.toString())
// lable选择字段设置
issueInputParameters.addCustomFieldValue(getSingleCustomFieldByName(labelField).id, "foo", "bar")
// version字段设置
def version = ComponentAccessor.versionManager.getVersions(issue.projectObject).findByName("Version1")
assert version: "Could not find version"
issueInputParameters.addCustomFieldValue(getSingleCustomFieldByName(versionField).id, version.id.toString())
def updateValidationResult = issueService.validateUpdate(loggedInUser, issue.id, issueInputParameters)
assert updateValidationResult.valid: updateValidationResult.errorCollection
def issueUpdateResult = issueService.update(loggedInUser, updateValidationResult, EventDispatchOption.ISSUE_UPDATED, sendMail)
assert issueUpdateResult.valid: issueUpdateResult.errorCollection
/**
* Get a custom field given a custom field name.
* If there are than one custom fields with the same name under the same Context then return the first one.
* @param fieldName The name of the custom field
* @param issue The issue to look for that custom field
* @return the custom field, if that exists
*/
CustomField getSingleCustomFieldByName(String fieldName) {
def issue = ComponentAccessor.issueManager.getIssueByCurrentKey(issueKey)
def customField = ComponentAccessor.customFieldManager.getCustomFieldObjects(issue).findByName(fieldName)
assert customField: "Could not find custom field with name $fieldName"
customField
}
/**
* Given a custom field name and option values, retrieve their ids as String
* @param customFieldName The name of the custom field
* @param values The values in order to get their ids
* @return List < String > The ids of the given values
*/
List<String> getOptionIdsForFieldByValue(String customFieldName, String... values) {
def issue = ComponentAccessor.issueManager.getIssueByCurrentKey(issueKey)
def customField = getSingleCustomFieldByName(customFieldName)
ComponentAccessor.optionsManager.getOptions(customField.getRelevantConfig(issue)).findAll {
it.value in values.toList()
}*.optionId*.toString()
}
GROOVY
Script Runner 执行 JQL查询
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchException
import com.atlassian.jira.web.bean.PagerFilter
import org.apache.log4j.Level
// Set log level to INFO
log.setLevel(Level.INFO)
// The JQL query you want to search with
final jqlSearch = "Some JQL query"
// Some components
def user = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def searchService = ComponentAccessor.getComponentOfType(SearchService)
// Parse the query
def parseResult = searchService.parseQuery(user, jqlSearch)
if (!parseResult.valid) {
log.error('Invalid query')
return null
}
try {
// Perform the query to get the issues
def results = searchService.search(user, parseResult.query, PagerFilter.unlimitedFilter)
def issues = results.results
issues.each {
log.info(it.key)
}
issues*.key
} catch (SearchException e) {
e.printStackTrace()
null
}
GROOVY
添加REST Endpoint
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonBuilder
import groovy.transform.BaseScript
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
@BaseScript CustomEndpointDelegate delegate
/* 代码说明:
REST 节点的名称doSomething
groups: ["jira-administrators"] : 这个是可选的,表明访问这个api的权限限制
queryParams 是输入的参数
body 是http请求的body
*/
doSomething(
httpMethod: "GET", groups: ["jira-administrators"]
) { MultivaluedMap queryParams, String body ->
return Response.ok(new JsonBuilder([abc: 42]).toString()).build()
}
GROOVY
作为HttpClient发送Http请求
import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.ContentType.*
import groovyx.net.http.ContentType
import static groovyx.net.http.Method.*
import groovy.json.JsonSlurper
import net.sf.json.groovy.JsonSlurper
def http = new HTTPBuilder('http://IP/authentication')
http.request(POST) {
requestContentType = ContentType.JSON
body = [username: 'USERNAME', password: 'PASSWORD']
response.success = { resp, JSON ->
return JSON
}
response.failure = { resp ->
return "Request failed with status ${resp.status}"
}
}
GROOVY
查询数据库
以下例子为查询JIRA的数据库
import com.atlassian.jira.component.ComponentAccessor
import groovy.sql.Sql
import org.ofbiz.core.entity.ConnectionFactory
import org.ofbiz.core.entity.DelegatorInterface
import java.sql.Connection
def delegator = (DelegatorInterface) ComponentAccessor.getComponent(DelegatorInterface)
String helperName = delegator.getGroupHelperName("default")
def sqlStmt = """
SELECT project.pname, COUNT(*) AS kount
FROM project
INNER JOIN jiraissue ON project.ID = jiraissue.PROJECT
GROUP BY project.pname
ORDER BY kount DESC
"""
Connection conn = ConnectionFactory.getConnection(helperName)
Sql sql = new Sql(conn)
try {
StringBuffer sb = new StringBuffer()
sql.eachRow(sqlStmt) {
sb << "${it.pname}\t${it.kount}\n"
}
log.debug sb.toString()
}
finally {
sql.close()
}
GROOVY