Dynamic Checkbox Selection On Create

Harvey Bennett July 13, 2021

Does anyone know if there is solution or plugin out there to load in checkboxes on the create screen that would be dynamic based on the logged in user. The checkboxes will be completely different based on the logged in user what each checkbox name would be. For instance if I'm logged in, I might see Suzy and Jill for my checkbox options. But if Bob logs in he might see Larry and Cindy as his checkbox options. I need this custom field to be dynamically loaded in and be able to be stored and referenced later on. The dropdown will have 1000's of possible combinations and will be changing rapidly so a static custom field with options is not going to be sufficient. I was hoping something existed where these could be "smart loaded in". The user might only select 1,2 of the options but those options should be stored in the Jira ticket. 

 

Edit: I just need a way to capture this data and pass it off to a user picker field. They do not want to have to manually type and search for their users and would rather have them listed and they check a box next to that users name. I was going to use ScriptRunner to move that value into a real multi user picker field for data integrity purposes. 

1 answer

1 accepted

1 vote
Answer accepted
Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
July 13, 2021

That sounds like a decent use case for Select List Conversion using Behaviours

But instead of loading the list based on user input, you query the source (could be hardcoded or some remote data source) in the behaviour initializer and apply the options using setFieldOptions.

I don't think this can be formatted as checkboxes, it would still work.

For example:

import com.atlassian.jira.component.ComponentAccessor
def field = getFieldByName('Name Selection')
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def sampleData = [
[userName: 'Suzy', showFor: ['Harvey']],
[userName: 'Jill', showFor: ['Harvey', 'Bob']],
[userName: 'Larry', showFor: ['Bob']],
[userName: 'Cindy', showFor: ['Bob']],
/* ... add more ... */
] //this could be retreived from an external API or DB call

def usersToShow = sampleData.findAll{it.showFor.contains(currentUser.name)}*.userName
def optionsMap = usersToShow.collectEntries{[(it):it]}
field.convertToMultiSelect().setFieldOptions(optionsMap)

In my example, you will see Suzy and Jill, but Bob would see Jill, Larry and Cindy.

The sample data structure could be reversed if that matches your case better.

Harvey Bennett July 14, 2021

@Peter-Dave Sheehan This is promising. The user list is actually local to Jira. So my list is generated based on a JQL response returned against my user profile. I would see if my name is listed as a supervisor on that list and in a separate custom field I'm tracking users that I manage. So this other custom field with the users that I'm manage I could preload with their usernames into the userName (key,value) map). Looks fantastic. 

This does look like it can be manipulated by Behaviors/Initializer using ScriptRunner but I'm curious how you are storing values in that field without it being defined in the Field Configuration Scheme? Wouldn't all the names need to be defined up front in order for it to be stored in that custom field? Wouldn't I lose the values stored on create if they aren't defined as possible options? I also do not know if I want possibly 1000's of options being stored in a custom field for performance reasons?

If it was multiselect that would work for me too, just needs to be see something where the supervisor would not need to know the names of the top of their head (so a list of some sorts).

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
July 14, 2021

convertToSingleSelect and convertToMultiSelect methods in behaviours work with free text fields only.

They just convert the UI to behave differently. The underlying data stored will be the key of the options map. In the case of Multi conversion, the data is a semi-colon separated string like:  "Suzy; Jill; Larry".

This isn't great from a search/indexing standpoint.

But if you are going to use the data downstream in some other automation, you just have to tokenize the string.

For example in a workflow transition using a scriptrunner condition, validator or postfunction, you would get the data like this:

import com.atlassian.jira.component.ComponentAccessor
def nameSelectionCf = ComponentAccessor.customFieldManager.getCustomFieldObjectsByName('Name Selection')[0]
def selectedNames = issue.getCustomFieldValue(nameSelectionCf)
selectedNames = selectedNames.tokenize(';')

def selectedUsers = selectedNames.collect{ComponentAccessor.userManager.getUserByName(it)}

If searching and indexing are important to you, then you would need to somehow load /refresh all your options in the configuration (can be done with a script job that runs daily) and use a variant of what I shared to filter the available options at runtime.

But performance on that would not be great. Behaviours run on the browser side first. So the form would have to load all 1000+ options, then send a request to your serverside script to ask for the filtered list. Then the browser-side javascript would update the list options to just those returned by the server-side script.

Harvey Bennett July 15, 2021

@Peter-Dave Sheehan This makes sense. I've been trying to play with this on and off when I can. Any reason why this would not convert it to appear as a multiselect field? I have my text field that I'm trying to convert on the create screen. It's is a multi-line text field. 

def field = getFieldByName("Users To Manage") // Multi-line text field
field.convertToMultiSelect()

Any ideas why this would still be a text box?

Thanks for your assistance :) 

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
July 15, 2021

What Renderer is applied to that field in your project field configuration?

I think if you will use a multiline text field, you have to make sure it's a "Default Text Rendered" and not Wiki or Rich Text.

Here ... I just tried on a random multi-line text field in my environment:

2021-07-15 13_02_28-Window.png

Of course, there is no data available to select in the multi select yet.

Harvey Bennett July 22, 2021

@Peter-Dave Sheehan Sorry for the delay. I have had the ability to test what you have written in a different environment and I'm seeing some success. Having some issues still though and not entirely sure why. I am seeing that this works like a champ in Jira Software but I'm having issues with it in Jira Service Management. The Behavior is running because I can see output in the jira-log file when i output words like "Hello". I'm noticing it is converting the text field into a more minified text field (looks like a text field with no CSS). I have tried tinkering with the JavaScript Ajax version of this method "convertToMultiSelect()" and it looks like I cannot get options to load in properly but the field does successfully convert when using the Ajax method. Do you have any idea how to make your above method work for Jira Service Management instead or how the Ajax option loads in options? 

