Hi there,
I know this type of question has been asked many times here, but I am stuck on my particular edge case. We have a Cascading Select field that is used throughout our company for business logic. It has over a hundred contexts. Fairly often, requests will be made to add an option (or more) to all contexts in the field. This would be a manual nightmare.
I know there is the most excellent built-in scriptrunner script for bulk updating custom fields options... and I would certainly use that if it could apply to all contexts at once. But alas, it makes me pick one at a time.
I know back in the day, the source for that script was available to look at. I know that is no longer an option. So.. looking at various examples, I started to cobble something together (see below). Right now what I have will iterate through all the contexts and list the Parent and Child options.
What I need help with is the bit where I want to add the new options. Let's say I have:
Foo1
Bar1
Bar2
Foo2
Bar3
Bar4
-- and I want to add those as options to each context. I'm assuming I'd need to put it in some sort of map, and add it to the options that way. But the devil's in the details, and I am hitting a wall.
I ended up creating this -- which seems to do the job. Feel free to suggest ways it can be improved! It uses a delimited text file (projectKey#parentOption#childOption) as its source.
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.context.IssueContextImpl;
import com.atlassian.jira.issue.customfields.option.Option
import groovy.xml.MarkupBuilder
def customFieldManager = ComponentAccessor.getCustomFieldManager();
def optionsManager = ComponentAccessor.getOptionsManager()
def projectManager = ComponentAccessor.getProjectManager()
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
Long parentSequence = 100L // used to indicate where parent option is displayed in field
Long childSequence = 100L // use to indicate where child option is displayed in field
/*
Notes:
-- script doesn't create contexts -- still need to implement that
it works fine if contexts exist already
-- it tokenizes on '#' currently, since some options may have
commas in them
*/
String filePath = "/path/to/file.txt"
filePath = filePath.trim()
def inputFile = new File(filePath);
def cf = customFieldManager.getCustomFieldObject(10107L) // Enter cf id
String issueTypeId = '10000' // Incident IssueType (replace with whatever) -- used (with projectKey) to find issueContext.
// so I can get pretty output in the script runner console (could be used for email results too)
xml.style(type: "text/css",
'''
#scriptField, #scriptField *{
border: 1px solid black;
}
#scriptField{
border-collapse: collapse;
}
''')
xml.table(id: "scriptField") {
tr {
th("Project")
th("Parent")
th("Child")
th("notes")
}
List lines = inputFile.readLines()
//lines.remove(0) // remove header in input file
lines.each {
def fields = it.tokenize('#')
def projectKey = ""
def parent = ""
def child = ""
if (fields.size() == 3) {
projectKey = fields.get(0)
parent = fields.get(1)
child = fields.get(2) ?: "all"
} else {
projectKey = fields.get(0)
parent = fields.get(1)
child = "all"
}
// if (projeKey.equals("Foobar")) { // incase you want to test on one project first
try {
def projectId = projectManager.getProjectObjByKey(projectKey).getId()
def issueContext = new IssueContextImpl(projectId, issueTypeId)
def fieldConfig = cf.getRelevantConfig(issueContext);
def options = optionsManager.getOptions(fieldConfig);
def rootOptions = options.getRootOptions()
if (parentAlreadyPresent(rootOptions, parent)) { // don't add parent, but look it up
def parentOption = getExistingParent(rootOptions, parent)
Long parentOptionID = parentOption.optionId
if (childAlreadyPresent(parentOption, child)) { // don't add child, go to next line in file
tr {
td(projectKey)
td(parent)
td(child)
td("Parent exists; Child exists")
}
return
} else {
optionsManager.createOption(fieldConfig, parentOptionID, childSequence, child) // add child
childSequence++
tr {
td(projectKey)
td(parent)
td(child)
td("Parent exists; Child added")
}
}
} else {
def parentOption = optionsManager.createOption(fieldConfig, null, parentSequence, parent)
parentSequence++
if (childAlreadyPresent(parentOption, child)) { // don't add child, go to next line ine file
tr {
td(projectKey)
td(parent)
td(child)
td("Parent added; Child already exists (note: this shouldn't happen!!)")
}
return
} else {
Long parentOptionID = parentOption.optionId
optionsManager.createOption(fieldConfig, parentOptionID, childSequence, child)
childSequence++
tr {
td(projectKey)
td(parent)
td(child)
td("Parent added; Child added")
}
}
}
}
catch (e) {
td(projectKey)
td(parent)
td(child)
td(e.message)
}
// }
// else {
// return
// }
}
}
return (writer.toString())
boolean parentAlreadyPresent(List<Option> rootOptions, String value) {
for (Option o : rootOptions) {
if (o.getValue().equals(value)) {
return true;
}
}
return false;
}
def getExistingParent(List<Option> rootOptions, String value) {
for (Option o : rootOptions) {
if (o.getValue().equals(value)) {
return o;
}
}
}
boolean childAlreadyPresent(Option parentOption, String value) {
def childOptions = parentOption.getChildOptions()
for (Option childOption in childOptions) {
if (childOption.getValue().equals(value)) {
return true;
}
}
return false;
}
Hey everyone! Are you ready to make a difference? Join the Atlassian Social Impact Bingo event! ❌⭕
Play Bingo! 🙋🏻♂️Online forums and learning are now in one easy-to-use experience.
By continuing, you accept the updated Community Terms of Use and acknowledge the Privacy Policy. Your public name, photo, and achievements may be publicly visible and available in search engines.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.