/src/main/resources/com/onresolve/jira/groovy/canned/admin/SplitCustomFieldContext.groovy
Groovy | 307 lines | 251 code | 48 blank | 8 comment | 22 complexity | 9709232b5f4a178316806cf720ef825a MD5 | raw file
- package com.onresolve.jira.groovy.canned.admin
-
- import com.onresolve.jira.groovy.canned.CannedScript
- import com.onresolve.jira.groovy.canned.CannedScriptArg
- import com.atlassian.jira.util.ErrorCollection
- import com.onresolve.jira.groovy.canned.utils.CannedScriptUtils
- import com.atlassian.jira.issue.context.manager.JiraContextTreeManager
- import com.atlassian.jira.ComponentManager
- import com.atlassian.jira.issue.customfields.CustomFieldUtils
- import com.atlassian.jira.issue.fields.config.FieldConfigScheme
- import com.atlassian.jira.issue.fields.config.manager.FieldConfigSchemeManager
- import com.atlassian.jira.component.ComponentAccessor
- import com.atlassian.jira.issue.fields.screen.FieldScreenManager
- import org.apache.log4j.Category
- import com.atlassian.jira.issue.customfields.option.Option
- import com.atlassian.jira.issue.fields.CustomField
- import com.atlassian.jira.jql.builder.JqlQueryBuilder
- import com.atlassian.jira.issue.search.SearchRequest
- import com.atlassian.jira.issue.search.SearchResults
- import com.atlassian.jira.web.bean.PagerFilter
- import com.atlassian.jira.issue.util.IssueChangeHolder
- import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
- import com.atlassian.jira.issue.ModifiedValue
- import com.atlassian.jira.util.ImportUtils
- import com.atlassian.jira.issue.search.SearchProvider
- import com.atlassian.jira.issue.index.IssueIndexManager
- import com.atlassian.jira.issue.fields.config.FieldConfig
- import com.atlassian.jira.util.SimpleErrorCollection
- import com.atlassian.jira.issue.customfields.view.CustomFieldParams
- import com.atlassian.jira.issue.customfields.view.CustomFieldParamsImpl
-
- class SplitCustomFieldContext implements CannedScript{
-
- public static String FIELD_PROJECT_IDS = "FIELD_PROJECT_IDS"
- public static String FIELD_ISSUE_TYPE_IDS = "FIELD_ISSUE_TYPE_IDS"
- public static String FIELD_NEW_CONTEXT_NAME = "FIELD_NEW_CONTEXT_NAME"
- public static String FIELD_FCS = "FIELD_FCS"
-
- def fieldConfigSchemeManager = ComponentManager.getComponentInstanceOfType(FieldConfigSchemeManager.class)
- def optionsManager = ComponentAccessor.getOptionsManager()
- ComponentManager componentManager = ComponentManager.getInstance()
- def projectManager = componentManager.getProjectManager()
- Category log = Category.getInstance(SplitCustomFieldContext.class)
- def searchProvider = componentManager.getSearchProvider()
- def issueManager = componentManager.getIssueManager()
- def indexManager = ComponentManager.getInstance().getIndexManager()
-
-
- String getName() {
- "Split custom field contexts"
- }
-
- String getDescription() {
- "This script will split projects out a custom field context, duplicating option values and updating issues with the new option values."
- }
-
- public String getHelpUrl() {
- "https://studio.plugins.atlassian.com/wiki/display/GRV/Built-In+Scripts#Built-InScripts-SplitCustomFieldContext"
- }
-
- List getCategories() {
- ["ADMIN"]
- }
-
- List getParameters(Map params) {
- if ( !isFinalParamsPage(params)) {
- getDefaultParameters()
- }
- else {
- [
- [
- Name:FIELD_PROJECT_IDS,
- Label:"Project Name",
- Description:"""Which project(s) to move to the new context. You must select at least one.""",
- Type:"multilist",
- Values: getProjectsForFieldConfigScheme(params[FIELD_FCS] as Long),
- ],
- [
- Name:FIELD_ISSUE_TYPE_IDS,
- Label:"Issue Types",
- Description:"""Which issue types to associate with the new scheme. Leave blank for All.""",
- Type:"multilist",
- Values: getIssueTypesForFieldConfigScheme(params[FIELD_FCS] as Long),
- ],
- [
- Name:FIELD_NEW_CONTEXT_NAME,
- Label:"New context name",
- Description:"""Name for the new context - not significant really.""",
- ],
- ]
- }
- }
-
- List getDefaultParameters() {
- [
- [
- Name:FIELD_FCS,
- Label:"Name of custom field config scheme",
- Description:"The name of the field config scheme in which to import these values",
- Type:"groupedlist", // multilist requires map of maps
- Values: CannedScriptUtils.getFieldConfigSchemesByCustomField(true)
- ],
- ]
- }
-
- Map getProjectsForFieldConfigScheme(Long fcsId) {
- def fcs = fieldConfigSchemeManager.getFieldConfigScheme(fcsId)
- if (fcs.allProjects) {
- return CannedScriptUtils.getProjectOptions(false)
- }
- else {
- def rt = [:]
- fcs.getAssociatedProjects().each {
- rt.put(it.key, it.name)
- }
- rt
- }
- }
-
- Map getIssueTypesForFieldConfigScheme(Long fcsId) {
- def fcs = fieldConfigSchemeManager.getFieldConfigScheme(fcsId)
- if (fcs.allIssueTypes) {
- return CannedScriptUtils.getAllIssueTypes(false, true)
- }
- else {
- def rt = [:]
- fcs.getAssociatedIssueTypes().each {
- rt.put(it.id, it.name)
- }
- rt
- }
- }
-
- ErrorCollection doValidate(Map params, boolean forPreview) {
- def errorCollection = new SimpleErrorCollection()
-
- if (!params[FIELD_FCS]) {
- errorCollection.addError(FIELD_FCS, "Please select a field config scheme.")
- }
-
- // use this to detect if we're on the second page as this is always sent (because it's a text box).
- if(params.containsKey(FIELD_NEW_CONTEXT_NAME)) {
- if (!params[FIELD_PROJECT_IDS]) {
- errorCollection.addError(FIELD_PROJECT_IDS, "Please select one or more project(s).")
- }
- if (!params[FIELD_NEW_CONTEXT_NAME]) {
- errorCollection.addError(FIELD_NEW_CONTEXT_NAME, "Please choose a name for the new context.")
- }
- }
-
- errorCollection
- }
-
- def List convertToListIfNecessary(obj) {
- if (obj instanceof String) {
- obj = [obj]
- }
- obj
- }
-
- Map doScript(Map params) {
- def projectIds = params[FIELD_PROJECT_IDS]
-
- // convert projectIds to Longs
- projectIds = convertProjectKeysToIds(projectIds)
- log.debug(projectIds)
-
- def issueTypeIds = params[FIELD_ISSUE_TYPE_IDS] as List
- issueTypeIds = convertToListIfNecessary(issueTypeIds)
-
- def fcsId = params[FIELD_FCS] as Long
- def newContextName = params[FIELD_NEW_CONTEXT_NAME] as String
-
- def fcs = fieldConfigSchemeManager.getFieldConfigScheme(fcsId)
- // I think this is something that you can do in the API but you can't do in the UI
- // assert fcs.getConfigs().entrySet().size() == 1
- def fieldConfig = fcs.getOneAndOnlyConfig()
-
- FieldConfigScheme targetFCS
- FieldConfig targetFC
- (targetFCS, targetFC) = createNewFieldConfigScheme(projectIds, issueTypeIds, newContextName, fcs)
-
- def cf = targetFCS.getField() as CustomField
-
- copyFieldOptions(fieldConfig, targetFC, cf)
- updateOptionsForIssues(projectIds, issueTypeIds, cf, targetFC, false)
-
- params.put("targetFC", targetFC)
- params["output"] = "New custom field configuration scheme created."
- params
- }
-
- private List<Long> convertProjectKeysToIds(projectIds) {
- projectIds = convertToListIfNecessary(projectIds)
- projectIds = projectIds.collect {projectManager.getProjectObjByKey(it).id} as List
- projectIds
- }
-
- private List createNewFieldConfigScheme(List<Long> projectIds, List<String> issueTypeIds, String newContextName, FieldConfigScheme fcs) {
- JiraContextTreeManager jiraContextTreeManager = new JiraContextTreeManager(projectManager, ComponentManager.getInstance().getConstantsManager())
-
- List contexts = CustomFieldUtils.buildJiraIssueContexts(false,
- null,
- projectIds as Long[],
- jiraContextTreeManager);
-
- // update original FCS to remove the items selected - happens automatically
-
- def targetFCS = fieldConfigSchemeManager.createFieldConfigScheme(
- new FieldConfigScheme.Builder().setName(newContextName).setDescription("Generated by script runner plugin.").toFieldConfigScheme(),
- contexts,
- issueTypeIds.collect {componentManager.constantsManager.getIssueType(it)} ?: FieldConfigSchemeManager.ALL_ISSUE_TYPES,
- fcs.getField()
- )
- def targetFC = targetFCS.getOneAndOnlyConfig()
- componentManager.getFieldManager().refresh()
- [targetFCS, targetFC]
- }
-
- private Long updateOptionsForIssues(projectIds, issueTypeIds, cf, targetFC, boolean preview) {
- // update values from old to new - could be multi and cascading
- def builder = JqlQueryBuilder.newBuilder().where()
- if (projectIds) {
- builder = builder.project(projectIds as Long[]).and()
- }
- if (issueTypeIds) {
- builder = builder.issueType(issueTypeIds as String[]).and()
- }
- def query = builder.customField(cf.idAsLong).isNotEmpty().buildQuery()
- log.debug("Execute query to find issues to update: $query")
-
- SearchRequest sr = new SearchRequest(query)
- SearchResults results = searchProvider.search(sr.getQuery(), componentManager.getJiraAuthenticationContext().getUser(), PagerFilter.getUnlimitedFilter())
-
- if (!preview) {
- results.issues.each {
- def iss = issueManager.getIssueObject(it.id)
- def oldFieldValue = iss.getCustomFieldValue(cf)
-
- def targetValue
- if (oldFieldValue instanceof CustomFieldParams) {
- def optionValues = oldFieldValue.getKeysAndValues().values()*.first()*.value
-
- def rootOptVal = optionValues[0]
- if (!rootOptVal) {
- log.debug("should never get here")
- return
- }
- def Option rootOption = optionsManager.getOptions(targetFC).find {it.value == rootOptVal} as Option
- def valuesMap = [
- null: [rootOption],
- ]
- if (optionValues.size() == 2) {
- valuesMap.put("1", [optionsManager.findByParentId(rootOption.optionId).find {it.value == optionValues[1]}])
- }
- targetValue = new CustomFieldParamsImpl(cf, valuesMap)
-
- }
- else {
- targetValue = optionsManager.getOptions(targetFC).find {it.value == oldFieldValue.value}
- }
-
- IssueChangeHolder changeHolder = new DefaultIssueChangeHolder()
- log.debug("Update issue ${iss.key}")
- cf.updateValue(null, iss, new ModifiedValue(oldFieldValue, targetValue), changeHolder)
-
- boolean wasIndexing = ImportUtils.isIndexIssues();
- indexManager.reIndex(iss);
- ImportUtils.setIndexIssues(wasIndexing);
- }
- }
-
- results.total
- }
-
- private void copyFieldOptions(FieldConfig fieldConfig, targetFC, CustomField cf) {
- def originalOptions = optionsManager.getOptions(fieldConfig)
-
- originalOptions.each {Option opt ->
- def newOpt = optionsManager.createOption(targetFC, null, null, opt.getValue())
- newOpt.setDisabled(opt.disabled)
- // todo: should be recursive to handle multi-level child options
- opt.childOptions.each {
- optionsManager.createOption(targetFC, newOpt.optionId, null, it.value)
- }
- }
-
- // set default value
- cf.getCustomFieldType().setDefaultValue(targetFC, cf.getCustomFieldType().getDefaultValue(fieldConfig))
- }
-
- String getDescription(Map params, boolean forPreview) {
- def projectIds = convertProjectKeysToIds(params[FIELD_PROJECT_IDS])
- def issueTypeIds = convertToListIfNecessary(params[FIELD_ISSUE_TYPE_IDS])
- def fcsId = params[FIELD_FCS] as Long
- def fcs = fieldConfigSchemeManager.getFieldConfigScheme(fcsId)
-
- def total =updateOptionsForIssues(projectIds, issueTypeIds, fcs.getField(), null, true)
-
- return getDescription() + "<br><b>$total</b> issues will be updated. The time this takes is proportional to the number " +
- "of issues updated."
- }
-
- Boolean isFinalParamsPage(Map params) {
- params[FIELD_FCS]
- }
- }