Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

How do I bulk edit saved filters in ScriptRunner?

Diana
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
September 21, 2022

We have a custom field called "Team" and it has single select values [Team A, Team B, Team C etc].

Team A decides to change their name to Team A-1. It's not a problem to edit the custom field values in the Jira admin settings, and all the issues gets updated. But all saved filters that still uses jql "Team = 'Team A'" does not get changed when the custom field value is edited.

I'm trying to get a script to run on Console that will first find all saved filters that contains "Team = Team A". Then, print the results to show the filter owner, query string, and filter name.

Then, the second run, get all those filters and update the string to be "Team = Team A-1".

But the code I run won't make the replaceAll changes. I'm using custom field ID in case I want to reuse this script for another custom field.

 

import com.atlassian.jira.bc.JiraServiceContextImpl
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.filter.SearchRequestService
import com.atlassian.jira.issue.search.SearchRequest
import com.atlassian.jira.bc.user.search.UserSearchParams
import com.atlassian.jira.bc.user.search.UserSearchService
import java.lang.StringBuffer
import java.lang.StringBuilder
import com.atlassian.jira.jql.parser.JqlQueryParser


SearchRequestService searchRequestService = ComponentAccessor.getComponent(SearchRequestService.class)
UserSearchService userSearchService = ComponentAccessor.getComponent(UserSearchService)
def sb = new StringBuffer()
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)

UserSearchParams userSearchParams = new UserSearchParams.Builder()
.allowEmptyQuery(true)
.includeInactive(false)
.ignorePermissionCheck(true)
.maxResults(5000)
.build()


//iterate over each user's filters
userSearchService.findUsers("", userSearchParams).each{ApplicationUser filter_owner ->
try {
searchRequestService.getOwnedFilters(filter_owner).each{SearchRequest filter->
String jql = filter.getQuery().toString()
def customField = ComponentAccessor.getCustomFieldManager().getCustomFieldObject(10800)//using ID if need to reuse this script for another field
def oldJQL = customField = A as String
def newJQL = customField = A-1 as String
def context = new JiraServiceContextImpl(filter_owner)
if (jql.contains(oldJQL) {
//jql.replaceAll(oldJQL, newJQL) //commented out because still not working
sb.append("Found: ${filter_owner.displayName}, ${filter.name}, ${filter.getPermissions().isPrivate() ? 'Private' : 'Shared'}, ${jql}\n")
}
}
} catch (Exception e) {
//if filter is private
sb.append("Unable to get filters for ${filter_owner.displayName} due to ${e}")
}
}

//output results
return sb.toString()

Any suggestions?

Side question: is something like this possible using REST API? Any guidance on that?

1 answer

1 accepted

Suggest an answer

Log in or Sign up to answer
1 vote
Answer accepted
Mohamed Benziane
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
September 23, 2022

Hi,

First i think the best option it to communicate about this change and let the owners make the change themselves.

But you can use the API to get all filter

/rest/api/3/filter/search

Then, get the owner to send them a message if the JQL match the customfield or update the filter by your own.

https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-filters/#api-rest-api-3-filter-id-put

Diana
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
September 26, 2022

Hi @Mohamed Benziane 

I'm afraid I'll have to agree. It would certainly be nice to have such a feature with Scriptrunner or Jira natively. I'll accept this as an answer, but I won't give up on this script just yet. I'll keep this on my backlog, and hopefully in the future someone can add a comment to this question.

Thanks,

Lopes_ Bonnie October 12, 2022

@Diana Gorv ,

Did you every get your script to work?  I have the exact same use case and have over 600 filters using the team name field I need to change to Team A-1 and changing them all manually is just not a good use of anyone's time just because a team decided to change their name.  I am really hoping you got this to work and wouldn't mind sharing your code.

I run Jira Server 

Diana
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 12, 2022

@Lopes_ Bonnie Unfortunately, I have not found a way. The most I was able to do was find all saved filters that contains the JQL string I want to change. For some reason, I can't seem to get "setQuery" to recognize the "newString". Also, I have not found a way for this to work for Quick Filters on Boards or Structure filters, if you use Structure. 

Also I have not found a way through REST API either.

I end up telling the team that they'll have to make new filters with their new team name.

But you or anyone can try to work off what I have. Maybe you'll have better luck than me.

import com.atlassian.jira.bc.JiraServiceContextImpl
import.java.lang.StringBuffer
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.filter.SearchRequestService
import com.atlassian.jira.issue.search.SearchRequest
import com.atlassian.jira.bc.user.search.UserSearchParams
import com.atlassian.jira.bc.user.search.UserSearchService
import java.lang.StringBuilder
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.bc.issue.search.SearchService

//important variables and components for later
SearchRequestService searchRequestService = ComponentAccessor.getComponent(SearchRequestService.class)
UserSearchService userSearchService = ComponentAccessor.getComponent(UserSearchService)
StringBuilder output = StringBuilder.newInstance()
def sb = new StringBuffer()
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchService = ComponentAccessor.getComponent(SearchService)

//parameters for searching queries
UserSearchParams userSearchParams = new UserSearchParams.Builder()
.allowEmptyQuery(true)
.includeInactive(false)
.ignorePermissionCheck(true)
.maxResults(5000)
.build()

//iterate over each user's filters
userSearchService.findUsers("", userSearchParams).each {ApplicationUser filter_owner ->
try{
searchRequestService.getOwnedFilters(filter_owner).each { SearchRequest filter ->
String jql = filter.getQuery().toString()
def customField = ComponentAccessor.getCustomFieldManager().getCustomFieldObject(12345)//**CHANGE FIELD ID HERE** to search for the custom field you want
def oldJQL = customField = "Team A" as String// **INPUT OLD VALUE HERE**
def newJQL = customField = "Team A-1" as String// **INPUT NEW VALUE HERE**
def context = new JiraServiceContextImpl(filter_owner)

//First Run = checks each filter, get JQL, and check if it contains out string. Useful for verifying before making changes
//Second Run = comment out sb.append and un-comment the others to perform the bulk editing, but still WIP...
if(jql.contains(oldJQL)) {
//String newString = jql.replace(oldJQL,newJQL)
//def newFilter = searchService.parseQuery(filter_owner,newString).query
//filter.setQuery(newFilter)
//searchRequestService.updateFilter(context,filter)
sb.append("Found: '${filter.name}' owned by ${filter_owner.displayName} - {filter.getPermissions().isPrivate() ? 'Private' : 'Shared'} - JQL: {jql} ||")
}
}
} catch (Exception e) {
//checks if filter is private
sb.append("Unable to get filters for ${filter_owner.displayName} due to ${e}")
}
}
return sb.toString()
Lopes_ Bonnie October 12, 2022

Thank you for the reply.  I have given your script to my technical folks and hope they can figure out a way to get it to work.  If they do.  I will surely post it here for all.

 

BTW, I use a plug-in called Configuration manager for Jira that will allow me to search and retrieve all filters that contain a search string.  It is quite useful, as it also tells me where a particular filter is used, on what boards and/or dashboards.  Besides filters there are many other configuration items.  (Power Admin) is the component within the Configuration Manager for Jira and I believe you can get Power Admin for free.  

David Niro
Marketplace Partner
Marketplace Partners provide apps and integrations available on the Atlassian Marketplace that extend the power of Atlassian products.
October 24, 2022

Hello @Diana Gorv ,

For updating your Structure Generators, you could use a script like this:

import com.atlassian.jira.component.ComponentAccessor;
import org.apache.log4j.Logger
import org.apache.log4j.Level

def log = Logger.getLogger('script.internal')
log.setLevel(Level.DEBUG)

def plugin = ComponentAccessor.pluginAccessor.getPlugin("com.almworks.jira.structure")
def structureManager = plugin.getModuleDescriptor('structure-manager').module
def forestService = plugin.getModuleDescriptor('forest-service').module
def rowManager = plugin.getModuleDescriptor('structure-row-manager').module
def generatorManager = plugin.getModuleDescriptor('generator-manager').module

def loadClass = {name -> plugin.classLoader.loadClass("com.almworks.jira.structure.api.$name")}

def PermissionLevel = loadClass('permissions.PermissionLevel')
def ForestSpec = loadClass('forest.ForestSpec')
def CoreIdentities = loadClass('item.CoreIdentities')

def structures = structureManager.getAllStructures(PermissionLevel.ADMIN)

/* Get all generator specs */
def generatorIds = new ArrayList();
for (def structure : structures) {
  def forestSpec = ForestSpec.structure(structure.id)
  def forest = forestService.getForestSource(forestSpec).latest.forest
  for (def rowId : forest.rows.toNativeArray()) {
    def itemId = rowManager.getRow(rowId).itemId
    if (CoreIdentities.isGenerator(itemId)) {
      generatorIds.add(itemId.longId)
    }
  }
}

/* Find generators with jql that needed for your case (with old usernames) and replace them to ones with new usernames */
for (def genId : generatorIds) {
  def spec = generatorManager.getGenerator(genId)
  def params = spec.parameters
  if (params.containsKey('jql') && params.containsValue ('project=test') /* replace 'project=test' with your original jql */) {
    log.debug "old jql: ${params.get('jql')}"
    def newJql =  'project=demo' /* replace 'project=demo' with your new jql */
    def updatedParams = new LinkedHashMap(params);
    updatedParams.put('jql', newJql);
    log.debug "new jql: ${updatedParams.get('jql')})"
    generatorManager.updateGenerator(genId, spec.moduleKey, updatedParams, spec.owningStructure)
  }
}

Unfortunately, it will not work with Transformations.  There is not a way to update those in a similar manner.

Hope this helps!

Best,
David

Diana
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
October 24, 2022

Hi @David Niro 

Thank you for this!

Unfortunately, I got an error "java.lang.ClassNotFoundException: com.almworks.jira.structure.apipermissions.PermissionLevel not found by com.almworks.jira.structure [18]"

Our version is 8.13.8, so if it requires an api, perhaps that is the cause? Since our instance does not have api functionalities yet. (Working on upgrading eventually).

David Niro
Marketplace Partner
Marketplace Partners provide apps and integrations available on the Atlassian Marketplace that extend the power of Atlassian products.
October 25, 2022

Hi @Diana Gorv ,

You are very welcome.  

It does require and API, which explains the error received.

Best,
David

TAGS
AUG Leaders

Atlassian Community Events