Last active
November 23, 2015 16:42
-
-
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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