原生 Jira 功能不溝通,UI友好型,工作流規則,字段邏輯等,選擇組合插件困難,需要 all in one 全家桶.
選型困難,排序 top sales 和 trending ,SciptRunner for JIRA 高居榜首,內置大量腳本,庫,還能各種觸達 metadata 魔改,師出名門,Adaptavist,功能夠強,擴展方便,售後支持有保障。
此插件功能過多,不一一贅述操作指南,跳過動圖演示和操作説明部分,詳情查看官方説明文檔,本文基於官方几個經典場景來做説明。
跟蹤首次轉換工作狀態所花時間,一般用於評估客户服務的響應速度,In Progress 環節可以按需修改。
package com.onresolve.jira.groovy.test.scriptfields.scripts
import com.atlassian.jira.component.ComponentAccessor
def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()
def created = changeHistoryManager.getChangeItemsForField(issue, "status").find {
it.toString == "In Progress"
}?.getCreated()
def createdTime = created?.getTime()
createdTime ? new Date(createdTime) : null
Template |
Date Time Picker |
Searcher |
Date Time Range picker |
JIRA 的好處是把項目過程數據,七七八八都關聯起來,糟糕的是,想要看關聯信息,每次跳來跳去,刷新網頁,操作體驗令人沮喪。
通過腳本,自動統計指定關聯 issue 的剩餘工作預計時間。
説明:Composition可以修改成相關的 issue 鏈接名稱。
package com.onresolve.jira.groovy.test.scriptfields.scripts
import com.atlassian.jira.component.ComponentAccessor
def issueLinkManager = ComponentAccessor.getIssueLinkManager()
def totalRemaining = 0
issueLinkManager.getOutwardLinks(issue.id).each { issueLink ->
if (issueLink.issueLinkType.name == "Composition") {
def linkedIssue = issueLink.destinationObject
totalRemaining += linkedIssue.getEstimate() ?: 0
}
}
// add the remaining work for this issue if there are children, otherwise it's just the same as the remaining estimate,
// so we won't display it,
if (totalRemaining) {
totalRemaining += issue.getEstimate() ?: 0
}
return totalRemaining as Long ?: 0L
Template |
Duration (time-tracking) |
Searcher |
Duration Searcher |
如果被關聯的 issue 不重新索引,則無法統計出正確結果,未解決此問題,需要加一個 腳本監聽器,關注 "is comprised of"
package com.onresolve.jira.groovy.test.scriptfields.scripts
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.index.IssueIndexManager
def issue = event.issue // event is an IssueEvent
def issueLinkManager = ComponentAccessor.getIssueLinkManager()
def issueIndexManager = ComponentAccessor.getComponent(IssueIndexManager)
issueLinkManager.getInwardLinks(issue.id).each { issueLink ->
if (issueLink.issueLinkType.name == "Composition") {
def linkedIssue = issueLink.getSourceObject()
issueIndexManager.reIndexIssueObjects([linkedIssue])
}
}
推薦配置
有時候希望在出現問題時給出個性化提醒,默認工作流不提供這樣的彈出框提示,SR 提供下述腳本,支持 HTML 語言,比如在關閉還有前置未完成任務的時候彈出警告框。
可以直接寫 HTML ,也可以使用 groovy 的 MarkupBuilder,如果 issue 沒有前置阻斷校驗 字段,則彈出框不會顯示。
package com.onresolve.jira.groovy.test.scriptfields.scripts
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import groovy.xml.MarkupBuilder
import com.atlassian.jira.config.properties.APKeys
def issueLinkManager = ComponentAccessor.getIssueLinkManager()
def baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL)
def ListblockingIssues = []
issueLinkManager.getInwardLinks(issue.id).each { issueLink ->
if (issueLink.issueLinkType.name == "Blocks") {
def linkedIssue = issueLink.sourceObject
if (!linkedIssue.assigneeId && !linkedIssue.resolutionObject) {
blockingIssues.add(linkedIssue)
}
}
}
if (blockingIssues) {
StringWriter writer = new StringWriter()
MarkupBuilder builder = new MarkupBuilder(writer)
builder.div(class: "aui-message error shadowed") {
p(class: "title") {
span(class: "aui-icon icon-error", "")
strong("This issue is blocked by the following unresolved, unassigned issue(s):")
}
ul {
blockingIssues.each { anIssue ->
li {
a(href: "$baseUrl/browse/${anIssue.key}", anIssue.key)
i(": ${anIssue.summary} (${anIssue.statusObject.name})")
}
}
}
}
return writer
} else {
returnnull
}
Template |
HTML |
Searcher |
None |
在 issue 界面快速瞭解項目的模塊負責人
效果如下
返回第一個 用户,注意 對象 類型需要選擇 ApplicationUser 而非 User ;如果選擇 User,返回的結果會是匿名用户;為保證已有 issue 展示 顯示模塊負責人字段數據,創建字段後,需重建索引。
package com.onresolve.jira.groovy.test.scriptfields.scripts
def components = issue.componentObjects.toList()
if (components) {
return components?.first()?.componentLead
}
Template |
User Picker (single user) |
Searcher |
User Picker Searcher |
package com.onresolve.jira.groovy.test.scriptfields.scripts
def componentLeads = issue.componentObjects*.componentLead.unique()
componentLeads.removeAll([null])
componentLeads
Template |
User Picker (multiple users) |
Searcher |
Multi User Picker Searcher |
默認的版本僅顯示已發佈和未發佈,不顯示已歸檔版本。通過此腳本可以包含所有的版本,按時間排序。
package com.onresolve.jira.groovy.test.scriptfields.scripts
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.comparator.VersionComparator
def versionManager = ComponentAccessor.getVersionManager()
def versions = versionManager.getVersions(issue.projectObject)
def comparator = new VersionComparator()
def lowestFixVersion = issue.fixVersions.min(comparator)
def returnedVersions = versions.findAll {
comparator.compare(it, lowestFixVersion) < 0
}
log.debug("All prior versions: ${returnedVersions}")
return (lowestFixVersion ? returnedVersions : null)
Template |
Version |
Searcher |
Version Searcher |
日常工作中,經常會遇到有需要為 issue 設定默認值的場景。
以下舉幾個常用例子
import com.atlassian.jira.component.ComponentAccessor
importstatic com.atlassian.jira.issue.IssueFieldConstants.AFFECTED_VERSIONS
importstatic com.atlassian.jira.issue.IssueFieldConstants.ASSIGNEE
importstatic com.atlassian.jira.issue.IssueFieldConstants.COMPONENTS
if (getActionName() != "Create Issue") {
return// not the initial action, so don't set default values
}
// set Components
def projectComponentManager = ComponentAccessor.getProjectComponentManager()
def components = projectComponentManager.findAllForProject(issueContext.projectObject.id)
getFieldById(COMPONENTS).setFormValue(components.findAll { it.name in ["Support Question", "Frontend"] }*.id)
// set "Affects Versions" to the latest version
def versionManager = ComponentAccessor.getVersionManager()
def versions = versionManager.getVersions(issueContext.projectObject)
if (versions) {
getFieldById(AFFECTED_VERSIONS).setFormValue([versions.last().id])
}
// set Assignee
getFieldById(ASSIGNEE).setFormValue("admin")
下拉框默認值設定
import com.atlassian.jira.component.ComponentAccessor
// set a select list value -- also same for radio buttons
def faveFruitFld = getFieldByName("Favourite Fruit")
def optionsManager = ComponentAccessor.getOptionsManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def customField = customFieldManager.getCustomFieldObject(faveFruitFld.getFieldId())
def config = customField.getRelevantConfig(getIssueContext())
def options = optionsManager.getOptions(config)
def optionToSelect = options.find { it.value == "Oranges" }
faveFruitFld.setFormValue(optionToSelect.optionId)
// same example but setting a multiselect - also same for checkboxes fields
def subComponentFld = getFieldByName("Subcomponent")
customField = customFieldManager.getCustomFieldObject(subComponentFld.getFieldId())
config = customField.getRelevantConfig(getIssueContext())
options = optionsManager.getOptions(config)
def optionsToSelect = options.findAll { it.value in ["Oranges", "Lemons"] }
subComponentFld.setFormValue(optionsToSelect*.optionId)
二聯下拉默認設定
比如要設定如下的二聯 下拉,以下兩個為 ID
field.setFormValue([12345, 67890])
def optionsManager = ComponentAccessor.getOptionsManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def fieldName = "testCascadingSelectList"
def field = getFieldByName(fieldName)
def customField = customFieldManager.getCustomFieldObjectByName(fieldName)
def fieldConfig = customField.getRelevantConfig(getIssueContext())
def options = optionsManager.getOptions(fieldConfig)
def parentOption = options.find { it.value == "A" }
def childOption = parentOption?.childOptions?.find { it.value == "A1" }
field.setFormValue([parentOption.optionId, childOption.optionId])
Jira 原生權限管控無法限制不同角色在 項目中提交的 issue類型,比如測試只能提交測試用例,產品經理只能提交用户故事。
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.security.roles.ProjectRoleManager
importstatic com.atlassian.jira.issue.IssueFieldConstants.ISSUE_TYPE
def projectRoleManager = ComponentAccessor.getComponent(ProjectRoleManager)
def allIssueTypes = ComponentAccessor.constantsManager.allIssueTypeObjects
def user = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def issueTypeField = getFieldById(ISSUE_TYPE)
def remoteUsersRoles = projectRoleManager.getProjectRoles(user, issueContext.projectObject)*.name
def availableIssueTypes = []
if ("Users"in remoteUsersRoles) {
availableIssueTypes.addAll(allIssueTypes.findAll { it.name in ["Query", "General Request"] })
}
if ("Developers"in remoteUsersRoles) {
availableIssueTypes.addAll(allIssueTypes.findAll { it.name in ["Bug", "Task", "New Feature"] })
}
issueTypeField.setFieldOptions(availableIssueTypes)
系統默認的可見範圍選項很少,可通過下述操作豐富 評論可見的範圍,讓悄悄話講起來更方便
setFieldOptions 有兩個用法
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.security.roles.ProjectRoleManager
def projectRoleManager = ComponentAccessor.getComponent(ProjectRoleManager)
def groupManager = ComponentAccessor.getGroupManager()
def group = groupManager.getGroup("jira-administrators")
def role = projectRoleManager.getProjectRole("Administrators")
def formField = getFieldById("commentLevel")
formField.setRequired(true)
formField.setFieldOptions([group, role])
def formField = getFieldById("commentLevel")
formField.setRequired(true)
formField.setFieldOptions(["group:jira-administrators": "jira-administrators", "role:10002": "Administrators"])
最高頻羣內提問,如何限制子任務全關閉後才能關閉父任務
以下案例為 QA 類型任務完成後父任務方能被設定為已解決
passesCondition = true
def subTasks = issue.getSubTaskObjects()
subTasks.each {
if (it.issueType.name == "QA" && !it.resolution) {
passesCondition = false
}
}
import com.opensymphony.workflow.InvalidInputException
if (issue.resolution.name == "Fixed" && !issue.fixVersions) {
thrownew InvalidInputException("fixVersions",
"Fix Version/s is required when specifying Resolution of 'Fixed'")
}
import com.atlassian.jira.component.ComponentAccessor
def issueService = ComponentAccessor.getIssueService()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def subTasks = issue.getSubTaskObjects()
subTasks.each {
if (it.statusObject.name == "Open") {
def issueInputParameters = issueService.newIssueInputParameters()
issueInputParameters.with {
setResolutionId("1") // resolution of "Fixed"
setComment("*Resolving* as a result of the *Resolve* action being applied to the parent.")
setSkipScreenCheck(true)
}
// validate and transition subtask
def validationResult = issueService.validateTransition(user, it.id, 5, issueInputParameters)
if (validationResult.isValid()) {
def issueResult = issueService.transition(user, validationResult)
if (!issueResult.isValid()) {
log.warn("Failed to transition subtask ${it.key}, errors: ${issueResult.errorCollection}")
}
} else {
log.warn("Could not transition subtask ${it.key}, errors: ${validationResult.errorCollection}")
}
}
}
當你的 客服團隊處理工單時需要知會所有關聯 issue,使用以下 腳本。為保持用户不被騷擾頻繁更新的評論更新通知騷擾以下腳本已做專門優化。
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.mail.Email
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.servicedesk.api.organization.CustomerOrganization
import com.atlassian.servicedesk.api.organization.OrganizationService
import com.atlassian.servicedesk.api.util.paging.SimplePagedRequest
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
@WithPlugin("com.atlassian.servicedesk")
final def LINK_NAME = "causes"
def issueLinks = ComponentAccessor.getIssueLinkManager().getOutwardLinks(issue.getId())
def causedByIssues = ComponentAccessor.getIssueLinkManager().getOutwardLinks(issue.getId())?.findAll {
it.issueLinkType.outward == LINK_NAME
}
if (!causedByIssues) {
log.debug "Does not cause any issues"
return
}
causedByIssues.each {
def destinationIssue = it.destinationObject
def watcherManager = ComponentAccessor.watcherManager
// this should be an admin you wish to use inside the script OR currentUser
def adminUser = ComponentAccessor.userManager.getUserByKey("admin")
def strSubject = "An issue linked to ${destinationIssue.getKey()} has been resolved"
def baseURL = ComponentAccessor.getApplicationProperties().getString("jira.baseurl")
def strIssueURL = "${issue.getKey()}"
def strDestinationURL = "${destinationIssue.getKey()}"
def strBody = """${strIssueURL} has been resolved.
This issue has a "${LINK_NAME}" issue link which points to ${strDestinationURL}.
You received this notification because you are watching ${strDestinationURL}."""
def emailAddressTo = destinationIssue.reporterUser ? destinationIssue.reporterUser.emailAddress : adminUser.emailAddress
def emailAddressesCC = []
emailAddressesCC = watcherManager.getWatchers(destinationIssue, Locale.ENGLISH)?.collect { it.emailAddress }
emailAddressesCC.addAll(getOrganizationMembersEmailsInIssue(destinationIssue, adminUser))
emailAddressesCC.addAll(getParticipantsEmailsInIssue(destinationIssue))
emailAddressesCC = emailAddressesCC.unique()
emailAddressesCC = emailAddressesCC.join(",")
sendEmail(emailAddressTo, emailAddressesCC, strSubject, strBody)
}
def sendEmail(String to, String cc, String subject, String body) {
log.debug "Attempting to send email..."
def mailServer = ComponentAccessor.getMailServerManager().getDefaultSMTPMailServer()
if (mailServer) {
Email email = new Email(to)
email.setCc(cc)
email.setSubject(subject)
email.setBody(body)
email.setMimeType("text/html")
mailServer.send(email)
log.debug("Mail sent to (${to}) and cc'd (${cc})")
} else {
log.warn("Please make sure that a valid mailServer is configured")
}
}
ListgetOrganizationMembersEmailsInIssue(Issue issue, ApplicationUser adminUser) {
def organisationService = ComponentAccessor.getOSGiComponentInstanceOfType(OrganizationService)
def cf = ComponentAccessor.customFieldManager.getCustomFieldObjectByName("Organizations")
def emailAddresses = []
(issue.getCustomFieldValue(cf) as List)?.each {
def pageRequest = new SimplePagedRequest(0, 50)
def usersInOrganizationQuery = organisationService.newUsersInOrganizationQuery().pagedRequest(pageRequest).customerOrganization(it).build()
// this is a paged response, it will return only the first 50 results, if you have more users in an organization
// then you will need to iterate though all the page responses
def pagedResponse = organisationService.getUsersInOrganization(adminUser, usersInOrganizationQuery)
if (pagedResponse.isLeft()) {
log.warn pagedResponse.left().get()
} else {
emailAddresses.addAll(pagedResponse.right().get().results.collect { it.emailAddress })
}
}
emailAddresses
}
ListgetParticipantsEmailsInIssue(Issue issue) {
def cf = ComponentAccessor.customFieldManager.getCustomFieldObjectByName("Request participants")
def cfVal = issue.getCustomFieldValue(cf)?.collect { it.emailAddress }
cfVal
}
每週複製 issue
其實 SR 與大量插件有做過集成,以下僅舉兩個例子,除此之外還有 Structure,Jira Agile,Midori,SmartDraw Diagram,Test Management for Jira ,Insight
根據tempo自定義工時屬性,展示在issue界面
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.worklog.Worklog
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.tempoplugin.core.workattribute.api.WorkAttributeService
import com.tempoplugin.core.workattribute.api.WorkAttributeValueService
@WithPlugin("is.origo.jira.tempo-plugin")
@PluginModule
WorkAttributeService workAttributeService
@PluginModule
WorkAttributeValueService workAttributeValueService
def worklogManager = ComponentAccessor.getWorklogManager()
def worklogs = worklogManager.getByIssue(issue)
def overtimeLogs = worklogs.findAll { worklog ->
def attribute = workAttributeService.getWorkAttributeByKey("_Overtime_").returnedValue
workAttributeValueService.getWorkAttributeValueByWorklogAndWorkAttribute(worklog.id, attribute.id).returnedValue
}
overtimeLogs.sum { Worklog worklog ->
worklog.timeSpent
} as Long
// if no overtime worklogs just return null
#if ($value)
$jiraDurationUtils.getFormattedDuration($value)
#end
Template |
Custom |
Searcher |
Duration Searcher |
自動登記工時
import com.atlassian.jira.component.ComponentAccessor
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.tempoplugin.common.TempoDateTimeFormatter
import com.tempoplugin.core.datetime.api.TempoDate
import com.tempoplugin.worklog.v4.rest.InputWorklogsFactory
import com.tempoplugin.worklog.v4.rest.TimesheetWorklogBean
import com.tempoplugin.worklog.v4.services.WorklogService
import org.apache.log4j.Level
import org.apache.log4j.Logger
import java.sql.Timestamp
import java.time.Instant
import java.time.temporal.ChronoUnit
@WithPlugin("is.origo.jira.tempo-plugin")
@PluginModule
WorklogService worklogService
@PluginModule
InputWorklogsFactory inputWorklogsFactory
// Set log level
def log = Logger.getLogger(getClass())
log.setLevel(Level.ERROR)
// Status to be counted
final statusName = ''
def changeHistoryManager = ComponentAccessor.changeHistoryManager
// Calculate the time we entered this state
def changeHistoryItems = changeHistoryManager.getAllChangeItems(issue).reverse()
def timeLastStatus = changeHistoryItems.find {
it.field == "status" && it.toValues.values().contains(statusName)
}?.created as Timestamp
final chronoUnit = ChronoUnit.SECONDS
def timeToLog = chronoUnit.between(timeLastStatus.toInstant(), Instant.now())
// Remaining estimate is calculated if and only if there is original estimate and it is greater than time to log
def remaining = issue.estimate && issue.estimate > timeToLog ? (issue.estimate - timeToLog) : 0
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def startDate = TempoDateTimeFormatter.formatTempoDate(TempoDate.now())
// Add all fields needed to create a new worklog
def timesheetWorklogBean = new TimesheetWorklogBean.Builder()
.issueIdOrKey(issue.key)
.comment('Auto-created worklog')
.startDate(startDate)
.workerKey(currentUser.key)
.timeSpentSeconds(timeToLog)
.remainingEstimate(remaining)
.build()
def inputWorklogs = inputWorklogsFactory.buildForCreate(timesheetWorklogBean)
worklogService.createTempoWorklogs(inputWorklogs)
行動處綁定 SR 腳本
JIRA 原生郵件內容,條件可編輯性不是特別強,通過腳本可以滿足各類業務需求。
import com.atlassian.mail.MailUtils
def subject = message.getSubject()
def body = MailUtils.getBody(message)
log.debug "${subject}"
log.debug "${body}"
試運行
若原來已存在則 附上內容,若不存在則創建新的 issue。
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.service.util.ServiceUtils
import com.atlassian.jira.service.util.handler.MessageUserProcessor
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserManager
import com.atlassian.mail.MailUtils
def userManager = ComponentAccessor.getComponent(UserManager)
def projectManager = ComponentAccessor.getProjectManager()
def issueFactory = ComponentAccessor.getIssueFactory()
def messageUserProcessor = ComponentAccessor.getComponent(MessageUserProcessor)
def subject = message.getSubject() as String
def issue = ServiceUtils.findIssueObjectInString(subject)
if (issue) {
return
}
ApplicationUser user = userManager.getUserByName("admin")
ApplicationUser reporter = messageUserProcessor.getAuthorFromSender(message) ?: user
def project = projectManager.getProjectObjByKey("SRTESTPRJ")
def issueObject = issueFactory.getIssue()
issueObject.setProjectObject(project)
issueObject.setSummary(subject)
issueObject.setDescription(MailUtils.getBody(message))
issueObject.setIssueTypeId(project.issueTypes.find { it.name == "Bug" }.id)
issueObject.setReporter(reporter)
messageHandlerContext.createIssue(user, issueObject)
當一個生產事故解決時,需自動創建一個根因分析 工單
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.servicedesk.api.requesttype.RequestTypeService
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
@WithPlugin("com.atlassian.servicedesk")
def requestTypeService = ComponentAccessor.getOSGiComponentInstanceOfType(RequestTypeService)
if (issue.issueType.name == "Incident") {
def sourceIssueRequestTypeQuery = requestTypeService
.newQueryBuilder()
.issue(issue.id)
.requestOverrideSecurity(true)
.build()
def requestTypeEither = requestTypeService.getRequestTypes(currentUser, sourceIssueRequestTypeQuery)
if (requestTypeEither.isLeft()) {
log.warn "${requestTypeEither.left().get()}"
return false
}
def requestType = requestTypeEither.right.results[0]
if (requestType.name == "Bug Report" && issue.resolution.name == "Bug Reproduced") {
return true
}
}
return false
wiki 用户不一定擁有訪問 JIRA 的權限,當工單解決時自動創建一份根因分析報告
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.properties.APKeys
import com.atlassian.sal.api.net.Request
import com.atlassian.sal.api.net.Response
import com.atlassian.sal.api.net.ResponseException
import com.atlassian.sal.api.net.ResponseHandler
import com.atlassian.servicedesk.api.organization.OrganizationService
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.opensymphony.workflow.WorkflowContext
import groovy.json.JsonBuilder
import groovy.xml.MarkupBuilder
@WithPlugin("com.atlassian.servicedesk")
/*Fetch and check for the confluence link*/
def applicationLinkService = ComponentAccessor.getComponent(ApplicationLinkService)
def confluenceLink = applicationLinkService.getPrimaryApplicationLink(ConfluenceApplicationType)
/*If your issue isn't an incident, you don't want to create a RCA ticket*/
if (issue.issueType.name != "Incident") {
return
}
/*Check that the confluence link exists*/
if (!confluenceLink) {
log.error "There is no confluence link setup"
return
}
def authenticatedRequestFactory = confluenceLink.createAuthenticatedRequestFactory()
/*Start getting information about the issue from Service desk*/
def issueLinkManager = ComponentAccessor.getIssueLinkManager()
def commentManager = ComponentAccessor.getCommentManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def organizationService = ComponentAccessor.getOSGiComponentInstanceOfType(OrganizationService)
/*SLA related fields*/
def timeFirstResponse = issue.getCustomFieldValue(customFieldManager.getCustomFieldObjectByName("Time to first response"))
def timeResolution = issue.getCustomFieldValue(customFieldManager.getCustomFieldObjectByName("Time to resolution"))
def organizations = issue.getCustomFieldValue(customFieldManager.getCustomFieldObjectByName("Organizations"))
def currentUserId = ((WorkflowContext) transientVars.get("context")).getCaller()
def currentUser = ComponentAccessor.getUserManager().getUserByKey(currentUserId)
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.h2("This is the RCA analysis thread for the issue above.")
xml.p("${issue.summary}")
xml.p("This issue was raised by ${issue.reporter.name} on ${issue.getCreated()} " +
"and resolved by ${currentUser.name} with resolution ${issue.getResolution().name}")
xml.h3("Time to first response:")
xml.p("Start date: ${timeFirstResponse.completeSLAData?.startTime[0].toDate().toString()}")
xml.p("Stop date: ${timeFirstResponse.completeSLAData?.stopTime[0].toDate().toString()}")
xml.h3("Times to resolution:")
xml.p("Start date(s): ${timeResolution.completeSLAData?.startTime[0].toDate().toString()}")
xml.p("Stop date(s): ${timeResolution.completeSLAData?.stopTime[0].toDate().toString()}")
xml.h3("Description:")
xml.p("${issue.description}
")
//You might want to log information about your users and organizations.
xml.h3("Organizations")
organizations?.each {
xml.p("${it.name}")
def usersEither = organizationService.getUsersInOrganization(currentUser, organizationService.newUsersInOrganizationQuery().customerOrganization(it).build())
if (usersEither.isLeft()) {
log.warn usersEither.left().get()
return
}
usersEither.right().get().results.collect { "${it.displayName}" }.each {
xml.p(it)
}
}
//You want to collect the outward links of your issue.
def outwardLinks = issueLinkManager.getOutwardLinks(issue.id)
xml.h3("Outward Issue Links")
if (outwardLinks instanceof List) {
outwardLinks?.collect { buildIssueURL(it.destinationObject.key) }?.join(" ").each {
xml.p(it)
}
} else {
xml.p(buildIssueURL(outwardLinks.destinationObject.key))
}
//You want to collect the inward links of your issue.
def inwardLinks = issueLinkManager.getInwardLinks(issue.id)
xml.h3("Inward Issue Links")
if (inwardLinks instanceof List) {
inwardLinks?.collect { buildIssueURL(it.destinationObject.key) }?.join(" ").each {
xml.p(it)
}
} else {
xml.p(buildIssueURL(inwardLinks.destinationObject.key))
}
//You might also want to collect the comments on the issue:
xml.h3("Comments")
commentManager.getComments(issue)?.collect { "${it.getAuthorFullName()} : $it.body" }.each {
xml.p(it)
}
//Here you parse the whole of the information collected into the RCA ticket.
def params = [
type : "page",
title: "RCA analysis: ${issue.key}",
space: [
key: "TEST"//This should be the name of your space, you should set it accordingly
],
body : [
storage: [
value : writer.toString(),
representation: "storage"
]
]
]
//This is used to send a REST request to the Confluence link.
authenticatedRequestFactory
.createRequest(Request.MethodType.POST, "rest/api/content")
.addHeader("Content-Type", "application/json")
.setRequestBody(new JsonBuilder(params).toString())
.execute(new ResponseHandler() {
@Override
void handle(Response response) throws ResponseException {
if (response.statusCode != HttpURLConnection.HTTP_OK) {
thrownew Exception(response.getResponseBodyAsString())
}
}
})
//This is an aux function to build the URL for the issue.
String buildIssueURL(String issueKey) {
def baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL)
"""
<a </ahref="$baseUrl/browse/$issueKey">$issueKey
"""
}
一直被模仿從未被超越,如 Power Scripts | Jira Workflow Automation,Jira Workflow Toolbox , JSU Automation Suite for Jira Workflows , Jira Misc Workflow Extensions (JMWE)
Tom Zhu
0 comments