This is the code that I'm working with (hardly any deviation from what you've written)

// https://community.atlassian.com/t5/Jira-questions/Dynamic-Checkbox-Selection-On-Create/qaq-p/1749221
import com.atlassian.jira.component.ComponentAccessor
def field = getFieldByName("Remove User")
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def sampleData = [
[userName: 'Suzy', showFor: ['harvey.bennett']],
[userName: 'Jill', showFor: ['harvey.bennett', 'Bob']],
[userName: 'Larry', showFor: ['Bob']],
[userName: 'Cindy', showFor: ['Bob']],
] //this could be retreived from an external API or DB call

def usersToShow = sampleData.findAll{it.showFor.contains(currentUser.name)}*.userName
def optionsMap = usersToShow.collectEntries{[(it):it]}
field.convertToMultiSelect().setFieldOptions(optionsMap)

 

This is it working in Jira Software create screen.

Image 1.PNG

 

This is what it tries to do in Jira Service Management create screen

Image 2.PNG

 

I started trying to use a REST endpoint but I would really rather just load it in from that variable called sampleData but none of the examples explain how this could be done from a local data set :/

The Ajax code I've been working with is as follows: 

// used for debugging
import org.apache.log4j.Level
import org.apache.log4j.Logger
log.setLevel(Level.DEBUG)
//log.debug("STARTING SCRIPT")

// https://community.atlassian.com/t5/Jira-questions/Dynamic-Checkbox-Selection-On-Create/qaq-p/1749221
import com.atlassian.jira.component.ComponentAccessor
def field = getFieldByName("Remove User")
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser

def sampleData = [
[userName: 'Suzy', showFor: ['harvey.bennett']],
[userName: 'Jill', showFor: ['Harvey', 'Bob']],
[userName: 'Larry', showFor: ['Bob']],
[userName: 'Cindy', showFor: ['Bob']],
] //this could be retreived from an external API or DB call

//def usersToShow = sampleData.findAll{it.showFor.contains(currentUser.name)}*.userName
//def optionsMap = usersToShow.collectEntries{[(it):it]}
field.convertToMultiSelect([
ajaxOptions: [
url: "MYCOMPANYURLHERE",
query: true, // keep going back to the sever for each keystroke
keyInputPeriod: 500,
minQueryLength: 0,
formatResponse: "general",
],
])

 

Result so far for the Ajax call on Jira Service Management:

Image 3.PNG

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
July 22, 2021

I had not used/test the convert options in JSM.

I found the same CSS issue when using the convert method without ajax options.
It might be worth reporting that to adaptavist.

But since it appears to work ok when using ajax options, I think this will be your best bet.

Here is a sample scriptrunner rest API where you can still maintain the mapping in sampleData and returns the content of that sample data in the appropriate format for the converted field:

import com.atlassian.jira.component.ComponentAccessor
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonBuilder
import groovy.transform.BaseScript

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

//you will need to include all the groups that should be able to query this
getUserNamesForCurrentUser (httpMethod: "GET", groups: ["jira-administrators"]) { MultivaluedMap queryParams, String body ->
def query = queryParams.getFirst("query") as String
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def currentUserName = 'Bob' //currentUser.name
def sampleData = [
   [userName: 'Suzy', showFor: ['harvey.bennett']],
    [userName: 'Jill', showFor: ['Harvey', 'Bob']],
   [userName: 'Larry', showFor: ['Bob']],
    [userName: 'Cindy', showFor: ['Bob']],
]

//first filter the sampleData for the current user and get just the matching userNames
def usersToShow = sampleData.findAll{it.showFor.contains(currentUserName)}*.userName
//then if a query (typeahead) has been entered, apply a second filter
if(query){
    usersToShow = usersToShow.findAll{it.contains(query)}
}
//create a return object
def rt = [
   items: usersToShow.collect{ userName->
   def html = userName
if(query) html = userName.replaceAll(/(?i)$query/) { "<b>${it}</b>" }
[
value: userName,
html: html,
label: userName
]
},
total: usersToShow.size(),
footer: 'Select a User'
]
return Response.ok(new JsonBuilder(rt).toString()).build();
}

 

Then in your behaviour, you call it like this (I used my summary field for testing):

def fld = getFieldById('summary')
fld.convertToMultiSelect(
[
ajaxOptions: [
url : getBaseUrl() + "/rest/scriptrunner/latest/custom/getUserNamesForCurrentUser",
query: true,
formatResponse: "general"
]
]
)

And it looks like this in the portal:

2021-07-22 16_42_50-My Service Desk - Home.png

Like Harvey Bennett likes this
Harvey Bennett July 22, 2021

Wow this works like a champ. I will have to raise that up with Adaptavist to take a look at that method call. Prefer simplicity and feel like the REST call is a little overkill. But I'm glad I have gotten the ability to see one of these in action (first time I've ever interacted with the REST endpoints). 

 

Had a very strange phenomenon occur with the getBaseURL method. For some reason it is getting an unsecure version of the Jira instance I'm working from (not entirely sure why its doing that). Needless to say it was throwing up JS popup errors when I was clicking on the box but that has been resolved and I'm seeing what you are now. 

 

You know how to write a thing or two haha. I can't thank you enough!! 

 

Kindly,

Harvey Bennett

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
July 22, 2021

Double-check your base url in system configuration, that's what it should return.

You can also confirm using the groovy console with:

com.atlassian.jira.component.ComponentAccessor.applicationProperties.getString('jira.baseurl')
Like Harvey Bennett likes this

Suggest an answer

Log in or Sign up to answer