Background
I want to auto-transition an Issue once the work ration goes above a certain level. Purpose is to move it to a special state that only project manager can move in to and from.
The trigger level is stored in a special issue type called 'Project', only one of these issues exists per project.
In my listener I trigger on any work logged.
Current Code
if(eventTypeId.equals(EventType.ISSUE_WORKLOGGED_ID) || eventTypeId.equals(EventType.ISSUE_WORKLOG_UPDATED_ID)){ log.info("Event caught by DSASystemsListener (Auto hold on overbooked) for issue "+issue.getKey()); //Get issue type String type = issue.getIssueTypeObject().getName(); if(type.equals("Project")){ return; } //Get issue status String status = issue.getStatusObject().getName(); if(status.matches("Closed|Baselined|Cancelled|Proposed|Open")){ return; } Issue projectIssue = getProjectIssue(issue); if(projectIssue == null){ return; } ApplicationUser pm = getProjectManager(projectIssue); if(pm == null){ return; } CustomField autoHoldTriggerField = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Auto Hold Trigger"); Double autoHoldTrigger = (Double)projectIssue.getCustomFieldValue(autoHoldTriggerField); log.debug("Auto hold trigger: "+autoHoldTrigger); //Determine if issue is overbooked Long origEst = issue.getOriginalEstimate(); Long timeSpent = issue.getTimeSpent(); log.debug("Orig: "+origEst+", Time: "+timeSpent); if(origEst != null && timeSpent != null){ float wr = ((float)timeSpent/(float)origEst)*100; log.debug("WR: "+wr); if(wr > autoHoldTrigger.floatValue()){ log.info("Issue has work ratio greater than "+autoHoldTrigger+", will hold issue"); holdIssue(issue, "Issue has been held as work ratio is "+wr, ApplicationUsers.toDirectoryUser(pm)); }else{ log.debug("Auto hold trigger not exceeded, return"); return; } }else if((origEst == null || origEst == 0) && timeSpent != null && timeSpent > 0){ log.info("Issue has 0 original estimate and a time spent greater than 0, will hold issue"); holdIssue(issue, "Issue has been held as time has been booked when Original Estimate is 0", ApplicationUsers.toDirectoryUser(pm)); }else{ log.debug("Unknown state, return"); return; } } } private void holdIssue(Issue issue, String comment, User projectManager) { log.debug("Will hold issue"); IssueService issueService = ComponentAccessor.getIssueService(); log.debug("Will validate test if possible transition"); CommentManager commentManager = ComponentAccessor.getCommentManager(); commentManager.create(issue, ApplicationUsers.from(projectManager),"AUTO-COMMENT: "+comment,false); IssueService.TransitionValidationResult transitionValidationResult = issueService.validateTransition(projectManager, issue.getId(), 191, issueService.newIssueInputParameters()); log.debug("Will validate transition result"); if(transitionValidationResult.isValid()){ log.debug("Will perform transition"); IssueService.IssueResult issueResult = issueService.transition(projectManager, transitionValidationResult); if(issueResult.isValid()){ log.debug("Successfully transition issue"); }else{ log.error("Failed to transition issue"); } }else{ List<String> errors = (List<String>)transitionValidationResult.getErrorCollection().getErrorMessages(); log.debug("Transition not valid"); for(String error : errors){ log.debug(error); } } } private Issue getProjectIssue(Issue issue){ Project project = issue.getProjectObject(); List<Long> issueIds = null; try { issueIds = (List<Long>)ComponentAccessor.getIssueManager().getIssueIdsForProject(project.getId()); } catch (GenericEntityException e) { e.printStackTrace(); return null; } List<Issue> issues = ComponentAccessor.getIssueManager().getIssueObjects(issueIds); for(Issue tempIssue : issues){ if(tempIssue.getIssueTypeObject().getName().equals("Project")){ return tempIssue; } } return null; } private ApplicationUser getProjectManager(Issue issue){ ProjectRoleManager projectRoleManager = ComponentAccessor.getComponent(ProjectRoleManager.class); ProjectRole projManagerRole = projectRoleManager.getProjectRole("Project Manager"); ProjectRoleActors projectRoleActors = projectRoleManager.getProjectRoleActors(projManagerRole,issue.getProjectObject()); Set<ApplicationUser> users = projectRoleActors.getApplicationUsers(); for(ApplicationUser user : users){ if(user != null){ log.debug("Found "+user.getDisplayName()+" to be Project Manager"); return user; } } return null; }
Sorry for the large amount of code, the private method holdIssue (line 57) is where it all happens.
Problem
I have had a number of weird effects;
What I know so far...
In a section of the listener that runs before this code but uses the same objects I am making some changes to the issue, I am updating using the IssueManager (old code) and it was causing problem 3 until I changed the code to include a reindex of the issue.
((MutableIssue)issue).setCustomFieldValue(tpsCustomField, tps); im.updateIssue(null, (MutableIssue)issue, com.atlassian.jira.event.type.EventDispatchOption.DO_NOT_DISPATCH, false); try { boolean imp = ImportUtils.isIndexIssues(); ImportUtils.setIndexIssues(true); ComponentAccessor.getIssueIndexManager().reIndex(issue); ImportUtils.setIndexIssues(imp); } catch (IndexException e) { e.printStackTrace(); }
This fixed problem 3 for a short while; now it is back under the conditions described above.
I am using the IssueService for the transition as I understand that it doesn't require indexing to be cared for.
I feel like this issue is around the fact that I am using a different user to perform the transition rather than the current user. In fact one of my tests I set the logged in user to the project manager via the JiraAuthenticationContext, this seemed to improve things but problem 3 still remained.
This transition is a global transition for the workflow and can be launched from any state. It has a precondition that only a member of the Project Role 'Project Manager' can perform the transition and a validation that a comment must be made. The comment part seems ok.
Questions
What am I doing wrong?!
Am I causing problems modifying the issue in multiple places in the listener using different methods?
Am I handling the users correctly?
NOTE: I tagged scriptrunner because I have used the fast-track post function in the past with great effect and this is more-or-less what I am trying to do here.
** UPDATE **
I replaced the issue manager update code to eliminate that possibility with this...
IssueService issueService = ComponentAccessor.getIssueService(); IssueInputParameters issueInputParameters = issueService.newIssueInputParameters(); issueInputParameters.addCustomFieldValue(tpsCustomField.getIdAsLong(), tps); IssueService.UpdateValidationResult issueUpdateValidationResult = issueService.validateUpdate(issue.getAssignee(), issue.getId(), issueInputParameters); if(issueUpdateValidationResult.isValid()){ issueService.update(issue.getAssignee(), issueUpdateValidationResult); log.debug("TPS Source field set to "+tps); }else{ List<String> errors = (List<String>)issueUpdateValidationResult.getErrorCollection().getErrorMessages(); log.error("Update of TPS not valid"); for(String error : errors){ log.debug(error); } }
The problem still persists...
Thanks in advance!
Fixed this with two changes, as per @Jamies comment about the re-index in a new thread.
I created a little member for this
private void reindex(){ Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); ComponentAccessor.getIssueIndexManager().reIndex(issue); } catch (IndexException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); }
...then called that after the transition was done successfully...
log.debug("Will perform transition"); IssueService.IssueResult issueResult = issueService.transition(projectManager, transitionValidationResult); if(issueResult.isValid()){ log.debug("Successfully transitioned issue"); reindex(); }else{ log.error("Failed to transition issue"); }
This fixed issue 3 in my question.
Then to fix issue 2 I had to make the Project Manager the logged in user
ComponentAccessor.getJiraAuthenticationContext().setLoggedInUser(ApplicationUsers.from(projectManager));
Then everything worked great!
Sorry @Jamie, couldn't figure out how to make your comment the answer, but thanks for the input nonetheless
Im going to implement the thread reindex trick, if the IssueService call to reindex fails ... ^^
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Mike Wells
I tried your solution but its not working for me. Can you please reply if there is any other work around.
thanks
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Why don't you just check the action? If it's an action that is invoked by the listener itself, then return. (You may be doing this already, have not read all your code).
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
There's something glitchy about reindexing in a listener, but I thought it was fixed in jira 6. I can't find the particular bug right now.
To rule out this problem you could try indexing in a new thread, after a short delay. Basically what might be happening is that you reindex, but when your code is done the original copy of the issue gets indexed, hence the wrong index values.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Possible issue that could lead to inconsistant state...
I had a close look at my logging;
Event caught by DSASystemsListener (Auto hold on overbooked) for issue J12345-61
Found Mike Wells to be Project Manager
Auto hold trigger: 170.0
Orig: 57600, Time: 378000
WR: 656.25
Issue has work ratio greater than 170.0, will hold issue
Will hold issue
Will validate test if possible transition
Field layout contains non-orderable field with id 'customfield_10201'.
Will validate transition result
Will perform transition
DSASystemsListener Caught Event: 13
Event caught by DSASystemsListener (Calculate TPS) for issue J12345-61
Time Spent: 378000, Orig: 57600, Work Ratio (long): 656, Work Ration (double): 656.25, Ongoing: false, Perc Comp: 0%
Starting GroovyScript TPSScriptedField
TPS Source = nok.png
TPS Source field set to nok.png
Event caught by DSASystemsListener (Calculate Daily Work Load) for issue J12345-61
Today: 2013-01-09 00:46:00.0
Planned Start: 2013-07-17 00:00:00.0
Planned End: 2013-07-24 00:00:00.0
Due Date: 2013-07-30 00:00:00.0
Successfully transition issue
I think the transition of the issue is causing the firing of an event which in turn is running my listener again. The italic logging is from listener call 1, then the bold logging shows the listener being called again. Finally the last line shows the finished transition. I expect this is not good!
I there a way of de-activating the listener while it is running or will I have to change how my listener is coded.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Jira is 6.0.1
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
@Alex - I think if you updated your code to use the IssueService class you should rid yourself of the problem. I believe IssueService was introduce in version 5.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Michael,
about the #3 weird-effect of your problem ... Im having a very similar issue: https://answers.atlassian.com/questions/186657/different-issue-status-shown-between-navigator-and-issue-view-again
(transition from a listener)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
What version of jira?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.