PageRenderTime 83ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/resources/com/onresolve/jira/groovy/canned/admin/SplitCustomFieldContext.groovy

https://bitbucket.org/sorin/jira-plugin-intellij
Groovy | 307 lines | 251 code | 48 blank | 8 comment | 22 complexity | 9709232b5f4a178316806cf720ef825a MD5 | raw file
  1. package com.onresolve.jira.groovy.canned.admin
  2. import com.onresolve.jira.groovy.canned.CannedScript
  3. import com.onresolve.jira.groovy.canned.CannedScriptArg
  4. import com.atlassian.jira.util.ErrorCollection
  5. import com.onresolve.jira.groovy.canned.utils.CannedScriptUtils
  6. import com.atlassian.jira.issue.context.manager.JiraContextTreeManager
  7. import com.atlassian.jira.ComponentManager
  8. import com.atlassian.jira.issue.customfields.CustomFieldUtils
  9. import com.atlassian.jira.issue.fields.config.FieldConfigScheme
  10. import com.atlassian.jira.issue.fields.config.manager.FieldConfigSchemeManager
  11. import com.atlassian.jira.component.ComponentAccessor
  12. import com.atlassian.jira.issue.fields.screen.FieldScreenManager
  13. import org.apache.log4j.Category
  14. import com.atlassian.jira.issue.customfields.option.Option
  15. import com.atlassian.jira.issue.fields.CustomField
  16. import com.atlassian.jira.jql.builder.JqlQueryBuilder
  17. import com.atlassian.jira.issue.search.SearchRequest
  18. import com.atlassian.jira.issue.search.SearchResults
  19. import com.atlassian.jira.web.bean.PagerFilter
  20. import com.atlassian.jira.issue.util.IssueChangeHolder
  21. import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
  22. import com.atlassian.jira.issue.ModifiedValue
  23. import com.atlassian.jira.util.ImportUtils
  24. import com.atlassian.jira.issue.search.SearchProvider
  25. import com.atlassian.jira.issue.index.IssueIndexManager
  26. import com.atlassian.jira.issue.fields.config.FieldConfig
  27. import com.atlassian.jira.util.SimpleErrorCollection
  28. import com.atlassian.jira.issue.customfields.view.CustomFieldParams
  29. import com.atlassian.jira.issue.customfields.view.CustomFieldParamsImpl
  30. class SplitCustomFieldContext implements CannedScript{
  31. public static String FIELD_PROJECT_IDS = "FIELD_PROJECT_IDS"
  32. public static String FIELD_ISSUE_TYPE_IDS = "FIELD_ISSUE_TYPE_IDS"
  33. public static String FIELD_NEW_CONTEXT_NAME = "FIELD_NEW_CONTEXT_NAME"
  34. public static String FIELD_FCS = "FIELD_FCS"
  35. def fieldConfigSchemeManager = ComponentManager.getComponentInstanceOfType(FieldConfigSchemeManager.class)
  36. def optionsManager = ComponentAccessor.getOptionsManager()
  37. ComponentManager componentManager = ComponentManager.getInstance()
  38. def projectManager = componentManager.getProjectManager()
  39. Category log = Category.getInstance(SplitCustomFieldContext.class)
  40. def searchProvider = componentManager.getSearchProvider()
  41. def issueManager = componentManager.getIssueManager()
  42. def indexManager = ComponentManager.getInstance().getIndexManager()
  43. String getName() {
  44. "Split custom field contexts"
  45. }
  46. String getDescription() {
  47. "This script will split projects out a custom field context, duplicating option values and updating issues with the new option values."
  48. }
  49. public String getHelpUrl() {
  50. "https://studio.plugins.atlassian.com/wiki/display/GRV/Built-In+Scripts#Built-InScripts-SplitCustomFieldContext"
  51. }
  52. List getCategories() {
  53. ["ADMIN"]
  54. }
  55. List getParameters(Map params) {
  56. if ( !isFinalParamsPage(params)) {
  57. getDefaultParameters()
  58. }
  59. else {
  60. [
  61. [
  62. Name:FIELD_PROJECT_IDS,
  63. Label:"Project Name",
  64. Description:"""Which project(s) to move to the new context. You must select at least one.""",
  65. Type:"multilist",
  66. Values: getProjectsForFieldConfigScheme(params[FIELD_FCS] as Long),
  67. ],
  68. [
  69. Name:FIELD_ISSUE_TYPE_IDS,
  70. Label:"Issue Types",
  71. Description:"""Which issue types to associate with the new scheme. Leave blank for All.""",
  72. Type:"multilist",
  73. Values: getIssueTypesForFieldConfigScheme(params[FIELD_FCS] as Long),
  74. ],
  75. [
  76. Name:FIELD_NEW_CONTEXT_NAME,
  77. Label:"New context name",
  78. Description:"""Name for the new context - not significant really.""",
  79. ],
  80. ]
  81. }
  82. }
  83. List getDefaultParameters() {
  84. [
  85. [
  86. Name:FIELD_FCS,
  87. Label:"Name of custom field config scheme",
  88. Description:"The name of the field config scheme in which to import these values",
  89. Type:"groupedlist", // multilist requires map of maps
  90. Values: CannedScriptUtils.getFieldConfigSchemesByCustomField(true)
  91. ],
  92. ]
  93. }
  94. Map getProjectsForFieldConfigScheme(Long fcsId) {
  95. def fcs = fieldConfigSchemeManager.getFieldConfigScheme(fcsId)
  96. if (fcs.allProjects) {
  97. return CannedScriptUtils.getProjectOptions(false)
  98. }
  99. else {
  100. def rt = [:]
  101. fcs.getAssociatedProjects().each {
  102. rt.put(it.key, it.name)
  103. }
  104. rt
  105. }
  106. }
  107. Map getIssueTypesForFieldConfigScheme(Long fcsId) {
  108. def fcs = fieldConfigSchemeManager.getFieldConfigScheme(fcsId)
  109. if (fcs.allIssueTypes) {
  110. return CannedScriptUtils.getAllIssueTypes(false, true)
  111. }
  112. else {
  113. def rt = [:]
  114. fcs.getAssociatedIssueTypes().each {
  115. rt.put(it.id, it.name)
  116. }
  117. rt
  118. }
  119. }
  120. ErrorCollection doValidate(Map params, boolean forPreview) {
  121. def errorCollection = new SimpleErrorCollection()
  122. if (!params[FIELD_FCS]) {
  123. errorCollection.addError(FIELD_FCS, "Please select a field config scheme.")
  124. }
  125. // use this to detect if we're on the second page as this is always sent (because it's a text box).
  126. if(params.containsKey(FIELD_NEW_CONTEXT_NAME)) {
  127. if (!params[FIELD_PROJECT_IDS]) {
  128. errorCollection.addError(FIELD_PROJECT_IDS, "Please select one or more project(s).")
  129. }
  130. if (!params[FIELD_NEW_CONTEXT_NAME]) {
  131. errorCollection.addError(FIELD_NEW_CONTEXT_NAME, "Please choose a name for the new context.")
  132. }
  133. }
  134. errorCollection
  135. }
  136. def List convertToListIfNecessary(obj) {
  137. if (obj instanceof String) {
  138. obj = [obj]
  139. }
  140. obj
  141. }
  142. Map doScript(Map params) {
  143. def projectIds = params[FIELD_PROJECT_IDS]
  144. // convert projectIds to Longs
  145. projectIds = convertProjectKeysToIds(projectIds)
  146. log.debug(projectIds)
  147. def issueTypeIds = params[FIELD_ISSUE_TYPE_IDS] as List
  148. issueTypeIds = convertToListIfNecessary(issueTypeIds)
  149. def fcsId = params[FIELD_FCS] as Long
  150. def newContextName = params[FIELD_NEW_CONTEXT_NAME] as String
  151. def fcs = fieldConfigSchemeManager.getFieldConfigScheme(fcsId)
  152. // I think this is something that you can do in the API but you can't do in the UI
  153. // assert fcs.getConfigs().entrySet().size() == 1
  154. def fieldConfig = fcs.getOneAndOnlyConfig()
  155. FieldConfigScheme targetFCS
  156. FieldConfig targetFC
  157. (targetFCS, targetFC) = createNewFieldConfigScheme(projectIds, issueTypeIds, newContextName, fcs)
  158. def cf = targetFCS.getField() as CustomField
  159. copyFieldOptions(fieldConfig, targetFC, cf)
  160. updateOptionsForIssues(projectIds, issueTypeIds, cf, targetFC, false)
  161. params.put("targetFC", targetFC)
  162. params["output"] = "New custom field configuration scheme created."
  163. params
  164. }
  165. private List<Long> convertProjectKeysToIds(projectIds) {
  166. projectIds = convertToListIfNecessary(projectIds)
  167. projectIds = projectIds.collect {projectManager.getProjectObjByKey(it).id} as List
  168. projectIds
  169. }
  170. private List createNewFieldConfigScheme(List<Long> projectIds, List<String> issueTypeIds, String newContextName, FieldConfigScheme fcs) {
  171. JiraContextTreeManager jiraContextTreeManager = new JiraContextTreeManager(projectManager, ComponentManager.getInstance().getConstantsManager())
  172. List contexts = CustomFieldUtils.buildJiraIssueContexts(false,
  173. null,
  174. projectIds as Long[],
  175. jiraContextTreeManager);
  176. // update original FCS to remove the items selected - happens automatically
  177. def targetFCS = fieldConfigSchemeManager.createFieldConfigScheme(
  178. new FieldConfigScheme.Builder().setName(newContextName).setDescription("Generated by script runner plugin.").toFieldConfigScheme(),
  179. contexts,
  180. issueTypeIds.collect {componentManager.constantsManager.getIssueType(it)} ?: FieldConfigSchemeManager.ALL_ISSUE_TYPES,
  181. fcs.getField()
  182. )
  183. def targetFC = targetFCS.getOneAndOnlyConfig()
  184. componentManager.getFieldManager().refresh()
  185. [targetFCS, targetFC]
  186. }
  187. private Long updateOptionsForIssues(projectIds, issueTypeIds, cf, targetFC, boolean preview) {
  188. // update values from old to new - could be multi and cascading
  189. def builder = JqlQueryBuilder.newBuilder().where()
  190. if (projectIds) {
  191. builder = builder.project(projectIds as Long[]).and()
  192. }
  193. if (issueTypeIds) {
  194. builder = builder.issueType(issueTypeIds as String[]).and()
  195. }
  196. def query = builder.customField(cf.idAsLong).isNotEmpty().buildQuery()
  197. log.debug("Execute query to find issues to update: $query")
  198. SearchRequest sr = new SearchRequest(query)
  199. SearchResults results = searchProvider.search(sr.getQuery(), componentManager.getJiraAuthenticationContext().getUser(), PagerFilter.getUnlimitedFilter())
  200. if (!preview) {
  201. results.issues.each {
  202. def iss = issueManager.getIssueObject(it.id)
  203. def oldFieldValue = iss.getCustomFieldValue(cf)
  204. def targetValue
  205. if (oldFieldValue instanceof CustomFieldParams) {
  206. def optionValues = oldFieldValue.getKeysAndValues().values()*.first()*.value
  207. def rootOptVal = optionValues[0]
  208. if (!rootOptVal) {
  209. log.debug("should never get here")
  210. return
  211. }
  212. def Option rootOption = optionsManager.getOptions(targetFC).find {it.value == rootOptVal} as Option
  213. def valuesMap = [
  214. null: [rootOption],
  215. ]
  216. if (optionValues.size() == 2) {
  217. valuesMap.put("1", [optionsManager.findByParentId(rootOption.optionId).find {it.value == optionValues[1]}])
  218. }
  219. targetValue = new CustomFieldParamsImpl(cf, valuesMap)
  220. }
  221. else {
  222. targetValue = optionsManager.getOptions(targetFC).find {it.value == oldFieldValue.value}
  223. }
  224. IssueChangeHolder changeHolder = new DefaultIssueChangeHolder()
  225. log.debug("Update issue ${iss.key}")
  226. cf.updateValue(null, iss, new ModifiedValue(oldFieldValue, targetValue), changeHolder)
  227. boolean wasIndexing = ImportUtils.isIndexIssues();
  228. indexManager.reIndex(iss);
  229. ImportUtils.setIndexIssues(wasIndexing);
  230. }
  231. }
  232. results.total
  233. }
  234. private void copyFieldOptions(FieldConfig fieldConfig, targetFC, CustomField cf) {
  235. def originalOptions = optionsManager.getOptions(fieldConfig)
  236. originalOptions.each {Option opt ->
  237. def newOpt = optionsManager.createOption(targetFC, null, null, opt.getValue())
  238. newOpt.setDisabled(opt.disabled)
  239. // todo: should be recursive to handle multi-level child options
  240. opt.childOptions.each {
  241. optionsManager.createOption(targetFC, newOpt.optionId, null, it.value)
  242. }
  243. }
  244. // set default value
  245. cf.getCustomFieldType().setDefaultValue(targetFC, cf.getCustomFieldType().getDefaultValue(fieldConfig))
  246. }
  247. String getDescription(Map params, boolean forPreview) {
  248. def projectIds = convertProjectKeysToIds(params[FIELD_PROJECT_IDS])
  249. def issueTypeIds = convertToListIfNecessary(params[FIELD_ISSUE_TYPE_IDS])
  250. def fcsId = params[FIELD_FCS] as Long
  251. def fcs = fieldConfigSchemeManager.getFieldConfigScheme(fcsId)
  252. def total =updateOptionsForIssues(projectIds, issueTypeIds, fcs.getField(), null, true)
  253. return getDescription() + "<br><b>$total</b> issues will be updated. The time this takes is proportional to the number " +
  254. "of issues updated."
  255. }
  256. Boolean isFinalParamsPage(Map params) {
  257. params[FIELD_FCS]
  258. }
  259. }