Advertisement
Guest User

Untitled

a guest
Jul 18th, 2018
167
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 57.24 KB | None | 0 0
  1. // SETTINGS FOR THE SCRIPT
  2. // enter the credentials of the Cloud user with admin permissions
  3. def username = "jiratestuser1"
  4. def password = "Jump2it!"
  5.  
  6. // enter the corresponding statuses of the source and destination issue
  7. // omit statuses if they are the same
  8. final def workflowMapping = [
  9. "Source Status A" : "Dest Status B",
  10. "Source Status C" : "Dest Status D",
  11. "Source Status E" : "Dest Status F",
  12. ]
  13. // if requested by the support team, fill in the project / workflow configs:
  14. def projectWorkflowSchemeMapping = [ "SD" : 11103L ]
  15. def workflowSchemeMapping = [ 11103L : """{"id":11103,"name":"Service Desk Workflow Scheme","defaultWorkflow":"Service Desk Workflow","issueTypeMappings":{},"draft":false,"self":"https://foo.atlassian.net/rest/api/2/workflowscheme/11103","issueTypes":{"10304":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10304","id":"10304","description":"A digital or physical item to be tracked.","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=11772&avatarType=issuetype","name":"Asset","subtask":false,"avatarId":11772},"10104":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10104","id":"10104","description":"A new feature of the product, which has yet to be developed.","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10311&avatarType=issuetype","name":"New Feature","subtask":false,"avatarId":10311},"10004":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10004","id":"10004","description":"The sub-task of the issue","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10316&avatarType=issuetype","name":"Sub-task","subtask":true,"avatarId":10316},"10102":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10102","id":"10102","description":"Issue template","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=11404&avatarType=issuetype","name":"Issue template (WBSGantt)","subtask":false,"avatarId":11404},"10001":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10001","id":"10001","description":"Created by Jira Agile - do not edit or delete. Issue type for a user story.","iconUrl":"https://foo.atlassian.net/images/icons/issuetypes/story.svg","name":"Story","subtask":false},"10002":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10002","id":"10002","description":"A problem which impairs or prevents the functions of the product.","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10303&avatarType=issuetype","name":"Bug","subtask":false,"avatarId":10303},"10200":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10200","id":"10200","description":"Created by JIRA Service Desk.","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10544&avatarType=issuetype","name":"Change","subtask":false},"10202":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10202","id":"10202","description":"For system outages or incidents. Created by JIRA Service Desk.","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10542&avatarType=issuetype","name":"Incident","subtask":false},"10203":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10203","id":"10203","description":"Track underlying causes of incidents. Created by JIRA Service Desk.","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10545&avatarType=issuetype","name":"Problem","subtask":false},"10204":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10204","id":"10204","description":"Created by JIRA Service Desk.","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10543&avatarType=issuetype","name":"Service Request","subtask":false},"10205":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10205","id":"10205","description":"UAT Feedback via Issue Collector","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10322&avatarType=issuetype","name":"User Feedback","subtask":false},"10201":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10201","id":"10201","description":"Improvement to an existing feature or functionality","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10310&avatarType=issuetype","name":"Enhancement","subtask":false,"avatarId":10310},"10301":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10301","id":"10301","description":"","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10322&avatarType=issuetype","name":"Design","subtask":false,"avatarId":10322},"10303":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10303","id":"10303","description":"","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10315&avatarType=issuetype","name":"Marketing / Social","subtask":false,"avatarId":10315},"10306":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10306","id":"10306","description":"Created by Jira Agile - do not edit or delete. Issue type for a user story.","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10300&avatarType=issuetype","name":"Story","subtask":false,"avatarId":10300},"10300":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10300","id":"10300","description":"A document requiring approval.","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10306&avatarType=issuetype","name":"Document","subtask":false,"avatarId":10306},"10302":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10302","id":"10302","description":"","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10306&avatarType=issuetype","name":"Communication","subtask":false,"avatarId":10306},"10003":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10003","id":"10003","description":"A task that needs to be done.","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10318&avatarType=issuetype","name":"Task","subtask":false,"avatarId":10318},"10101":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10101","id":"10101","description":"","iconUrl":"https://foo.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10304&avatarType=issuetype","name":"Video","subtask":false,"avatarId":10304},"10000":{"self":"https://foo.atlassian.net/rest/api/2/issuetype/10000","id":"10000","description":"Created by Jira Software - do not edit or delete. Issue type for a big user story that needs to be broken down.","iconUrl":"https://foo.atlassian.net/images/icons/issuetypes/epic.svg","name":"Epic","subtask":false}}}""".toString()]
  16. def workflowDetailsMapping = [ "Service Desk Workflow" : """{"name":"Service Desk Workflow","description":"","sources":[{"fromStatus":{"statusCategory":{"translatedName":"To Do","primaryAlias":"To Do","colorName":"blue-gray","aliases":["To Do"],"sequence":2,"name":"New","key":"new","id":2},"iconUrl":"/images/icons/statuses/open.png","description":"The issue is open and ready for the assignee to start work on it.","name":"Open","id":"1"},"targets":[{"toStatus":{"statusCategory":{"translatedName":"In Progress","primaryAlias":"In Progress","colorName":"yellow","aliases":[],"sequence":3,"name":"In Progress","key":"indeterminate","id":4},"iconUrl":"/images/icons/statuses/inprogress.png","description":"This issue is being actively worked on at the moment by the assignee.","name":"Writing / Editing","id":"3"},"transitionName":"Start Progress"},{"toStatus":{"statusCategory":{"translatedName":"Done","primaryAlias":"Done","colorName":"green","aliases":["Done"],"sequence":4,"name":"Complete","key":"done","id":3},"iconUrl":"/images/icons/statuses/resolved.png","description":"A resolution has been taken, and it is awaiting verification by reporter. From here issues are either reopened, or are closed.","name":"Resolved","id":"5"},"transitionName":"Resolve Issue","screen":{"id":3,"name":"Resolve Issue Screen"}},{"toStatus":{"statusCategory":{"translatedName":"Done","primaryAlias":"Done","colorName":"green","aliases":["Done"],"sequence":4,"name":"Complete","key":"done","id":3},"iconUrl":"/images/icons/statuses/closed.png","description":"The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.","name":"Closed","id":"6"},"transitionName":"Close Issue","screen":{"id":3,"name":"Resolve Issue Screen"}}]},{"fromStatus":{"statusCategory":{"translatedName":"In Progress","primaryAlias":"In Progress","colorName":"yellow","aliases":[],"sequence":3,"name":"In Progress","key":"indeterminate","id":4},"iconUrl":"/images/icons/statuses/inprogress.png","description":"This issue is being actively worked on at the moment by the assignee.","name":"Writing / Editing","id":"3"},"targets":[{"toStatus":{"statusCategory":{"translatedName":"To Do","primaryAlias":"To Do","colorName":"blue-gray","aliases":["To Do"],"sequence":2,"name":"New","key":"new","id":2},"iconUrl":"/images/icons/statuses/open.png","description":"The issue is open and ready for the assignee to start work on it.","name":"Open","id":"1"},"transitionName":"Stop Progress"},{"toStatus":{"statusCategory":{"translatedName":"Done","primaryAlias":"Done","colorName":"green","aliases":["Done"],"sequence":4,"name":"Complete","key":"done","id":3},"iconUrl":"/images/icons/statuses/resolved.png","description":"A resolution has been taken, and it is awaiting verification by reporter. From here issues are either reopened, or are closed.","name":"Resolved","id":"5"},"transitionName":"Resolve Issue","screen":{"id":3,"name":"Resolve Issue Screen"}},{"toStatus":{"statusCategory":{"translatedName":"Done","primaryAlias":"Done","colorName":"green","aliases":["Done"],"sequence":4,"name":"Complete","key":"done","id":3},"iconUrl":"/images/icons/statuses/closed.png","description":"The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.","name":"Closed","id":"6"},"transitionName":"Close Issue","screen":{"id":3,"name":"Resolve Issue Screen"}}]},{"fromStatus":{"statusCategory":{"translatedName":"Done","primaryAlias":"Done","colorName":"green","aliases":["Done"],"sequence":4,"name":"Complete","key":"done","id":3},"iconUrl":"/images/icons/statuses/resolved.png","description":"A resolution has been taken, and it is awaiting verification by reporter. From here issues are either reopened, or are closed.","name":"Resolved","id":"5"},"targets":[{"toStatus":{"statusCategory":{"translatedName":"Done","primaryAlias":"Done","colorName":"green","aliases":["Done"],"sequence":4,"name":"Complete","key":"done","id":3},"iconUrl":"/images/icons/statuses/closed.png","description":"The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.","name":"Closed","id":"6"},"transitionName":"Close Issue","screen":{"id":2,"name":"Workflow Screen"}},{"toStatus":{"statusCategory":{"translatedName":"To Do","primaryAlias":"To Do","colorName":"blue-gray","aliases":["To Do"],"sequence":2,"name":"New","key":"new","id":2},"iconUrl":"/images/icons/statuses/reopened.png","description":"This issue was once resolved, but the resolution was deemed incorrect. From here issues are either marked assigned or resolved.","name":"Reopened","id":"4"},"transitionName":"Reopen Issue","screen":{"id":2,"name":"Workflow Screen"}}]},{"fromStatus":{"statusCategory":{"translatedName":"To Do","primaryAlias":"To Do","colorName":"blue-gray","aliases":["To Do"],"sequence":2,"name":"New","key":"new","id":2},"iconUrl":"/images/icons/statuses/reopened.png","description":"This issue was once resolved, but the resolution was deemed incorrect. From here issues are either marked assigned or resolved.","name":"Reopened","id":"4"},"targets":[{"toStatus":{"statusCategory":{"translatedName":"Done","primaryAlias":"Done","colorName":"green","aliases":["Done"],"sequence":4,"name":"Complete","key":"done","id":3},"iconUrl":"/images/icons/statuses/resolved.png","description":"A resolution has been taken, and it is awaiting verification by reporter. From here issues are either reopened, or are closed.","name":"Resolved","id":"5"},"transitionName":"Resolve Issue","screen":{"id":3,"name":"Resolve Issue Screen"}},{"toStatus":{"statusCategory":{"translatedName":"Done","primaryAlias":"Done","colorName":"green","aliases":["Done"],"sequence":4,"name":"Complete","key":"done","id":3},"iconUrl":"/images/icons/statuses/closed.png","description":"The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.","name":"Closed","id":"6"},"transitionName":"Close Issue","screen":{"id":3,"name":"Resolve Issue Screen"}},{"toStatus":{"statusCategory":{"translatedName":"In Progress","primaryAlias":"In Progress","colorName":"yellow","aliases":[],"sequence":3,"name":"In Progress","key":"indeterminate","id":4},"iconUrl":"/images/icons/statuses/inprogress.png","description":"This issue is being actively worked on at the moment by the assignee.","name":"Writing / Editing","id":"3"},"transitionName":"Start Progress"}]},{"fromStatus":{"statusCategory":{"translatedName":"Done","primaryAlias":"Done","colorName":"green","aliases":["Done"],"sequence":4,"name":"Complete","key":"done","id":3},"iconUrl":"/images/icons/statuses/closed.png","description":"The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.","name":"Closed","id":"6"},"targets":[{"toStatus":{"statusCategory":{"translatedName":"To Do","primaryAlias":"To Do","colorName":"blue-gray","aliases":["To Do"],"sequence":2,"name":"New","key":"new","id":2},"iconUrl":"/images/icons/statuses/reopened.png","description":"This issue was once resolved, but the resolution was deemed incorrect. From here issues are either marked assigned or resolved.","name":"Reopened","id":"4"},"transitionName":"Reopen Issue","screen":{"id":2,"name":"Workflow Screen"}}]}],"id":11212,"displayName":"Service Desk Workflow","admin":true}""".toString()]
  17. // END SETTINGS FOR THE SCRIPT
  18.  
  19. issue.assignee = nodeHelper.getUserByEmail(replica.assignee?.email)
  20.  
  21.  
  22. // sync comments
  23. issue.comments = commentHelper.mergeComments(issue, replica, { c ->
  24. c.executor = nodeHelper.getUserByEmail(c.author.email)
  25. if (c.executor == null) {
  26. c.body = "[${c.author.displayName}|mailto:${c.author.email}] commented:\n${c.body}".toString()
  27. }
  28. c
  29. })
  30.  
  31. // EPIC NAME AND LINK SYNC
  32. ({
  33. final def jIssue = issue
  34.  
  35. final def injector = play.api.Play$.MODULE$.current().injector()
  36. def issueLevelError = { String msg ->
  37. new com.exalate.api.exception.IssueTrackerException(msg)
  38. }
  39. def issueLevelError2 = { String msg, Throwable e ->
  40. new com.exalate.api.exception.IssueTrackerException(msg, e)
  41. }
  42. def await = { scala.concurrent.Future<?> f -> scala.concurrent.Await$.MODULE$.result(f, scala.concurrent.duration.Duration$.MODULE$.Inf()) }
  43. def orNull = { scala.Option<?> opt -> opt.isDefined() ? opt.get() : null }
  44. def none = { scala.Option$.MODULE$.<?>empty() }
  45. def pair = { l, r -> scala.Tuple2$.MODULE$.<?, ?>apply(l, r) }
  46. def seq = { ... ts ->
  47. def list = Arrays.asList(ts)
  48. def scalaBuffer = scala.collection.JavaConversions.asScalaBuffer(list)
  49. scalaBuffer.toSeq()
  50. }
  51. def getGeneralSettings = {
  52. def gsp = injector.instanceOf(com.exalate.api.persistence.issuetracker.jcloud.IJCloudGeneralSettingsPersistence.class)
  53. def gsOpt = await(gsp.get())
  54. def gs = orNull(gsOpt)
  55. gs
  56. }
  57. final def gs = getGeneralSettings()
  58.  
  59. def removeTailingSlash = { String str -> str.trim().replace("/+\$","") }
  60. final def jiraCloudUrl = removeTailingSlash(gs.issueTrackerUrl)
  61. def getFieldsJson = {
  62. //"com.pyxis.greenhopper.jira:gh-epic-link"
  63.  
  64. def fieldsResponse
  65. try {
  66. fieldsResponse = await(httpClient.thisJira("/rest/api/2/field", "GET", null, null).get())
  67. } catch (Exception e) {
  68. throw relationLevelError2("Unable to get the fields json, please contact Exalate Support: " + e.message, e)
  69. }
  70. if (fieldsResponse.status() != 200) {
  71. throw relationLevelError("Can not get fields (status "+ fieldsResponse.status() +"), please contact Exalate Support: "+ fieldsResponse.body())
  72. }
  73. groovy.json.JsonSlurper s = new groovy.json.JsonSlurper()
  74. /*
  75. [..., {"id":"customfield_10990","key":"customfield_10990","name":"Epic Link","custom":true,"orderable":true,"navigable":true,"searchable":true,"clauseNames":["cf[10990]","Epic Link"],"schema":{"type":"any","custom":"com.pyxis.greenhopper.jira:gh-epic-link","customId":10990}, ...}]
  76. */
  77. def fieldsJson
  78. try {
  79. fieldsJson = s.parseText(fieldsResponse.body())
  80. } catch (Exception e) {
  81. throw relationLevelError2("Can not parse fields json, please contact Exalate Support: " + fieldsResponse.body(), e)
  82. }
  83. if (!(fieldsJson instanceof List)) {
  84. throw relationLevelError("Fields json has unrecognized strucutre, please contact Exalate Support: " + fieldsResponse.body())
  85. }
  86. fieldsJson as List<Map<String, Object>>
  87. }
  88. final def fieldsJson = getFieldsJson().findAll { it.schema instanceof Map }
  89. final def epicLinkCfJson = fieldsJson.find { it.schema.custom == "com.pyxis.greenhopper.jira:gh-epic-link" }
  90. final def epicNameCfJson = fieldsJson.find { it.schema.custom == "com.pyxis.greenhopper.jira:gh-epic-label" }
  91.  
  92. def updateIssue = { com.exalate.basic.domain.BasicIssueKey issueKeyToUpdate, Map<String, Object> json ->
  93. // PUT /rest/api/2/issue/{issueIdOrKey}
  94. /*
  95. {
  96. "fields": {
  97. "summary": "This is a shorthand for a set operation on the summary field",
  98. "customfield_10010": 1,
  99. "customfield_10000": "This is a shorthand for a set operation on a text custom field"
  100. }
  101. }
  102. */
  103. def jsonStr = groovy.json.JsonOutput.toJson(json)
  104. def response
  105. try {
  106. def writable = play.api.http.Writeable$.MODULE$.wString(play.api.mvc.Codec.utf_8())
  107.  
  108. //noinspection GroovyAssignabilityCheck
  109. response = await(await(httpClient.authenticate(
  110. none(),
  111. httpClient
  112. .ws()
  113. .url(jiraCloudUrl+"/rest/api/2/issue/"+issueKeyToUpdate.id)
  114. .withHeaders(seq(pair("Content-Type", "application/json")))
  115. .withBody(jsonStr, writable)
  116. .withMethod("PUT"),
  117. gs
  118. )).execute())
  119. } catch (Exception e) {
  120. throw issueLevelError2("Unable to update issue `"+ issueKeyToUpdate.URN +"`, please contact Exalate Support: \n" +
  121. "PUT "+jiraCloudUrl+"/rest/api/2/issue/"+issueKeyToUpdate.id+"\nBody: "+jsonStr+"\nError Message:"+ e.message, e)
  122. }
  123. if (response.status() != 204) {
  124. throw issueLevelError("Can not update issue `"+ issueKeyToUpdate.URN +"` (status "+ response.status() +"), please contact Exalate Support: \nPUT "+jiraCloudUrl+"/rest/api/2/issue/"+issueKeyToUpdate.id+"\nBody: "+jsonStr+"\nResponse:"+ response.body())
  125. }
  126. }
  127. def getLocalIssue = { remoteSuspectId, currentIssue ->
  128. if(replica.id.equals((remoteSuspectId as Long) as String)) {
  129. return currentIssue
  130. }
  131. nodeHelper.getLocalIssueFromRemoteId(remoteSuspectId as Long)
  132. }
  133. def getLocalEpicIssue = { currentIssue ->
  134. def epicContext = replica.customKeys."epicContext"
  135. if (!(epicContext instanceof Map)) {
  136. return null
  137. }
  138. def remoteEpicId = epicContext.epic.id
  139. getLocalIssue(remoteEpicId, currentIssue)
  140. }
  141.  
  142.  
  143. // EPIC NAME SYNC
  144. if (replica.customKeys."Epic Name" != null) {
  145. def cf = issue.customFields[epicNameCfJson.schema.customId as String]
  146. if (cf == null) {
  147. throw issueLevelError("Can not find the `Epic Name` custom field by id `"+epicNameCfJson.schema.customId+"`, please contact Exalate Support")
  148. }
  149. cf.value = replica.customKeys."Epic Name"
  150. }
  151. // END: EPIC NAME SYNC
  152.  
  153. // EPIC SYNC
  154. // try to link all the stories to the epic:
  155. def localEpicJissue = getLocalEpicIssue(jIssue)
  156. if (localEpicJissue != null) {
  157. replica
  158. .customKeys
  159. ."epicContext"
  160. .stories
  161. .collect { story -> getLocalIssue(story.id, jIssue) }
  162. .findAll { it != null }
  163. .each { localStory ->
  164. // log.debug("linking the localStory `"+localStory.key+"` to epic `"+ localEpicJissue.key +"` for the issue `"+ jIssue.key +"` for remote issue `"+ replica.key +"`")
  165. updateIssue(
  166. new com.exalate.basic.domain.BasicIssueKey(localStory.id as Long, localStory.key),
  167. [
  168. "fields" : [
  169. (epicLinkCfJson.key) : localEpicJissue.key
  170. ]
  171. ]
  172. )
  173. }
  174. }
  175. def epicLinkCfIdStr = epicLinkCfJson?.schema?.customId as String
  176. def epicLinkCfValueInternal = issue.customFields[epicLinkCfIdStr].value
  177. issue.customFields.remove(epicLinkCfIdStr)
  178. if (replica.customKeys."epicContext" == null &&
  179. epicLinkCfValueInternal != null) {
  180. issue.customFields[epicLinkCfIdStr].value = null
  181. }
  182. // END: EPIC SYNC
  183. })()
  184. //END: EPIC NAME AND LINK SYNC
  185.  
  186. // STATUS SYNC
  187. ({
  188. try {
  189. def jIssue = issue
  190. def localExIssueKey = issueKey
  191. final def injector = play.api.Play$.MODULE$.current().injector()
  192. def issueLevelError = { String msg ->
  193. new com.exalate.api.exception.IssueTrackerException(msg)
  194. }
  195. def issueLevelError2 = { String msg, Throwable e ->
  196. new com.exalate.api.exception.IssueTrackerException(msg, e)
  197. }
  198. def await = { scala.concurrent.Future<?> f -> scala.concurrent.Await$.MODULE$.result(f, scala.concurrent.duration.Duration$.MODULE$.Inf()) }
  199. def orNull = { scala.Option<?> opt -> opt.isDefined() ? opt.get() : null }
  200. def none = { scala.Option$.MODULE$.<?> empty() }
  201. def pair = { l, r -> scala.Tuple2$.MODULE$.<?, ?> apply(l, r) }
  202. def seq = { ... ts ->
  203. def list = Arrays.asList(ts)
  204. def scalaBuffer = scala.collection.JavaConversions.asScalaBuffer(list)
  205. scalaBuffer.toSeq()
  206. }
  207. def getGeneralSettings = {
  208. def gsp = injector.instanceOf(com.exalate.api.persistence.issuetracker.jcloud.IJCloudGeneralSettingsPersistence.class)
  209. def gsOpt = await(gsp.get())
  210. def gs = orNull(gsOpt)
  211. gs
  212. }
  213. final def gs = getGeneralSettings()
  214.  
  215. def removeTailingSlash = { String str -> str.trim().replace("/+\$", "") }
  216. final def jiraCloudUrl = removeTailingSlash(gs.issueTrackerUrl)
  217.  
  218.  
  219. def getWorkflowSchemeIdForProject = { String projectKey ->
  220. def wfsId = projectWorkflowSchemeMapping[projectKey]
  221. if (wfsId != null) {
  222. return wfsId
  223. }
  224. def response
  225. try {
  226. //noinspection GroovyAssignabilityCheck
  227. response = await(httpClient
  228. .ws()
  229. .url(jiraCloudUrl + "/rest/projectconfig/1/workflowscheme/" + projectKey)
  230. .withAuth(username, password, play.api.libs.ws.WSAuthScheme.BASIC$.MODULE$)
  231. .withMethod("GET")
  232. .execute()
  233. )
  234. } catch (Exception e) {
  235. throw issueLevelError2(
  236. "Unable to get workflow scheme for project `" + projectKey + "` on behalf of user `" + username + "` using a private REST API, please contact Exalate Support: " +
  237. "\nRequest: GET /rest/projectconfig/1/workflowscheme/" + projectKey +
  238. "\nAuth: " + username + ":" + password +
  239. "\nError: " + e.message,
  240. e
  241. )
  242. }
  243. if (response.status() == 401) {
  244. throw issueLevelError("Can not get workflow scheme for project `"+ projectKey +"`. " +
  245. "\nKnown projects / workflow schemes: `"+ projectWorkflowSchemeMapping +"`" +
  246. "\nPlease contact Exalate Support. " +
  247. "\nAlso failed to get it through REST API: "+
  248. "\nRequest: GET /rest/projectconfig/1/workflowscheme/" + projectKey +
  249. "\nAuth: " + username +
  250. "\nResponse: " + response.body()
  251. )
  252. }
  253. if (response.status() != 200) {
  254. throw issueLevelError(
  255. "Failed to get workflow scheme for project `" + projectKey + "` on behalf of user `" + username + "` using a private REST API (status " + response.status() + "), please contact Exalate Support: " +
  256. "\nRequest: GET /rest/projectconfig/1/workflowscheme/" + projectKey +
  257. "\nAuth: " + username + ":" + password +
  258. "\nResponse: " + response.body()
  259. )
  260. }
  261. def s = new groovy.json.JsonSlurper()
  262.  
  263. def json
  264. try {
  265. json = s.parseText(response.body())
  266. } catch (Exception e) {
  267. throw issueLevelError2("Can not parse workflow scheme json from private REST API, please contact Exalate Support: " + response.body(), e)
  268. }
  269. if (!(json instanceof Map<String, Object>)) {
  270. throw issueLevelError("Workflow scheme json from private REST API has unrecognized structure, please contact Exalate Support: " + response.body())
  271. }
  272. (json as Map<String, Object>).id as Long
  273. }
  274.  
  275. def getWorkflowScheme = { Long workflowSchemeId ->
  276. def jsonStrInternal = workflowSchemeMapping[workflowSchemeId]
  277. if (jsonStrInternal == null) {
  278. def response
  279. try {
  280. //noinspection GrUnresolvedAccess
  281. response = await(httpClient
  282. .ws()
  283. .url(jiraCloudUrl + "/rest/api/2/workflowscheme/" + workflowSchemeId)
  284. .withAuth(username, password, play.api.libs.ws.WSAuthScheme.BASIC$.MODULE$)
  285. .withMethod("GET")
  286. .execute()
  287. )
  288. } catch (Exception e) {
  289. throw issueLevelError2(
  290. "Unable to get workflow scheme by id `" + workflowSchemeId + "`, please contact Exalate Support: " +
  291. "\nRequest: GET /rest/api/2/workflowscheme/" + workflowSchemeId +
  292. "\nAuth: " + username + ":" + password +
  293. "\nError: " + e.message,
  294. e
  295. )
  296. }
  297. if (response.status() == 401) {
  298. throw issueLevelError("Can not get details for workflow scheme by id `"+ workflowSchemeId +"`. " +
  299. "\nKnown projects / workflow schemes: `"+ workflowSchemeMapping +"`" +
  300. "\nPlease contact Exalate Support."+
  301. "\nAlso failed to get it through REST API: "+
  302. "\nRequest: GET /rest/api/2/workflowscheme/" + workflowSchemeId +
  303. "\nAuth: " + username +
  304. "\nResponse: " + response.body()
  305. )
  306. }
  307. if (response.status() != 200) {
  308. throw issueLevelError(
  309. "Failed to get workflow scheme by id `" + workflowSchemeId + "` (status " + response.status() + "), please contact Exalate Support: " +
  310. "\nRequest: GET /rest/api/2/workflowscheme/" + workflowSchemeId +
  311. "\nAuth: " + username + ":" + password +
  312. "\nResponse: " + response.body()
  313. )
  314. }
  315. jsonStrInternal = response.body()
  316. }
  317.  
  318.  
  319. def s = new groovy.json.JsonSlurper()
  320. def json
  321. try {
  322. json = s.parseText(jsonStrInternal)
  323. } catch (Exception e) {
  324. throw issueLevelError2("Can not parse workflow scheme json, please contact Exalate Support: " + jsonStrInternal, e)
  325. }
  326. if (!(json instanceof Map<String, Object>)) {
  327. throw issueLevelError("Workflow scheme json has unrecognized structure, please contact Exalate Support: " + jsonStrInternal)
  328. }
  329. if (!((json as Map<String, Object>).issueTypeMappings instanceof Map<String, Object>)) {
  330. throw issueLevelError("Workflow scheme issueTypeMappings json has unrecognized structure, please contact Exalate Support: " + jsonStrInternal)
  331. }
  332. if (!((json as Map<String, Object>).defaultWorkflow instanceof String)) {
  333. throw issueLevelError("Workflow scheme defaultWorkflow json has unrecognized structure, please contact Exalate Support: " + jsonStrInternal)
  334. }
  335. json
  336. }
  337.  
  338. def getWorkflowName = { Long workflowSchemeId, String issueTypeId ->
  339. def workflowSchemeJson = getWorkflowScheme(workflowSchemeId)
  340. def wfName = workflowSchemeJson.issueTypeMappings[issueTypeId] ?: workflowSchemeJson.defaultWorkflow
  341. wfName as String
  342. }
  343.  
  344. def getWorkflowDetailsForProject = { String workflowName, String projectKey ->
  345. def projectToWorkflowDetailsMapping = workflowDetailsMapping[workflowName]
  346.  
  347. def jsonStr
  348. if (projectToWorkflowDetailsMapping != null) {
  349. jsonStr = projectToWorkflowDetailsMapping
  350. } else {
  351. def response
  352. try {
  353. response = await(httpClient
  354. .ws()
  355. .url(jiraCloudUrl + "/rest/projectconfig/1/workflow")
  356. .withQueryString(seq(
  357. pair("workflowName", workflowName),
  358. pair("projectKey", projectKey)
  359. ))
  360. .withAuth(username, password, play.api.libs.ws.WSAuthScheme.BASIC$.MODULE$)
  361. .withMethod("GET")
  362. .execute()
  363. )
  364. } catch (Exception e) {
  365. throw issueLevelError2(
  366. "Unable to get workflow details for name `" + workflowName + "` for project `" + projectKey + "` on behalf of user `" + username + "`, please contact Exalate Support: " +
  367. "\nRequest: GET /rest/projectconfig/1/workflow?workflowName=" + workflowName + "&projectKey=" + projectKey +
  368. "\nAuth: " + username + ":" + password +
  369. "\nError: " + e.message,
  370. e
  371. )
  372. }
  373. if (response.status() == 401) {
  374. throw issueLevelError("Can not get project to workflow details mapping for workflow name`"+ workflowName +"`. " +
  375. "\nKnown workflow name to project to details mapping: `"+ workflowDetailsMapping +"`" +
  376. "\nPlease contact Exalate Support."+
  377. "\nAlso failed to get it through REST API: "+
  378. "\nRequest: GET /rest/projectconfig/1/workflow?workflowName=" + workflowName + "&projectKey=" + projectKey +
  379. "\nAuth: " + username +
  380. "\nResponse: " + response.body()
  381. )
  382. }
  383. if (response.status() != 200) {
  384. throw issueLevelError(
  385. "Failed to get workflow details for name `" + workflowName + "` for project `" + projectKey + "` on behalf of user `" + username + "` (status " + response.status() + "), please contact Exalate Support: " +
  386. "\nRequest: GET /rest/projectconfig/1/workflow?workflowName=" + workflowName + "&projectKey=" + projectKey +
  387. "\nAuth: " + username + ":" + password +
  388. "\nResponse: " + response.body()
  389. )
  390. }
  391. jsonStr = response.body()
  392. }
  393.  
  394.  
  395. def s = new groovy.json.JsonSlurper()
  396.  
  397. def json
  398. try {
  399. json = s.parseText(jsonStr)
  400. } catch (Exception e) {
  401. throw issueLevelError2("Can not parse workflow details json, please contact Exalate Support: " + jsonStr, e)
  402. }
  403. if (!(json instanceof Map<String, Object>)) {
  404. throw issueLevelError("Workflow details json has unrecognized structure, please contact Exalate Support: " + jsonStr)
  405. }
  406. if (!((json as Map<String, Object>).sources instanceof List<Map<String, Object>>)) {
  407. throw issueLevelError("Workflow details sources json has unrecognized structure, please contact Exalate Support: " + jsonStr)
  408. }
  409. json as Map<String, Object>
  410. }
  411.  
  412.  
  413. def getTransitionsForIssue = { String issueKeyStr ->
  414. def response
  415. try {
  416. response = await(await(httpClient.authenticate(
  417. none(),
  418. httpClient
  419. .ws()
  420. .url(jiraCloudUrl + "/rest/api/2/issue/" + issueKeyStr + "/transitions")
  421. .withMethod("GET"),
  422. gs
  423. )).get())
  424. } catch (Exception e) {
  425. throw issueLevelError2("Unable to get transitions for issue, please contact Exalate Support: " +
  426. "\nRequest: GET /rest/api/2/issue/" + issueKeyStr + "/transitions" +
  427. "\nError: " + e.message, e)
  428. }
  429. if (response.status() != 200) {
  430. throw issueLevelError("Can not get transitions for issue (status " + response.status() + "), please contact Exalate Support: " +
  431. "\nRequest: GET /rest/api/2/issue/" + issueKeyStr + "/transitions" +
  432. "\nResponse: " + response.body())
  433. }
  434. def resultStr = response.body()
  435. def s = new groovy.json.JsonSlurper()
  436. def json
  437. try {
  438. json = s.parseText(resultStr)
  439. } catch (Exception e) {
  440. throw issueLevelError2("Can not parse the get transitions for issue json, please contact Exalate Support: " + resultStr, e)
  441. }
  442. if (!(json instanceof Map)) {
  443. throw issueLevelError("Get transitions for issue json has unrecognized structure, please contact Exalate Support: " + resultStr)
  444. }
  445. if (!(json.transitions instanceof List)) {
  446. throw issueLevelError("Get transitions for issue .transitions json has unrecognized structure, please contact Exalate Support: " + resultStr)
  447. }
  448. json as Map<String, Object>
  449. }
  450. def getGlobalTransitionsViaIssue = { String issueKeyStr ->
  451. def tsResponse = getTransitionsForIssue(issueKeyStr)
  452. def ts = tsResponse.transitions as List<Map<String, Object>>;
  453. ts
  454. .findAll { t -> t.isGlobal }
  455. .collect { t ->
  456. [
  457. "id" : t.id as String,
  458. "name" : t.name as String,
  459. "to" : [
  460. "id" : t.to.id as String,
  461. "name": t.to.name as String
  462. ],
  463. "global": true
  464. ]
  465. }
  466. }
  467. def getWorkflow = { String workflowName, String projectKey, String issueKeyStr ->
  468. def json = getWorkflowDetailsForProject(workflowName, projectKey);
  469. def sources = (json as Map<String, Object>).sources as List<Map<String, Object>>;
  470. def transitions = sources
  471. .collect { src ->
  472. if (!(src.fromStatus instanceof Map<String, Object>)) {
  473. throw issueLevelError("Workflow details sources `" + src + "` from status `" + src.fromStatus + "` json has unrecognized structure, please contact Exalate Support: " + response.body())
  474. }
  475. def fromStatus = src.fromStatus as Map<String, Object>
  476. if (!(fromStatus.id instanceof String)) {
  477. throw issueLevelError("Workflow details sources `" + src + "` from status `" + fromStatus + "` id `" + fromStatus.id + "` json has unrecognized structure, please contact Exalate Support: " + response.body())
  478. }
  479. if (!(fromStatus.name instanceof String)) {
  480. throw issueLevelError("Workflow details sources `" + src + "` from status `" + fromStatus + "` name `" + fromStatus.name + "` json has unrecognized structure, please contact Exalate Support: " + response.body())
  481. }
  482. if (!(src.targets instanceof List)) {
  483. throw issueLevelError("Workflow details sources `" + src + "` targets `" + src.targets + "` json has unrecognized structure, please contact Exalate Support: " + response.body())
  484. }
  485. def targets = src.targets as List<Map<String, Object>>;
  486. def ts = targets.collect { target ->
  487. if (!(target instanceof Map<String, Object>)) {
  488. throw issueLevelError("Workflow details sources `" + src + "` target `" + target + "` json has unrecognized structure, please contact Exalate Support: " + response.body())
  489. }
  490. if (!(target.toStatus instanceof Map<String, Object>)) {
  491. throw issueLevelError("Workflow details sources `" + src + "` target `" + target + "` toStatus `" + target.toStatus + "` json has unrecognized structure, please contact Exalate Support: " + response.body())
  492. }
  493. def toStatus = target.toStatus as Map<String, Object>;
  494. if (!(toStatus.id instanceof String)) {
  495. throw issueLevelError("Workflow details sources `" + src + "` target `" + target + "` toStatus `" + toStatus + "` id `" + toStatus.id + "` json has unrecognized structure, please contact Exalate Support: " + response.body())
  496. }
  497. if (!(toStatus.name instanceof String)) {
  498. throw issueLevelError("Workflow details sources `" + src + "` target `" + target + "` toStatus `" + toStatus + "` name `" + toStatus.name + "` json has unrecognized structure, please contact Exalate Support: " + response.body())
  499. }
  500. if (!(target.transitionName instanceof String)) {
  501. throw issueLevelError("Workflow details sources `" + src + "` target `" + target + "` transitionName `" + target.transitionName + "` json has unrecognized structure, please contact Exalate Support: " + response.body())
  502. }
  503. [
  504. "name" : target.transitionName,
  505. "from" : [
  506. "id" : fromStatus.id,
  507. "name": fromStatus.name,
  508. ],
  509. "to" : [
  510. "id" : toStatus.id,
  511. "name": toStatus.name,
  512. ],
  513. "global": false
  514. ]
  515. }
  516. ts
  517. }
  518. .flatten()
  519. def allTransitions = transitions + getGlobalTransitionsViaIssue(issueKeyStr)
  520. [
  521. "name" : workflowName,
  522. "transitions": allTransitions,
  523. "steps" : allTransitions.inject([]) { List<Map<String, Object>> result, t ->
  524. def stepsToAddToResult = t.global ? [t.to] : [t.from, t.to]
  525. stepsToAddToResult.inject(result) { r, s ->
  526. def curStep = r.find { step -> step.id == s.id }
  527. if (curStep != null) {
  528. // the step is already in the result steps list
  529. if (t.from.id == s.id && !curStep.transitions?.any { it.name == t.name }) {
  530. // if the transition is from this step
  531. curStep.transitions += t
  532. }
  533. } else {
  534. // the step is not in the result steps list
  535. r += [
  536. "id" : s.id,
  537. "name" : s.name,
  538. "transitions": t.from.id == s.id ? [t] : []
  539. ]
  540. }
  541. r
  542. }
  543. }
  544. ]
  545. }
  546. def transition = { com.exalate.basic.domain.BasicIssueKey zissueKey ->
  547. { Map<String, Object> t, String transitionExecUserKey, Map<String, Object> fields ->
  548. if (t.id == null) {
  549. def currentlyAvailableTransitions = getTransitionsForIssue(zissueKey.URN).transitions as List<Map<String, Object>>;
  550. def zTransition = currentlyAvailableTransitions.find { Map<String, Object> aTransition ->
  551. aTransition.name as String == t.name as String &&
  552. aTransition.to?.id as Long == t.to.id as Long
  553. }
  554. if (zTransition == null) {
  555. throw issueLevelError("Failing to transition because the transition found by algorithm `" + t.name + "` is no longer available for current step `" + t.from.name + "` (" + t.from.id + "). Please contact Exalate Support" +
  556. "\nAvailable transitions:" + currentlyAvailableTransitions)
  557. }
  558. t.id = zTransition.id
  559. }
  560. def json = [
  561. "transition": [
  562. "id": t.id
  563. ],
  564. ] as Map<String, Object>
  565. if (fields != null) {
  566. json.fields = fields
  567. }
  568. def jsonStr = groovy.json.JsonOutput.toJson(json)
  569. def response
  570. try {
  571. def writable = play.api.http.Writeable$.MODULE$.wString(play.api.mvc.Codec.utf_8())
  572. //noinspection GroovyAssignabilityCheck
  573. response = await(await(httpClient.authenticate(
  574. none(),
  575. httpClient
  576. .ws()
  577. .url(jiraCloudUrl + "/rest/api/2/issue/" + zissueKey.URN + "/transitions")
  578. .withHeaders(seq(pair("Content-Type", "application/json")))
  579. .withBody(jsonStr, writable)
  580. .withMethod("POST"),
  581. gs
  582. )).execute())
  583. } catch (Exception e) {
  584. throw issueLevelError2("Unable to transition issue `" + zissueKey.URN + "`, please contact Exalate Support: \n" +
  585. "POST " + jiraCloudUrl + "/rest/api/2/issue/" + zissueKey.URN + "\nBody: " + jsonStr + "\nError Message:" + e.message, e)
  586. }
  587. if (response.status() != 204) {
  588. throw issueLevelError("Can not transition issue `" + zissueKey.URN + "` (http status " + response.status() + "), please contact Exalate Support: \nPUT " + jiraCloudUrl + "/rest/api/2/issue/" + zissueKey.id + "\nBody: " + jsonStr + "\nResponse:" + response.body())
  589. }
  590. }
  591. }
  592. def getTransitions = { String statusName, Map<String, Object> wf ->
  593. def step = wf.steps.find { s -> s.name == statusName }
  594. if (step == null) {
  595. throw issueLevelError("Can not find step `" + statusName + "`. Available steps: `" + wf.steps.collect { s -> s.name + " (" + s.id + ")" } + "`. Create it or review the transitioning script.")
  596. }
  597. step.transitions
  598. }
  599. def getGlobalTransitions = { Map<String, Object> wf ->
  600. wf
  601. .transitions
  602. .findAll { t -> t.global }
  603. }
  604. def getAllTransitions = { String statusName, List<String> visitedStates, Map<String, Object> wf ->
  605. (getTransitions(statusName, wf) + getGlobalTransitions(wf))
  606. .findAll { t -> !visitedStates.any { it == t.to.name } }
  607. }
  608. def getTransitionPathInternal
  609. getTransitionPathInternal = { transitionWithParent ->
  610. if (!transitionWithParent) return null;
  611. if (transitionWithParent.parentTransition == null) {
  612. return [transitionWithParent.transition]
  613. }
  614. getTransitionPathInternal(transitionWithParent.parentTransition) + [transitionWithParent.transition]
  615. }
  616. def getPathsFromAtoBInternal = { String currentStatusName, String targetStatusName, Map<String, Object> wf ->
  617. if (currentStatusName == targetStatusName) {
  618. return null
  619. }
  620. def q = [] as Queue<Map<String, Object>>;
  621. def visitedStates = [] as List<String>;
  622. def allFirstTransitions = getAllTransitions(currentStatusName, visitedStates, wf) as List<Map<String, Object>>;
  623. for (aTransition in allFirstTransitions) {
  624. def nextStatus = aTransition.to.name
  625. def transitionWithParent = ["transition": aTransition, "parentTransition": null]
  626. if (nextStatus == targetStatusName) {
  627. return transitionWithParent
  628. }
  629. if (aTransition.from) {
  630. visitedStates.add(aTransition.from.name as String)
  631. }
  632. q.add(transitionWithParent)
  633. }
  634. //solution -> every state has a from transition property
  635. while (!q.isEmpty()) {
  636. def transitionWithParent = q.remove()
  637. def nextStatus = transitionWithParent.transition.to.name as String
  638. def childTransitions = getAllTransitions(nextStatus, visitedStates, wf) as List<Map<String, Object>>;
  639. for (childTransition in childTransitions) {
  640. def nextChildStatus = childTransition.to.name
  641. if (targetStatusName == nextChildStatus) {
  642. return ["transition": childTransition, "parentTransition": transitionWithParent]
  643. }
  644. if (childTransition.from) {
  645. visitedStates.add(childTransition.from.name as String)
  646. }
  647. q.add(["transition": childTransition, "parentTransition": transitionWithParent])
  648. }
  649. }
  650. }
  651. def getEditIssueMeta = { String issueKeyStr ->
  652. // GET /rest/api/2/issue/{issueIdOrKey}/editmeta
  653. def response
  654. try {
  655. //noinspection GrUnresolvedAccess
  656. response = await(await(httpClient.authenticate(
  657. none(),
  658. httpClient
  659. .ws()
  660. .url(jiraCloudUrl + "/rest/api/2/issue/" + issueKeyStr + "/editmeta")
  661. .withMethod("GET"),
  662. gs
  663. )).get())
  664. } catch (Exception e) {
  665. throw issueLevelError2("Unable to get edit meta for issue, please contact Exalate Support: " +
  666. "\nRequest: GET /rest/api/2/issue/" + issueKeyStr + "/editmeta" +
  667. "\nError: " + e.message, e)
  668. }
  669. if (response.status() != 200) {
  670. throw issueLevelError("Can not get edit meta for issue (status " + response.status() + "), please contact Exalate Support: " +
  671. "\nRequest: GET /rest/api/2/issue/" + issueKeyStr + "/editmeta" +
  672. "\nResponse: " + response.body())
  673. }
  674. def resultStr = response.body()
  675. def s = new groovy.json.JsonSlurper()
  676. def json
  677. try {
  678. json = s.parseText(resultStr)
  679. } catch (Exception e) {
  680. throw issueLevelError2("Can not parse the get edit meta for issue json, please contact Exalate Support: " + resultStr, e)
  681. }
  682.  
  683. /*
  684. {"fields":{
  685. "summary":{"required":true,"schema":{"type":"string","system":"summary"},"name":"Summary","key":"summary","operations":["set"]},
  686. "issuetype":{"required":true,"schema":{"type":"issuetype","system":"issuetype"},"name":"Issue Type","key":"issuetype","operations":[],"allowedValues":[{"self":"https://tsleft.atlassian.net/rest/api/2/issuetype/10002","id":"10002","description":"A task that needs to be done.","iconUrl":"https://tsleft.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10318&avatarType=issuetype","name":"Task","subtask":false,"avatarId":10318},{"self":"https://tsleft.atlassian.net/rest/api/2/issuetype/10001","id":"10001","description":"Created by Jira Agile - do not edit or delete. Issue type for a user story.","iconUrl":"https://tsleft.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10315&avatarType=issuetype","name":"Story","subtask":false,"avatarId":10315},{"self":"https://tsleft.atlassian.net/rest/api/2/issuetype/10004","id":"10004","description":"A problem which impairs or prevents the functions of the product.","iconUrl":"https://tsleft.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10303&avatarType=issuetype","name":"Bug","subtask":false,"avatarId":10303},{"self":"https://tsleft.atlassian.net/rest/api/2/issuetype/10000","id":"10000","description":"Created by Jira Software - do not edit or delete. Issue type for a big user story that needs to be broken down.","iconUrl":"https://tsleft.atlassian.net/images/icons/issuetypes/epic.svg","name":"Epic","subtask":false}]},
  687. "components":{"required":false,"schema":{"type":"array","items":"component","system":"components"},"name":"Component/s","key":"components","operations":["add","set","remove"],"allowedValues":[]},
  688. "description":{"required":false,"schema":{"type":"string","system":"description"},"name":"Description","key":"description","operations":["set"]},
  689. "customfield_10064":{"required":false,"schema":{"type":"string","custom":"com.atlassian.jira.plugin.system.customfieldtypes:textfield","customId":10064},"name":"Remote Issue ID","key":"customfield_10064","operations":["set"]},
  690. "customfield_10010":{"required":false,"schema":{"type":"array","items":"string","custom":"com.pyxis.greenhopper.jira:gh-sprint","customId":10010},"name":"Sprint","key":"customfield_10010","operations":["set"]},
  691. "fixVersions":{"required":false,"schema":{"type":"array","items":"version","system":"fixVersions"},"name":"Fix Version/s","key":"fixVersions","operations":["set","add","remove"],"allowedValues":[]},
  692. "priority":{"required":false,"schema":{"type":"priority","system":"priority"},"name":"Priority","key":"priority","operations":["set"],"allowedValues":[{"self":"https://tsleft.atlassian.net/rest/api/2/priority/1","iconUrl":"https://tsleft.atlassian.net/images/icons/priorities/highest.svg","name":"Highest","id":"1"},{"self":"https://tsleft.atlassian.net/rest/api/2/priority/2","iconUrl":"https://tsleft.atlassian.net/images/icons/priorities/high.svg","name":"High","id":"2"},{"self":"https://tsleft.atlassian.net/rest/api/2/priority/3","iconUrl":"https://tsleft.atlassian.net/images/icons/priorities/medium.svg","name":"Medium","id":"3"},{"self":"https://tsleft.atlassian.net/rest/api/2/priority/4","iconUrl":"https://tsleft.atlassian.net/images/icons/priorities/low.svg","name":"Low","id":"4"},{"self":"https://tsleft.atlassian.net/rest/api/2/priority/5","iconUrl":"https://tsleft.atlassian.net/images/icons/priorities/lowest.svg","name":"Lowest","id":"5"}]},
  693. "labels":{"required":false,"schema":{"type":"array","items":"string","system":"labels"},"name":"Labels","key":"labels","autoCompleteUrl":"https://tsleft.atlassian.net/rest/api/1.0/labels/suggest?query=","operations":["add","set","remove"]},
  694. "customfield_10008":{"required":false,"schema":{"type":"any","custom":"com.pyxis.greenhopper.jira:gh-epic-link","customId":10008},"name":"Epic Link","key":"customfield_10008","operations":["set"]},
  695. "attachment":{"required":false,"schema":{"type":"array","items":"attachment","system":"attachment"},"name":"Attachment","key":"attachment","operations":[]},
  696. "issuelinks":{"required":false,"schema":{"type":"array","items":"issuelinks","system":"issuelinks"},"name":"Linked Issues","key":"issuelinks","autoCompleteUrl":"https://tsleft.atlassian.net/rest/api/2/issue/picker?currentProjectId=&showSubTaskParent=true&showSubTasks=true&currentIssueKey=LPROJ-9&query=","operations":["add"]},
  697. "comment":{"required":false,"schema":{"type":"comments-page","system":"comment"},"name":"Comment","key":"comment","operations":["add","edit","remove"]},
  698. "assignee":{"required":false,"schema":{"type":"user","system":"assignee"},"name":"Assignee","key":"assignee","autoCompleteUrl":"https://tsleft.atlassian.net/rest/api/latest/user/assignable/search?issueKey=LPROJ-9&username=","operations":["set"]},
  699. "resolution":{"required":false,"schema":{"type":"resolution","system":"resolution"},"name":"Resolution","key":"resolution","operations":["set"],"allowedValues":[{"self":"https://tsleft.atlassian.net/rest/api/2/resolution/10000","name":"Done","id":"10000"},{"self":"https://tsleft.atlassian.net/rest/api/2/resolution/10001","name":"Won't Do","id":"10001"},{"self":"https://tsleft.atlassian.net/rest/api/2/resolution/10002","name":"Duplicate","id":"10002"},{"self":"https://tsleft.atlassian.net/rest/api/2/resolution/10003","name":"Cannot Reproduce","id":"10003"}]}
  700. }}
  701. */
  702. if (!(json instanceof Map)) {
  703. throw issueLevelError("Get edit meta for issue json has unrecognized structure, please contact Exalate Support: " + resultStr)
  704. }
  705. if (!(json.fields instanceof Map)) {
  706. throw issueLevelError("Get edit meta for issue `.fields` json has unrecognized structure, please contact Exalate Support: " + resultStr)
  707. }
  708. json as Map<String, Object>
  709. }
  710.  
  711. // applying the resolution
  712. if (
  713. issue.resolution?.name != replica.resolution?.name &&
  714. (getEditIssueMeta(jIssue.key).fields as Map<String, Object>)
  715. .values()
  716. .any { meta ->
  717. meta.key == "resolution" &&
  718. meta.schema?.system == "resolution"
  719. }
  720. ) {
  721. if (replica.resolution?.name == null) {
  722. issue.resolution = null
  723. } else {
  724. def resolution = nodeHelper.getResolution(replica.resolution?.name)
  725. if (resolution == null) {
  726. throw issueLevelError("Could not find resolution `" + replica.resolution?.name + "`. Please create one and resolve the error or contact Exalate Supprt.")
  727. }
  728. issue.resolution = resolution
  729. }
  730. }
  731. def desiredStatusName = (workflowMapping[replica.status.name] ?: replica.status.name)
  732. if (jIssue.status.name != desiredStatusName) {
  733. def wfSchemeId = getWorkflowSchemeIdForProject(issue.project.key)
  734. def wfName = getWorkflowName(wfSchemeId, issue.type.id)
  735. def wf = getWorkflow(wfName, issue.project.key, jIssue.key)
  736. def shortestPath = getTransitionPathInternal(getPathsFromAtoBInternal(jIssue.status.name, desiredStatusName, wf))
  737. if (shortestPath == null) {
  738. throw issueLevelError(
  739. "Can not find path from `" + jIssue.status.name + "` to `" + desiredStatusName + "` " +
  740. "in workflow `" + wf.name + ". " +
  741. "Please review whether `" + wf.name + "` is the correct workflow for the issue `" + jIssue.key + "`."
  742. )
  743. }
  744. shortestPath.each { t ->
  745. transition(localExIssueKey)(t as Map<String, Object>, null, null as Map<String, Object>)
  746. }
  747. }
  748. } catch (com.exalate.api.exception.IssueTrackerException ite) {
  749. throw ite
  750. } catch (Exception e) {
  751. throw new com.exalate.api.exception.IssueTrackerException(e)
  752. }
  753. })()
  754. // END: STATUS SYNC
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement