We are planning an upgrade of JIRA from 6.0.7 to 6.3.7, and in our testing have discovered that the built-in script to Rename a User ID is missing.
Our version of Scriptrunner is 3.0.7.
We rely heavily on this script to avoid orphaned objects in JIRA when our users leave the company. Any assistance here will be appreciated.
Community moderators have prevented the ability to post new answers.
I figured that we should share, I did a port of the old script in the previous versions, and so far it's been working for me. There are still some bugs with it but it should get the job done, the bugs that I've seen so far are:
-Sometimes the loading spinner just keeps going, and going, and going.... doesn't give you any report at the end. It's still going to do it's work, just wait a few minutes and it should still complete
-It may spam your logs with errors while it's cycling through all of your instance's filters. It's nothing critical, it might not be liking some of the characters in the filters? I'm still not sure what is causing this.
That being said it's worked for all of the times that I used it, I even corrected a small bug that preventing the groups from transferring properties when the user wasn't in the internal directory.
If there are any bugs that you're able to find, i'm all ears. Just place the file in your script home folder and it should load in the admin panel.
Last thing of note, I tested the rename, but not as much on the merge. That being said, from how the merge works it shouldn't be a problem.
//Renames Users, as a built-in script in the admin panel. Configured for Groovy 3.0 package com.onresolve.scriptrunner.canned.jira.admin import com.atlassian.core.ofbiz.util.CoreTransactionUtil import com.atlassian.event.api.EventPublisher import com.atlassian.jira.ComponentManager import com.atlassian.jira.ManagerFactory import com.atlassian.jira.bc.JiraServiceContextImpl import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.event.ClearCacheEvent import com.atlassian.jira.imports.project.parser.UserAssociationParser import com.atlassian.jira.issue.CustomFieldManager import com.atlassian.jira.issue.IssueManager import com.atlassian.jira.issue.customfields.CustomFieldType import com.atlassian.jira.issue.fields.UserField import com.atlassian.jira.issue.index.IssueIndexManager import com.atlassian.jira.issue.search.CachingSearchRequestStore import com.atlassian.jira.issue.search.SearchRequest import com.atlassian.jira.issue.search.SearchRequestManager import com.atlassian.jira.issue.search.managers.SearchHandlerManager import com.atlassian.jira.issue.security.IssueSecuritySchemeManagerImpl import com.atlassian.jira.jql.ClauseHandler import com.atlassian.jira.notification.DefaultNotificationSchemeManager import com.atlassian.jira.notification.type.SingleUser import com.atlassian.jira.permission.DefaultPermissionSchemeManager import com.atlassian.jira.security.roles.CachingProjectRoleAndActorStore import com.atlassian.jira.security.roles.actor.UserRoleActorFactory import com.atlassian.jira.user.util.UserUtil import com.atlassian.jira.util.BuildUtils import com.atlassian.jira.util.Consumer import com.atlassian.jira.util.ImportUtils import com.atlassian.jira.util.collect.EnclosedIterable import com.atlassian.query.Query import com.atlassian.query.QueryImpl import com.atlassian.query.clause.* import com.atlassian.query.operand.* import com.onresolve.scriptrunner.canned.CannedScript import com.onresolve.scriptrunner.canned.util.BuiltinScriptErrors import com.onresolve.scriptrunner.canned.util.SimpleBuiltinScriptErrors import org.apache.log4j.Category import org.ofbiz.core.entity.* public class RenameUser implements CannedScript { Category log = Category.getInstance(RenameUser.class) ComponentManager componentManager = ComponentManager.getInstance() UserUtil userUtil = componentManager.getUserUtil() SearchRequestManager searchRequestManager = componentManager.getSearchRequestManager() def searchRequestService = componentManager.getSearchRequestService() public static final String FIELD_FROM_USER_ID = 'FIELD_FROM_USER_ID' public static final String FIELD_TO_USER_ID = 'FIELD_TO_USER_ID' public static final String FIELD_MERGE = 'FIELD_MERGE' List msgs = [] String msg String msg2 Set<Long> reindexIssueIds = new HashSet() boolean preview = false Long nSearchReqsUpdated = 0 boolean doMerge public String getName() { "Renames a user ID" } public String getDescription() { "Changes a user's ID without reindexing or restarting" } List getCategories() { ["ADMIN"] } public Boolean isFinalParamsPage(Map params) { true } public List getParameters(Map params) { [ [ Name : FIELD_FROM_USER_ID, Label : 'From user ID', Description: "Rename or merge from who?", ], [ Name : FIELD_TO_USER_ID, Label : 'To user ID', Description: "Rename or merge to who?", ], [ Name : FIELD_MERGE, Label : 'Merge', Type : 'radio', Description: "Merge the first user to the second user, rather than rename", Values : ["false": "Rename", "true": "Merge"], ], ] } public BuiltinScriptErrors doValidate(Map params, boolean forPreview) { def errorCollection = new SimpleBuiltinScriptErrors() String sourceUserId = params[FIELD_FROM_USER_ID] as String String targetUserId = params[FIELD_TO_USER_ID] as String if (!params[FIELD_MERGE]) { errorCollection.addError(FIELD_MERGE, "Please select an option") } doMerge = Boolean.parseBoolean(params[FIELD_MERGE] as String) [FIELD_FROM_USER_ID, FIELD_TO_USER_ID].each { String p -> if (!params[p]) { errorCollection.addError(p, "Please provide the user ID") } } if (errorCollection.hasAnyErrors()) { return errorCollection } if (!userUtil.getUser(sourceUserId as String)) { errorCollection.addError(FIELD_FROM_USER_ID, "Cannot find user ID") } if (sourceUserId == targetUserId) { errorCollection.addErrorMessage("From and to user can't be the same") } boolean targetExists = userUtil.getUser(targetUserId as String) as Boolean if (doMerge && !targetExists) { errorCollection.addError(FIELD_TO_USER_ID, "Target user ID must exist already for a merge") } if (!doMerge && targetExists) { errorCollection.addError(FIELD_TO_USER_ID, "Target user ID must not exist already") } if (sourceUserId == ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()?.name) { errorCollection.addError(FIELD_FROM_USER_ID, "You can't rename the logged in user") } errorCollection } Query transformSearchRequest(Query query, String sourceUser, String targetUser) { Clause whereClause = query.getWhereClause() Clause newWhereClause = whereClause.accept(new RenamingClauseVisitor(sourceUser, targetUser)) as Clause // log.debug("original whereClause: " + query.whereClause.toString()) // log.debug "new where clause: " + newWhereClause.toString() Query newQuery = new QueryImpl(newWhereClause) newQuery = new QueryImpl(newWhereClause, query.orderByClause, newQuery.queryString) newQuery } Map doScript(Map params) { String sourceUser = params[FIELD_FROM_USER_ID] as String String targetUser = params[FIELD_TO_USER_ID] as String doMerge = Boolean.parseBoolean(params[FIELD_MERGE] as String) // make sure they're not logged on?? if (!preview) { msgs << "<b>Completed</b><br>" } updateGenericValues(sourceUser, targetUser) updateAllSearchRequests(sourceUser, targetUser) if (!preview) { msg = "Completed ${doMerge ? "merge" : "rename"} of $sourceUser to $targetUser" log.warn(msg) msgs << msg } params["output"] = "<pre>" + msgs.join("\n") + "<pre>" params } private def reindexIssue(IssueManager issueManager, Long issueId, Category log, IssueIndexManager indexManager) { GenericValue issue = issueManager.getIssue(issueId) boolean wasIndexing = ImportUtils.isIndexIssues(); ImportUtils.setIndexIssues(true); if ((BuildUtils.getCurrentBuildNumber() as Long) < 614) { ManagerFactory.getCacheManager().flush(com.atlassian.jira.issue.cache.CacheManager.ISSUE_CACHE, issue) } log.debug("Reindex issue ${issue.key}") indexManager.reIndex(issue); ImportUtils.setIndexIssues(wasIndexing) } def updateGenericValues(String sourceUser, String targetUser) { boolean began = false DelegatorInterface gd = (DelegatorInterface) componentManager.getComponentInstanceOfType(DelegatorInterface.class) IssueIndexManager indexManager = ComponentManager.getInstance().getIndexManager() CachingProjectRoleAndActorStore cachingProjectRoleAndActorStore = ComponentManager.getComponentInstanceOfType(CachingProjectRoleAndActorStore.class) as CachingProjectRoleAndActorStore DefaultNotificationSchemeManager notificationSchemeManager = ComponentManager.getComponentInstanceOfType(DefaultNotificationSchemeManager.class) as DefaultNotificationSchemeManager DefaultPermissionSchemeManager defaultPermissionSchemeManager = ComponentManager.getComponentInstanceOfType(DefaultPermissionSchemeManager.class) as DefaultPermissionSchemeManager IssueSecuritySchemeManagerImpl securitySchemeManager = ComponentManager.getComponentInstanceOfType(IssueSecuritySchemeManagerImpl.class) as IssueSecuritySchemeManagerImpl CachingSearchRequestStore cachingSearchRequestStore = ComponentManager.getComponentInstanceOfType(CachingSearchRequestStore.class) as CachingSearchRequestStore IssueManager issueManager = componentManager.getIssueManager() if (!preview) { log.debug("Begin transaction") began = CoreTransactionUtil.begin(); } try { msg = "Beginning ${doMerge ? "merge" : "rename"} of $sourceUser to $targetUser" log.warn(msg) msgs << msg // Rename the user record itself if ((BuildUtils.getCurrentBuildNumber() as Long) < 614) { List gvs = gd.findByAnd("OSUser", [name: sourceUser]) log.debug(gvs) assert gvs.size() == 1 if (!preview) { GenericValue usergv = gvs.first() as GenericValue if (doMerge) { usergv.remove() } else { usergv.set("name", targetUser) usergv.store() } } // group memberships gvs = gd.findByAnd("OSMembership", [userName: sourceUser]) msg = "Update ${gvs.size()} OSMembership records" log.debug(gvs) log.debug(msg) msgs << msg if (!preview) { List<GenericValue> targetGvs = gd.findByAnd("OSMembership", [userName: targetUser]) List<Object> targetGroups = targetGvs*.get("groupName") gvs.each { GenericValue gv -> if (doMerge && targetGroups.contains(gv.get("groupName"))) { gv.remove() } else { gv.set("userName", targetUser) gv.store() } } } } else { List gvs = gd.findByAnd("User", [userName: sourceUser]) assert gvs.size() == 1 log.debug(gvs) if (!preview) { GenericValue usergv = gvs.first() as GenericValue if (doMerge) { usergv.remove() } else { usergv.set("userName", targetUser) usergv.set("lowerUserName", targetUser.toLowerCase()) usergv.store() } } // group memberships gvs = gd.findByAnd("Membership", [childName: sourceUser, membershipType: "GROUP_USER"]) msg = "Update ${gvs.size()} Membership records" log.debug(gvs) log.debug(msg) msgs << msg if (!preview) { List<GenericValue> targetGvs = gd.findByAnd("Membership", [childName: targetUser, membershipType: "GROUP_USER"]) List<Object> targetGroups = targetGvs*.get("parentName") gvs.each { GenericValue gv -> if (doMerge && targetGroups.contains(gv.get("parentName"))) { gv.remove() } else { gv.set("childName", targetUser) gv.set("lowerChildName", targetUser) gv.store() } } } } if ((BuildUtils.getCurrentBuildNumber() as Long) > 6093) { List gvs = gd.findByAnd("ApplicationUser", [userKey: sourceUser, lowerUserName: sourceUser]) List gvstoremove = gd.findByAnd("ApplicationUser", [userKey: targetUser]) log.debug(gvs) log.debug(gvstoremove) msg = "Update ${gvs.size()} ApplicationUser records" GenericValue gvremove if (gvstoremove) { msg2 = "Remove ${gvstoremove.size()} outdated ApplicationUser records" msgs << msg2 gvremove = gvstoremove.first() as GenericValue } log.debug(msg) msgs << msg assert gvs.size() == 1 if (!preview) { GenericValue usergv = gvs.first() as GenericValue if (doMerge) { // todo: delete their associated property records usergv.remove() } else { if (gvremove) { gvremove.remove() } usergv.set("userKey", targetUser) usergv.set("lowerUserName", targetUser) usergv.store() } } } // trustedapp List gvs = gd.findByAnd("TrustedApplication", [createdBy: sourceUser]) msg = "Update ${gvs.size()} TrustedApplication records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> gv.set("createdBy", targetUser) gv.store() } } // SearchRequest ownership // if this is a merge, we may get dupe filter names. Not ideal but handled OK by the UI gvs = gd.findByOr("SearchRequest", [user: sourceUser, author: sourceUser]) msg = "Update ${gvs.size()} SearchRequest records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> if (gv.get("user")) { gv.set("user", targetUser) } if (gv.get("author")) { gv.set("author", targetUser) } removeFromCache(cachingSearchRequestStore) gv.store() } } // ColumnLayout - choose one or tother. gvs = gd.findByAnd("ColumnLayout", [username: sourceUser]) msg = "Update ${gvs.size()} ColumnLayout records" List<GenericValue> targetGvs = gd.findByAnd("ColumnLayout", [username: targetUser]) log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> if (doMerge && targetGvs.find { it.getLong("searchrequest") == gv.getLong("searchrequest") }) { gd.removeRelated("ChildColumnLayoutItem", gv) gv.remove() } else { gv.set("username", targetUser) gv.store() } } } if (!doMerge) { gvs = gd.findByAnd("UserHistoryItem", [username: sourceUser]) msg = "Update ${gvs.size()} UserHistoryItem records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> gv.set("username", targetUser) gv.store() } } } gvs = gd.findByAnd("FavouriteAssociations", [username: sourceUser]) targetGvs = gd.findByAnd("FavouriteAssociations", [username: targetUser]) msg = "Update ${gvs.size()} FavouriteAssociations records" log.debug(msg) msgs << msg if (!preview) { List matchFields = ["entityType", "entityId"] gvs.each { GenericValue gv -> // favourite assocs, only fave if not already faved if (doMerge && targetGvs.find { it.getFields(matchFields) == gv.getFields(matchFields) }) { // favcount must be adjusted downwards when both are favouriting log.debug("Remove fav association record and decrement favourite count") GenericValue sharedEntityGv = gd.findByPrimaryKey(gv.get("entityType") as String, [id: gv.get("entityId")]) sharedEntityGv.set("favCount", (sharedEntityGv.get("favCount") as Long) - 1) sharedEntityGv.store() gv.remove() } else { gv.set("username", targetUser) gv.store() } } } ["PortalPage", "FilterSubscription"].each { String entityName -> gvs = gd.findByAnd(entityName, [username: sourceUser]) msg = "Update ${gvs.size()} $entityName records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> gv.set("username", targetUser) gv.store() } } } // note: for versions prior to 4.1 the favouritesStoreCache needs to be flushed // Core issue fields gvs = gd.findByOr("Issue", [reporter: sourceUser, assignee: sourceUser]) msg = "Update ${gvs.size()} Issue records" log.debug(msg) msgs << msg gvs.each { GenericValue gv -> if (!preview) { if (gv.get("reporter") == sourceUser) { gv.set("reporter", targetUser) } if (gv.get("assignee") == sourceUser) { gv.set("assignee", targetUser) } gv.store() } reindexIssueIds.add(gv.getLong("id")) } // issue transition history gvs = gd.findByOr("Action", [author: sourceUser, updateauthor: sourceUser]) msg = "Update ${gvs.size()} Action records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> if (gv.get("author") == sourceUser) { gv.set("author", targetUser) } if (gv.get("updateauthor") == sourceUser) { gv.set("updateauthor", targetUser) } gv.store() } } // change groups gvs = gd.findByAnd("ChangeGroup", [author: sourceUser]) msg = "Update ${gvs.size()} ChangeGroup records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> gv.set("author", targetUser) gv.store() } } // Change items // todo: custom field history items - problem is they are referenced by name which is not unique EntityConditionList conditionList = new EntityConditionList( [new EntityConditionList( [ // Stoopid oracle can't do equals on a clob, LIKE shd be OK // alternative is to do the filtering in the code new EntityExpr("oldvalue", EntityOperator.LIKE, sourceUser), new EntityExpr("newvalue", EntityOperator.LIKE, sourceUser), ], EntityOperator.OR), new EntityConditionList([new EntityExpr("field", EntityOperator.IN, ["reporter", "assignee"])], EntityOperator.AND) ], EntityOperator.AND ) gvs = gd.findByCondition("ChangeItem", conditionList, ["id", "field", "oldvalue", "newvalue"], Collections.<String> emptyList()) msg = "Update ${gvs.size()} ChangeItem records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> if (gv.get("oldvalue") == sourceUser) { gv.set("oldvalue", targetUser) } if (gv.get("newvalue") == sourceUser) { gv.set("newvalue", targetUser) } gv.store() } } // Watchers and voters gvs = gd.findByAnd("UserAssociation", getUserAssociationConditionList(sourceUser)) targetGvs = gd.findByAnd("UserAssociation", getUserAssociationConditionList(targetUser)) msg = "Update ${gvs.size()} UserAssociation records" log.debug(msg) msgs << msg gvs.each { GenericValue gv -> if (gv.get("sinkNodeEntity") == "Issue") { reindexIssueIds.add(gv.getLong("sinkNodeId")) } if (!preview) { gd.removeValue(gv) if (doMerge && targetGvs.find { it.get("sinkNodeId") == gv.get("sinkNodeId") && it.get("sinkNodeEntity") == gv.get("sinkNodeEntity") && it.get("associationType") == gv.get("associationType") }) { // do nothing as always remove log.debug("Removed userassoc record") } else { // gv.store() doesn't work because we're changing the primary key gv.set("sourceName", targetUser) gd.create(gv) } } } // roles gvs = gd.findByAnd("ProjectRoleActor", [roletype: UserRoleActorFactory.TYPE, roletypeparameter: sourceUser]) targetGvs = gd.findByAnd("ProjectRoleActor", [roletype: UserRoleActorFactory.TYPE, roletypeparameter: targetUser]) msg = "Update ${gvs.size()} ProjectRoleActor records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> List matchFields = ["pid", "projectroleid"] if (doMerge && targetGvs.find { gv.getFields(matchFields) == it.getFields(matchFields) }) { log.debug("Remove role record") gv.remove() } else { gv.set("roletypeparameter", targetUser) gv.store() } } cachingProjectRoleAndActorStore.clearCaches() } // SharePermissions not required as cannot share with a user // worklogs gvs = gd.findByOr("Worklog", [author: sourceUser, updateauthor: sourceUser]) msg = "Update ${gvs.size()} Worklog records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> if (gv.get("author") == sourceUser) { gv.set("author", targetUser) } if (gv.get("updateauthor") == sourceUser) { gv.set("updateauthor", targetUser) } gv.store() } } // project and comp leads gvs = gd.findByAnd("Project", [lead: sourceUser]) msg = "Update ${gvs.size()} Project records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> gv.set("lead", targetUser) gv.store() } } gvs = gd.findByAnd("Component", [lead: sourceUser]) msg = "Update ${gvs.size()} Component records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> gv.set("lead", targetUser) gv.store() } } // notification schemes gvs = gd.findByAnd("Notification", [type: SingleUser.DESC, parameter: sourceUser]) targetGvs = gd.findByAnd("Notification", [type: SingleUser.DESC, parameter: targetUser]) msg = "Update ${gvs.size()} Notification records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> if (doMerge && targetGvs.find { gv.getFields(["scheme", "eventTypeId", "type"]) == it.getFields(["scheme", "eventTypeId", "type"]) }) { log.debug("Remove Notification record") gv.remove() } else { gv.set("parameter", targetUser) gv.store() } } notificationSchemeManager.flushProjectSchemes() } // permission schemes gvs = gd.findByAnd("SchemePermissions", [type: "user", parameter: sourceUser]) targetGvs = gd.findByAnd("SchemePermissions", [type: "user", parameter: targetUser]) msg = "Update ${gvs.size()} SchemePermissions records" log.debug(msg) msgs << msg if (!preview) { List matchFields = ['scheme', 'permission', 'type', 'parameter'] gvs.each { GenericValue gv -> if (doMerge && targetGvs.find { gv.getFields(matchFields) == it.getFields(matchFields) }) { log.debug("Remove Notification record") gv.remove() } else { gv.set("parameter", targetUser) gv.store() } } defaultPermissionSchemeManager.flushProjectSchemes() defaultPermissionSchemeManager.flushSchemeEntities() } // issue security schemes gvs = gd.findByAnd("SchemeIssueSecurities", [type: "user", parameter: sourceUser]) targetGvs = gd.findByAnd("SchemePermissions", [type: "user", parameter: targetUser]) msg = "Update ${gvs.size()} SchemeIssueSecurities records" log.debug(msg) msgs << msg if (!preview) { List matchFields = ['scheme', 'security', 'type', 'parameter'] gvs.each { GenericValue gv -> if (doMerge && targetGvs.find { gv.getFields(matchFields) == it.getFields(matchFields) }) { log.debug("Remove Notification record") gv.remove() } else { gv.set("parameter", targetUser) gv.store() } } securitySchemeManager.flushProjectSchemes() } // user and multi-user custom fields gvs = gd.findByCondition("CustomField", new EntityExpr("customfieldtypekey", EntityOperator.IN, [ "com.atlassian.jira.plugin.system.customfieldtypes:userpicker", "com.atlassian.jira.plugin.system.customfieldtypes:multiuserpicker" ]) , ["id"], Collections.<String> emptyList()) Long cfValueKount = 0 gvs.each { GenericValue gv -> gv.getRelatedByAnd("ChildCustomFieldValue", [stringvalue: sourceUser]).each { GenericValue cfGv -> cfValueKount++ if (!preview) { cfGv.set("stringvalue", targetUser) cfGv.store() } reindexIssueIds.add(cfGv.getLong("issue")) } } msg = "Update $cfValueKount CustomFieldValue records" log.debug(msg) msgs << msg gvs = gd.findByAnd("OSHistoryStep", [caller: sourceUser]) msg = "Update ${gvs.size()} OSHistoryStep records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> gv.set("caller", targetUser) gv.store() } } // fileattachments gvs = gd.findByAnd("FileAttachment", [author: sourceUser]) msg = "Update ${gvs.size()} FileAttachment records" log.debug(msg) msgs << msg if (!preview) { gvs.each { GenericValue gv -> gv.set("author", targetUser) gv.store() } } // -------------------------------------- Finalise -------------------------------------- if (!preview) { log.debug("Commit transaction") CoreTransactionUtil.commit(began); // Only for 4.1 and above - clearing the other caches are not necessary above but kept there if // I decide to backport this log.debug("Publish clear cache event") ComponentManager.getComponentInstanceOfType(EventPublisher.class).publish(ClearCacheEvent.INSTANCE); // reindex issues reindexIssueIds.each { Long issueId -> reindexIssue(issueManager, issueId, log, indexManager); } } msg = "Reindex ${reindexIssueIds.size()} issues" log.debug(msg) msgs << msg } catch (Exception e) { log.error("Problem renaming user, rolling back") CoreTransactionUtil.rollback(began) throw e } finally { // not sure if we need anything here } } private void removeFromCache(CachingSearchRequestStore cachingSearchRequestStore) { def cachemap = cachingSearchRequestStore.cacheByUser cachemap.removeAll() } private List<EntityExpr> getUserAssociationConditionList(String userId) { List userAssocCList = [ new EntityExpr("sourceName", EntityOperator.EQUALS, userId), new EntityExpr("associationType", EntityOperator.IN, [UserAssociationParser.ASSOCIATION_TYPE_WATCH_ISSUE, UserAssociationParser.ASSOCIATION_TYPE_VOTE_ISSUE]), ] return userAssocCList } def updateSingleRequest(SearchRequest sr, String sourceUser, String targetUser) { Query query = sr.getQuery() Query newQuery // A query may have no where clause and select everything... if so .accept dies if (query.getWhereClause()) { newQuery = transformSearchRequest(query, sourceUser, targetUser) } else { newQuery = query } if (query.whereClause.toString() != newQuery.whereClause.toString()) { // this is stupid but setting a queryString seems to blow away the whereClause, so we do this twice nSearchReqsUpdated++ log.debug("Update sr name: " + sr.name) // log.debug("queryString: " + newQuery.queryString) // log.debug("newQuery: $newQuery") sr.setQuery(newQuery) JiraServiceContextImpl ctx = new JiraServiceContextImpl(userUtil.getUser(sr.ownerUserName)) searchRequestService.validateFilterForUpdate(ctx, sr) if (ctx.errorCollection.hasAnyErrors()) { log.error("errors: " + ctx.getErrorCollection()) } else { if (!preview) { searchRequestService.updateFilter(ctx, sr) } } } else { log.debug("No need to update name: " + sr.name) } } private def updateAllSearchRequests(String sourceUser, String targetUser) { EnclosedIterable<SearchRequest> srs = searchRequestManager.getAll() srs.foreach(new Consumer<SearchRequest>() { void consume(SearchRequest sr) { try { updateSingleRequest(sr, sourceUser, targetUser) } catch (Exception e) { log.error("Error checking or updating search request: $sr - this is not fatal but you may wish to check this manually", e) } } }) msg = "Update parameters for $nSearchReqsUpdated search requests" log.debug(msg) msgs << msg } String getDescription(Map params, boolean forPreview) { preview = true Map out = doScript(params) out["output"] } public String getHelpUrl() { "https://jamieechlin.atlassian.net/wiki/display/GRV/Built-In+Scripts#Built-InScripts-RenameaUserID" } } class RenamingClauseVisitor implements ClauseVisitor { Category log = Category.getInstance(RenamingClauseVisitor.class) SearchHandlerManager searchHandlerManager = ComponentManager.getComponentInstanceOfType(SearchHandlerManager.class) as SearchHandlerManager ComponentManager componentManager = ComponentManager.getInstance() CustomFieldManager customFieldManager = componentManager.getCustomFieldManager() String sourceUser String targetUser RenamingClauseVisitor(String sourceUser, String targetUser) { this.sourceUser = sourceUser this.targetUser = targetUser } Object visit(AndClause andClause) { Collection<Clause> newClauses = transformSubClauses(andClause) new AndClause(newClauses) } Object visit(NotClause notClause) { new NotClause(notClause.getSubClause().accept(new RenamingClauseVisitor(sourceUser, targetUser)) as Clause) } Object visit(OrClause orClause) { Collection<Clause> newClauses = transformSubClauses(orClause) new OrClause(newClauses) } private Collection<Clause> transformSubClauses(Clause mainClause) { Collection<Clause> newClauses = [] mainClause.getClauses().each { Clause clause -> newClauses.add(clause.accept(new RenamingClauseVisitor(sourceUser, targetUser)) as Clause) } return newClauses } Object visit(TerminalClause clause) { final Collection<ClauseHandler> handlers = searchHandlerManager.getClauseHandler(clause.getName()); Collection<String> fieldIds = searchHandlerManager.getFieldIds(clause.getName()) // If there are multiple handers, eg field has multiple duplicate names, don't deal with it // ie multiple fields with the same name visible to this user if (fieldIds?.size() != 1) { return clause } String fieldId = fieldIds.toList().first() boolean isUserField = false if (fieldId.startsWith("customfield_")) { CustomFieldType cfType = customFieldManager.getCustomFieldObject(fieldId).getCustomFieldType() if (cfType instanceof UserField) { isUserField = true } } if (["reporter", "assignee"].contains(fieldId) || isUserField) { Operand operand = clause.getOperand() Operand newOp = operand.accept(new RenamingOperandVisitor(sourceUser, targetUser)) as Operand TerminalClauseImpl newTermClause = new TerminalClauseImpl(clause.name, clause.getOperator(), newOp) return newTermClause } clause } Object visit(WasClause wasClause) { // wasClause in 4.3 not handled at the moment wasClause } Object visit(ChangedClause changedClause) { // changed not handled return null } } class RenamingOperandVisitor implements OperandVisitor { Category log = Category.getInstance(RenamingOperandVisitor.class) String sourceUser String targetUser RenamingOperandVisitor(String sourceUser, String targetUser) { this.sourceUser = sourceUser this.targetUser = targetUser } Object visit(EmptyOperand empty) { empty } Object visit(FunctionOperand function) { function } Object visit(MultiValueOperand multiValue) { List<Operand> newOps = multiValue.getValues().collectAll { Operand operand -> if (operand instanceof SingleValueOperand && operand.getStringValue() == sourceUser) { return new SingleValueOperand(targetUser) } operand } new MultiValueOperand(newOps) } Object visit(SingleValueOperand singleValueOperand) { if (singleValueOperand.getStringValue() == sourceUser) { return new SingleValueOperand(targetUser) } singleValueOperand } }
Cheers,
Eric
How do you use this thing? Is it a groovy script or java? How do you do "Just place the file in your script home folder and it should load in the admin panel." Where is my script home folder? /var/atlassian/application-data/jira/scripts? I tried putting this there but I didn't see it in my admin panel.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Same problem here, we may reuse certain user names for new employees after people leaving the company. therefore we used merge user for moving all "old stuff" to one "left employee" account.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I would ask your ad admin to leave the user but deactivate them. Then the user will be deactivated in JIRA and not count to your license. Otherwise you are losing history in JIRA for no gain
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
In JIRA 6 you should be able to rename a user ID directly from the User Admin Panel
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I think that's why Script Runner no longer has "rename user" - you can do it natively now.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Thanks for the reply. I'm not sure I see where we can accomplish our use case. In order to preserve the name associated with objects in Jira (issues, filters, dashboards, etc.) we have created an internal user and used the built-in command to "move" everything to the new user using the merge option. If this is a capability through the user management panel, I fail to see it. Can you help? Thanks!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Correct. I found where users in the internal directory can be edited, but we use the AD sync to define and authenticate our users, and their entries in Jira are read-only. When a user leaves the company, we are given a 30 day window to define an internal user, and "move" all the AD user's objects to the internal user. Thanks!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
you could try to put back the edit issue script into the right path in Jira and test it in your jira 6.3. I am not sure this script will work as it is. (I doubt it) If it's not, maybe debugging this script to make it compatible to jira 6.3 would be the way to go for you. you can find the file in the .jar of script runner plugin (I suggest downloading the last version that still has this builtin) After extracting the plugin files, you will find your desired script here : com\onresolve\jira\groovy\canned\admin\RenameUser.groovy you then have to place this file in : jiraHome/WEB-INF/classes/com/onresolve/jira/groovy/canned/admin You should not need to reboot to get the script to load back into your panel. Give it a try, and see if it works. Michel
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I am now in a similar position. The ability to rename a user natively is great. but really what I want to do is remove the incorrectly defined local user by merging all their activity with the properly defined user from AD. Did the script get tried out on 6.3?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Michel we have latest JIRA 6.3.12, i dont see that path(jiraHome/WEB-INF/classes/com/onresolve/jira/groovy/canned/admin) in our JIRA!, i think they moved all plugins to some other folder structure. Do you know where to keep RenameUser.groovy in latest JIRA version, i found this groovy from old version of script runner. Let us know if you have any workaround for this, thanks alot. Raju
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Usually the folder WEB-INF/classes should be there but youwill have to create the com/onresolve/jira/groovy/canned/admin If you test it, only try it on a test instance first. As there might have some difference between 6.2 and 6.3 that will make this script break things.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Community moderators have prevented the ability to post new answers.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.