Continuous cleanup for Jira. Part 1.

Hi! 

 

After reading this article, I have tried to implement automation scripts for my Jira instance.

Today, I would like to share a few scripts related to clean up inactive users references to issue properties, group, role membership. 

 

 

Disclaimer,  as usual, everything should be tested in the test-environment first.

Also, I'll be happy if you share or provide advice for the related small scripts. All scripts were tested and executed on Jira 7 Server releases (7.2.1, 7.6.3, 7.6.10).

Well, you can use for execute groovy on Jira one of these apps (add-ons) - Scriptrunner for Jira (commercial) , MyGroovy (Opensource BSD-2) , Groovioli (Opensource BSD-2)

Well, let's next step is cleanup our Jira instance.

1. Let's remove memberships from group and project role for inactive users. (This script has been adopted for Jira 7 from Jamie Echlin answer from answer.atlassian.com)

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.ComponentManager
import com.atlassian.jira.bc.user.search.UserSearchService
import com.atlassian.jira.bc.user.search.UserSearchParams
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserUtil
import com.atlassian.jira.util.SimpleErrorCollection
import com.atlassian.jira.bc.projectroles.ProjectRoleService
import com.atlassian.jira.project.Project
import com.atlassian.crowd.embedded.api.Group
import com.atlassian.jira.security.roles.actor.UserRoleActorFactory
import com.atlassian.jira.security.GlobalPermissionManager
import org.apache.log4j.Logger
import org.apache.log4j.Level


def log = Logger.getLogger("com.gonchik.scripts.groovy.deleteInActiveUsersFromGroup")
log.setLevel(Level.DEBUG)


