Skip to content

Instantly share code, notes, and snippets.

@HakShak
Last active November 23, 2015 16:42
Show Gist options
  • Save HakShak/8bd0d0c0dca40c5dfb42 to your computer and use it in GitHub Desktop.
Save HakShak/8bd0d0c0dca40c5dfb42 to your computer and use it in GitHub Desktop.
JIRA Custom Field Cleanup/Sanity Check/Repair Script to make sure custom fields aren't slowing bulk changes down. WARNING: BREAKS ALL THE THINGS
/**
* This is broken and I am not sure why. It makes the Custom Fields admin page
* and configurations look exactly like you want them to, but screens for creating issues does terrible things.
* I'm thinking it might be the "isGlobal()" check in the inspirational material...
*
* Created by nicholas.herring@ccpgames.com on 11/20/2015.
* This script attempts to sanity check and repair the custom fields in a JIRA installation.
* Because there is a terrible evolution of custom fields, the admin interface has a bad practice flow.
* All new custom fields are added to all projects and all issues types. This is bad for large installations.
* It makes things like bulk edit to timeout when you have ~250 unrestricted custom fields.
*
* This script will only touch custom fields with global project or global issue type set.
*
* 1. The script iterates through all custom fields and finds any associated screens.
* (There is probably a helper function somewhere for this, but I couldn't find it.)
* 2. Then we find all projects associated with those screens. (Yay helper funtions!)
* 3. Then we associate the custom fields with only those projects.
* (Inspiration: https://answers.atlassian.com/questions/192749/set-project-field-configuration-scheme-programmatically )
* 4. Then we take all the project issue types and associate the issue types with the custom fields.
* (Inspiration: https://bitbucket.org/topmanage/tm-project-templates/src/924705163705b0bb8e711e3cc01af23d742fe254/src/main/java/com/topmanage/jiraplugins/projecttemplates/ProjectCloner.java?at=master&fileviewer=file-view-default
* Search for "cfConfigScheme" )
* 5. Email the sanity check results.
*
* This script will email the changes it wants to make. Once you are happy with those, uncomment out the line that starts
* with "fieldConfigSchemeManager".
*/
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.context.manager.JiraContextTreeManager
import com.atlassian.jira.issue.fields.config.FieldConfig
import com.atlassian.jira.issue.fields.config.manager.FieldConfigManager
import com.atlassian.jira.issue.fields.config.manager.FieldConfigSchemeManager
import com.atlassian.jira.issue.fields.screen.FieldScreenManager
import com.atlassian.jira.issue.fields.screen.ProjectFieldScreenHelper
import com.atlassian.mail.Email
import com.atlassian.mail.server.MailServerManager
import com.atlassian.jira.issue.customfields.CustomFieldUtils
import com.atlassian.jira.issue.fields.config.FieldConfigScheme
def customFieldManager = ComponentAccessor.getComponent(CustomFieldManager)
def fieldScreenManager = ComponentAccessor.getComponent(FieldScreenManager)
def fieldConfigManager = ComponentAccessor.getComponent(FieldConfigManager)
def fieldConfigSchemeManager = ComponentAccessor.getComponent(FieldConfigSchemeManager)
def projectFieldScreenHelper = ComponentAccessor.getComponent(ProjectFieldScreenHelper)
def mailServerManager = ComponentAccessor.getComponent(MailServerManager)
def mailServer = mailServerManager.getDefaultSMTPMailServer()
def allScreens = fieldScreenManager.getFieldScreens()
def result = "Log start"<<"<BR>"
//stats
int checked = 0
int skipped = 0
int sane = 0
int repaired = 0
int failed = 0
int needDelete = 0
customFieldManager.getCustomFieldObjects().each { customField ->
result << "<HR>Processing Custom Field: <font color=\"blue\">" << customField.getFieldName() << "</font><BR>"
checked++
if (customField.getFieldName().contains("Epic") || customField.getFieldName().contains("Story Points") || customField.getFieldName() == "Sprint") {
//Don't touch anything with "Epic" in it.
result << "<font color=\"green\">- This field looks like it has to do with JIRA Agile. Skipping.</font>" << "<BR>"
skipped++
return
}
//Field Schemes are ancient, so there is only one of them these days.
def scheme = customField.getConfigurationSchemes().get(0)
//Check if contexts or configs are empty - This may happen if you fuck up this script and the update fails resulting
//in all associations for projects and issue types being removed. Fear not, we should be able to rebuild them from
//the associated screens.
def hasConfigs = false
def hasContexts = false
if (scheme.getConfigs().size > 0) {
hasConfigs = true
}
if (scheme.getContexts().size() > 0)
hasContexts = true
if (!customField.isAllProjects() && !customField.isAllIssueTypes() && (hasConfigs || hasContexts)) {
result << "<font color=\"green\">- This field has associated projects and not all global issues, which means it's likely sane. Skipping.</font>" << "<BR>"
sane++
return
}
def suggestedProjects = [:]
def hasAtLeastOneScreen = false
//Figure out which screen has our custom field. This is how we link back to projects, and then issue types
allScreens.each { screen ->
if (screen.name == "Default Screen") {
//skip this. We probably don't want to touch it.
return
}
if (screen.containsField(customField.getId())) {
hasAtLeastOneScreen = true
def projects = projectFieldScreenHelper.getProjectsForFieldScreen(screen)
projects.each { project ->
suggestedProjects[project.name] = project
}
}
}
if (!hasAtLeastOneScreen) {
result << "<font color=\"red\">- No screens are showing this field, which means it is hidden and should probably deleted after data is migrated from it.</font>" << "<BR>"
needDelete++
return
}
//Projects------------
//Associate only projects that are referenced in discovered screens
result << "<font color=\"red\">- No associated projects. This is bad because it adds the field to all projects.</font>" << "<BR>"
def projectLongs = []
suggestedProjects.each { name, object ->
result << "<font color=\"orange\">-- Associating Project: " << name << "</font><BR>"
projectLongs << object.getId()
}
//Apply suggested projects to custom field.
//Holy shit out server is out of date. :/
//This should be updated to use the ProjectManager parameters.
def suggestedJiraContextNodes = CustomFieldUtils.buildJiraIssueContexts(false, new Long[0], projectLongs.toArray(new Long[0]), ComponentAccessor.getComponent(JiraContextTreeManager))
//End Projects--------
//Issue Types - YOU CANNOT MODIFY THESE WITHOUT PROJECT CONTEXTS!!!
//Of course issue types are done differently. Can't just use contexts, have to use Field Configs inside Field Config Schemes. FML
result << "<font color=\"red\">- Global issue type associated. This is bad because it adds the field to all issue types.</font>" << "<BR>"
def suggestedIssueTypes = [:]
//Find the issues types for the suggested projects
suggestedProjects.each { name, object ->
object.getIssueTypes().each { issueType ->
suggestedIssueTypes[issueType.getId()] = issueType
}
}
//Create configs for each issue type
//This guy lets us change out the configs on the existing scheme
FieldConfigScheme.Builder builder = new FieldConfigScheme.Builder(scheme)
//Need config for field to associate with each issue type
FieldConfig config = fieldConfigManager.getFieldConfig(scheme.getId())
HashMap<String, FieldConfig> configs = new HashMap<String, FieldConfig>();
//Abusing maps for single instance
suggestedIssueTypes.each { id, object ->
result << "<font color=\"orange\">-- Associating Issue Type: " << object.name << "</font><BR>"
configs.put(id, config)
}
builder.setConfigs(configs)
scheme = builder.toFieldConfigScheme()
//End Issue Types
try {
//Apply the issue types through the scheme and the projects through the contexts.
//fieldConfigSchemeManager.updateFieldConfigScheme(scheme, suggestedJiraContextNodes, customField)
repaired++
}
catch (Exception e) {
result << "<font color=\"red\">- Failure to update: " << e << "<BR>"
failed++
}
}
def emailBody = "Checked: " << checked << "<BR>"
emailBody << "Sane: " << sane << "<BR>"
emailBody << "Skipped: " << skipped << "<BR>"
emailBody << "Need to delete: " << needDelete << "<BR>"
emailBody << "Repaired: " << repaired << "<BR>"
emailBody << "Failed: " << failed << "<BR><BR>"
emailBody << result
//Save our changes
customFieldManager.refresh()
//Yell at the user who ran it.
User currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();
Email email = new Email(currentUser.getEmailAddress())
email.setSubject(("Custom Field Sanity Check Results - " << new Date()).toString())
email.setMimeType("text/html")
email.setBody(emailBody.toString())
mailServer.send(email)
return "Email sent to " << email.getTo()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment