After playing around through some community threads and testing, I created a script that allows you to
This is great for removing unneeded JSM customers in datacenter that appear in your user list under Internal Directory.
Recommended you limit results to 20k or less and run iteratively
(Script broken into two parts due to formatting)
import com.atlassian.crowd.manager.directory.DirectoryManager
import com.atlassian.jira.bc.user.search.UserSearchParams
import com.atlassian.jira.bc.user.search.UserSearchService
import com.atlassian.jira.component.ComponentAccessor
import groovy.xml.MarkupBuilder
import java.io.StringWriter
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.bc.user.UserService
final directoryToCheck = 'Jira Internal Directory'
// Get necessary Jira components
def directoryManager = ComponentAccessor.getComponent(DirectoryManager)
def userSearchService = ComponentAccessor.getComponent(UserSearchService)
def userService = ComponentAccessor.getComponent(UserService)
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
// Get the internal directory ID based on directory name
def internalDirectoryId = directoryManager.findAllDirectories()?.find { it.name.toString().toLowerCase() == directoryToCheck.toLowerCase() }?.id
// Build search parameters for user search
UserSearchParams.Builder paramBuilder = UserSearchParams.builder()
.allowEmptyQuery(true)
.includeActive(true) // Only active users
.includeInactive(false) // Exclude inactive users
.limitResults(200) // Adjust this if you have many users
// Get all active users from the internal directory
def allInternalDirectoryUsers = userSearchService.findUsers('', paramBuilder.build()).findAll { it.directoryId == internalDirectoryId }
// Method to check if a user does **not** have any assigned, reported, or commented issues
def isUserInactive(user) {
if (user instanceof ApplicationUser) {
def username = user.username
// Construct JQL queries for:
// - Issues assigned to the user
// - Issues reported by the user
// - Comments authored by the user
def assignedJQL = "assignee = \"${username}\""
def reportedJQL = "reporter = \"${username}\""
def commentedJQL = "issue in commentsBy(\"${username}\")"
try {
def assignedIssues = Issues.search(assignedJQL)
def reportedIssues = Issues.search(reportedJQL)
def commentedIssues = Issues.search(commentedJQL)
// If none of the queries return issues, the user is effectively inactive (no assigned, reported, or commented issues)
return (assignedIssues.size() == 0 && reportedIssues.size() == 0 && commentedIssues.size() == 0)
} catch (Exception e) {
println "Error executing JQL: ${e.message}"
return false
}
}
return false
}
// Collect inactive users for the table and prepare a list of deleted users
def inactiveUsers = []
def deletedUsers = []
allInternalDirectoryUsers.each { user ->
// Safely access user properties and check if they are active but have no issues or comments
if (user instanceof ApplicationUser && isUserInactive(user)) {
// Add the inactive user to the list (active but no issues, no comments)
inactiveUsers.add([username: user.username, email: user.emailAddress]) // Only add users with username and email
// Validate the deletion of the user
try {
def result = userService.validateDeleteUser(currentUser, user.username)
// Check if the validation is successful
if (result.isValid()) {
// If validation passes, delete the user
userService.removeUser(currentUser, result)
deletedUsers.add([username: user.username, email: user.emailAddress]) // Track deleted user
println "User deleted: ${user.username} (Email: ${user.emailAddress})"
} else {
println "User deletion validation failed for ${user.username}: ${result.errorCollection}"
}
} catch (Exception e) {
println "Error deleting user ${user.username}: ${e.message}"
}
}
}
// Create the StringWriter to generate HTML output
def stringWriter = new StringWriter()
def content = new MarkupBuilder(stringWriter)
// Create the header for the HTML output
content.html {
p("Users in Jira Internal Directory: ${allInternalDirectoryUsers?.size()}")
p("List of Deleted Users:")
// Print a table of deleted users
if (deletedUsers.size() > 0) {
table {
tr {
th("Username")
th("Email")
}
deletedUsers.each { user ->
tr {
td(user.username)
td(user.email)
}
}
Ok, but why is this raised as a question?
Sure you can use something like this but it's missing a few other checks that would be error-prone to suggest this so broadly. Sure it's a start, but it's by no means a generic solution for everyone. Leaving it as is will cause problems elsewhere that perhaps you might not be susceptible to with your use case, but I do recall finding some troubles when I wrote something similar.
Deleting users also drops some other data in parallel (dashboards come to mind, for instance), so that is already one possibility to cause "data loss" if the user was not JUST a jsm customer - and I can easily see someone copy pasting this on their instance and subsequently verifying their backup restoration procedure.
As you're using UserService, I recall coming across it leaving corrupt data related to favourite associations that had to be pre-wiped to avoid it, so that's another potential thing.
Additionally, I wouldn't trust indexes to decide whether to keep or delete anything. UserService validation should prevent the actual deletion anyway, but it's not optimal (you could as well not do any JQL check at all and just straight up pass every user for deletion, and now that doesn't feel very safe does it).
JQL with 'commentsBy' in it may run forever on sufficiently large instances (once you go over couple millions, you start noticing global functions in jql right away).
I'm not saying this won't work for you, but it shouldn't be relied on or be regarded as safe enough to suggest broadly.
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.