UserSearchService userSearchService = ComponentAccessor.getComponent(UserSearchService.class);
UserSearchParams userSearchParams = (new UserSearchParams.Builder()).allowEmptyQuery(true).includeActive(false).includeInactive(true).maxResults(100000).build();
boolean isPreview = false
for (ApplicationUser appUser : userSearchService.findUsers("", userSearchParams)) {
ApplicationUser user = appUser
def userUtil = ComponentAccessor.userUtil
// get the first user of the first group which has the ADMIN privilege...
// cannot use current user, not really sure who that is when run as a service
def adminUser = userUtil.getJiraAdministrators()[0]
log.debug("deactivateUser ${user.getName()}")
// Remove user from all groups...
def groups = userUtil.getGroupsForUser(user.name)
if (!groups.isEmpty()) {
try {
userUtil.removeUserFromGroups(groups, user)
log.info(userToRemove.name + " from groups")
} catch (Exception e){
log.info(user.name + " should be reviewed in AD")
log.error(e)
}
}
// Remove user from all roles...
ProjectRoleService projectRoleService = (ProjectRoleService) ComponentManager.getComponentInstanceOfType(ProjectRoleService.class);
SimpleErrorCollection errorCollection = new SimpleErrorCollection();
log.debug("Removing all roles references for ${user.getName()}")
projectRoleService.getProjectsContainingRoleActorByNameAndType(adminUser, user.getName(), 'atlassian-user-role-actor', errorCollection).each { Project project ->
log.debug("Remove user ${user.getName()} from role: ${project.getName()}")
}
if (!isPreview) {
projectRoleService.removeAllRoleActorsByNameAndType(adminUser, user.getName(), 'atlassian-user-role-actor', errorCollection)
}
println errorCollection.dump()

2.  Let's set unfavourite for inactive users favourite filters and dashboards.

def decreaseFavouriteCounter(ApplicationUser appUser){
def portalPageService = ComponentAccessor.getComponent(PortalPageService.class)
def pages = portalPageService.getFavouritePortalPages(appUser)
JiraServiceContext serviceContext = new JiraServiceContextImpl(appUser)
def isFavourite = false
pages.each { page ->
portalPageService.updatePortalPage(serviceContext, page, isFavourite)
log.debug "| ${page.id} | ${page.name} | ${page.favouriteCount} | ${page.ownerUserName} | ${page.permissions.isPrivate()} |"
}

// decrease the filter counts
def searchRequestService = ComponentAccessor.getComponent(SearchRequestService.class)
def filters = searchRequestService.getFavouriteFilters(appUser)
filters.findAll { filter ->
searchRequestService.updateFilter(serviceContext, filter, isFavourite)
log.debug "| ${filter.id} | ${filter.name} | ${filter.favouriteCount} | ${filter.ownerUserName} | ${filter.permissions.isPrivate()} |"
}
}

3. Let's remove private dashboards and dashboards favourite counter <= 1 for inactive users. 

def deletePrivateDashBoards(ApplicationUser appUser){
def portalPageService = ComponentAccessor.getComponent(PortalPageService.class)
def pages = portalPageService.getOwnedPortalPages(appUser)
JiraServiceContext serviceContext = new JiraServiceContextImpl(appUser)
pages.each { page ->
if (page.permissions.isPrivate() || page.favouriteCount <= 1) {
portalPageService.deletePortalPage(serviceContext, (long) page.id)
log.debug "| ${page.id} | ${page.name} | ${page.favouriteCount} | ${page.ownerUserName} | ${page.permissions.isPrivate()} |"
}
}
}

4. Let's to do the same as p.3 for filters.

def deletePrivateFilters(ApplicationUser appUser){
def searchRequestService = ComponentAccessor.getComponent(SearchRequestService.class)
def filters = searchRequestService.getOwnedFilters(appUser)
JiraServiceContext serviceContext = new JiraServiceContextImpl(appUser)
filters.findAll { filter ->
if (filter.permissions.isPrivate() || filter.favouriteCount <= 1) {
searchRequestService.deleteFilter(serviceContext, (long) filter.id)
log.debug "| ${filter.id} | ${filter.name} | ${filter.favouriteCount} | ${filter.ownerUserName} | ${filter.permissions.isPrivate()} |"
}
}
}

5. Also, I don't like to see in watchers field inactive users.  Therefore let's remove inactive users from watchers fields.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.user.search.UserSearchService
import com.atlassian.jira.bc.user.search.UserSearchParams
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.issue.watchers.WatcherManager
import org.apache.log4j.Logger
import org.apache.log4j.Level

def log = Logger.getLogger("com.gonchik.scripts.groovy.cleanupStopWatchingInactiveUsers")
log.setLevel(Level.DEBUG)

// this script shows how to clean up the inactive watchers
UserSearchService userSearchService = ComponentAccessor.getComponent(UserSearchService.class)
UserSearchParams userSearchParams = (new UserSearchParams.Builder()).allowEmptyQuery(true).includeActive(false).includeInactive(true).maxResults(100000).build()
WatcherManager watcherManager = ComponentAccessor.getComponent(WatcherManager.class)

for (ApplicationUser appUser : userSearchService.findUsers("", userSearchParams)) {
ApplicationUser userToRemove = appUser
watcherManager.removeAllWatchesForUser(userToRemove)
log.debug('Done for ' + userToRemove.getName())
}

 

6. And let's remove votes from inactive users. This thing was very useful for migrating Jira projects. 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.user.search.UserSearchService
import com.atlassian.jira.bc.user.search.UserSearchParams
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.issue.vote.VoteManager
import org.apache.log4j.Logger
import org.apache.log4j.Level

def log = Logger.getLogger("com.gonchik.scripts.groovy.cleanupVotesForInactiveUsers")
log.setLevel(Level.DEBUG)

// This script shows how to clean up the votes from inactive users
UserSearchService userSearchService = ComponentAccessor.getComponent(UserSearchService.class)
UserSearchParams userSearchParams = (new UserSearchParams.Builder()).allowEmptyQuery(true).includeActive(false).includeInactive(true).maxResults(100000).build()
VoteManager voteManager = ComponentAccessor.getComponent(VoteManager.class)

for (ApplicationUser appUser : userSearchService.findUsers("", userSearchParams)) {
ApplicationUser userToRemove = appUser
voteManager.removeVotesForUser(userToRemove)
log.debug('Done for ' + userToRemove.getName())
}

 

7. Of course, you can use tools from article, for very useful is Integrity checker from Configuration manager.

References:

https://community.atlassian.com/t5/Jira-articles/Spring-Cleaning-for-Jira/ba-p/766063

https://community.atlassian.com/t5/Jira-articles/Continuous-cleanup-for-Jira-Part-2/ba-p/997326

Next article is here.

 

Cheers, 

Gonchik Tsymzhitov

 

8 comments

Deleted user January 2, 2019

Hi @Gonchik Tsymzhitov,

Cannot wait to try these scripts out, as a JIRA Admin inactive users can become an issue when migrating data so I’m really happy that I found your article.

Do you know when part 2 will be available? 

Thanks 🙏 

Gonchik Tsymzhitov
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 3, 2019

Hi @[deleted] , 

 

you are right about migrating Jira instances :) I am also, on that found many interesting things. 

I am thinking which part of script I need to provide :) 

1. SQL consistency checker and cleanup on SQL level

2. Lucene index analyses

3. Cleanup and analyses 2 Jira isntances by REST  API - ( I use python for to do, it is need to do daily cleanup :) )

4. Groovy cleaners :) 

 

Cheers,

Gonchik Tsymzhitov

Deleted user March 4, 2019

These are great "cut and paste" scripts that most of us can use.  Thank you.

Like Gonchik Tsymzhitov likes this
Daniel Eads
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
November 3, 2019

Great work Gonchik! This helps reduce the time needed for cleanup significantly.

Like Gonchik Tsymzhitov likes this
Gonchik Tsymzhitov
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 3, 2020

@Daniel Eads  @[deleted]  Thanks!

Feel free to add more info, 

https://github.com/gonchik/cleanup-scripts

 

Also I can mirror into bitbucket as well

Casper Hjorth Christensen
Contributor
March 12, 2020

Hi @Gonchik Tsymzhitov 

Any chance you would update these scripts to match JIRA 8.6.X?

Like Gonchik Tsymzhitov likes this
Gonchik Tsymzhitov
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
March 12, 2020

Hi @Casper Hjorth Christensen , 

sure, I have plan to upgrade, also, I see some users already send to me a Pull Request.

Gonchik Tsymzhitov
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
March 12, 2020

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events