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;
- If I am defined as the Project Manager (Project Role) AND am the current user AND the issue assignee then this seems to work just fine
- If I make another person the Project Manager then it fails and gives an error message about the transition not being valid at this point in the workflow, therefore nothing happens.
- If I am current user and another person is the assignee and project manger then the transition happens but in the search view it still shows as 'In Progress' but in the navigator view it shows as 'On Hold'
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!