Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in
Deleted user
0 / 0 points
Next:
badges earned

Your Points Tracker
Challenges
Leaderboard
  • Global
  • Feed

Badge for your thoughts?

You're enrolled in our new beta rewards program. Join our group to get the inside scoop and share your feedback.

Join group
Recognition
Give the gift of kudos
You have 0 kudos available to give
Who do you want to recognize?
Why do you want to recognize them?
Kudos
Great job appreciating your peers!
Check back soon to give more kudos.

Past Kudos Given
No kudos given
You haven't given any kudos yet. Share the love above and you'll see it here.

It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

Find latest created issue in project created between 4AM-4PM UTC

Our instance uses a modified version of Adaptavist's Round Robin Script. In the code block that pulls the last issue with an assignee, I changed it to pull 'Defect' issues that have an assignee and also a value in a custom field called 'Original Assignee':

 def lastIssueIdWithAssignee = issueManager.getIssueIdsForProject(issue.projectObject.id)
.sort()
.reverse()
.find {
def foundIssue = issueManager.getIssueObject(it)
return foundIssue.assignee && foundIssue.issueType.name == "Defect" && foundIssue.getCustomFieldValue(originalAssigneeCustomField)
}

I want to add to the return statement and make it so that it pulls the last issue that fulfills the above criteria, but have it also look at the created date/time. In other words, I want it to pull the last created issue that:

  • is of 'Defect' Issue type
  • has an assignee
  • has a value in the 'Original Assignee' custom field

and in addition...

  • was created in between the times of 4:00AM - 4:00 PM UTC

 

Is this possible? If so, how would I translate it into code?

1 answer

1 accepted

1 vote
Answer accepted

I haven't used or tested the round robin script. But I have some concern about performance on projects with lots of issues. The more issue to have the more issues you have to sort and reverse. (Since this deals with just a list of long integer, it may not be a valid concern in most normal use case)

But given that concern along with your requirements, I was lead to think about leveraging JQL.  

Here is a console snippet that should return the last issue assigned according to your criteria:

import java.time.ZoneOffset
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor

def searchService = ComponentAccessor.getComponent(SearchService)
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def issue = ComponentAccessor.issueManager.getIssueObject('ASP-411') //CHANGE THIS TO A SAMPLE ISSUE TO TEST
def jql = /project = $issue.projectObject.id and "Original Assignee" is not empty and assignee is not empty and issueType=Defect and Created > -30d order by Created desc/


def parseResult = searchService.parseQuery(currentUser, jql)
def lastAssignee
if(parseResult.isValid()){
def results = searchService.search(currentUser, parseResult.query,PagerFilter.unlimitedFilter)
log.info results.total
lastAssignee = results.results.find{
def hour = it.created.toInstant().atZone(ZoneOffset.UTC).hour
hour >=4 && hour < 16
}?.assignee
}
return lastAssginee

Plug that into the rest of the script (adjusting for the fact that you will have a user object instead of issueId).

Hi @Peter-Dave Sheehan ,

Thanks for the reply! My apologies for the delay in response.

I've plugged in the snippet as follows:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.security.roles.ProjectRoleManager

import java.time.ZoneOffset

import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.bc.issue.search.SearchService

def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.issueManager

if (!!issue.assignee){
return
} else {
def utcHour = new Date().toInstant().atOffset( ZoneOffset.ofTotalSeconds(0)).hour
if (utcHour >= 4 && utcHour < 16) {
// The role you want assignees to set from
final roleName = 'Operations'
final originalAssigneeCustomFieldName = "customfield_20321"

// If it is true, the assigned issues will be reassigned
final reassignedIssues = true

def projectRoleManager = ComponentAccessor.getComponent(ProjectRoleManager)
def customFieldManager = ComponentAccessor.getCustomFieldManager()

def originalAssigneeCustomField = customFieldManager.getCustomFieldObject(originalAssigneeCustomFieldName)

// Get all of the users associated with the specified project role
def projectRole = projectRoleManager.getProjectRole(roleName)

// Sort the users of the project role using the user key
def users = projectRoleManager.getProjectRoleActors(projectRole, issue.projectObject)
.applicationUsers
.toSorted { it.key }

// There are no users in the specific project role
if (!users) {
log.info ("No users for project role $roleName")
return
}

if (!reassignedIssues && issue.assignee) {
log.info ('The issue is already assigned')
return
}

// Find the latest created issue id that has an assignee
def lastIssueIdWithAssignee = issueManager.getIssueIdsForProject(issue.projectObject.id)
.sort()
.reverse()
.find {
def foundIssue = issueManager.getIssueObject(it)
def jql = /project = $issue.projectObject.id and "Original Assignee" is not empty and assignee is not empty and issueType=Defect and Created > -30d order by Created desc/
def parseResult = searchService.parseQuery(foundIssue.assignee, jql)
def lastAssigned
if (parseResult.isValid()) {
def results = searchService.search(foundIssue.assignee, parseResult.query, PagerFilter.unlimitedFilter)
log.info results.total
lastAssigned = results.results.find {
def hour = it.created.toInstant().atZone(ZoneOffset.UTC).hour
hour >= 4 && hour < 16

}?.assignee
}
return lastAssigned //foundIssue.assignee && foundIssue.issueType.name == "Defect" && foundIssue.getCustomFieldValue(originalAssigneeCustomField)
}

// If no issue fulfilling above criteria is found, new cert issue is assigned to the first user in rr queue
if (!lastIssueIdWithAssignee) {
issue.setAssignee(users.first())
def changeHolder = new DefaultIssueChangeHolder()
originalAssigneeCustomField.updateValue(null, issue, new ModifiedValue(null, users.first()), changeHolder)
return
}

// If issue is found, then assignee is based on the Original Assignee and Assignee fields:
// * assuming Original Assignee field is not empty, user in said field is taken, and issue assigns to
//the next user in queue
def lastIssue = issueManager.getIssueObject(lastIssueIdWithAssignee)

def lastAssignee = lastIssue.getCustomFieldValue(originalAssigneeCustomField) ?: lastIssue.assignee
def lastAssigneeIndex = users.indexOf(lastAssignee)
def nextAssignee = users[(lastAssigneeIndex + 1) % users.size()]

issue.setAssignee(nextAssignee)
def changeHolder = new DefaultIssueChangeHolder()
originalAssigneeCustomField.updateValue(null, issue, new ModifiedValue(null, nextAssignee), changeHolder)

def prevIssueId = issueManager.getIssueIdsForProject(issue.projectObject.id)
.sort()
.reverse()
.find {
def foundIssue = issueManager.getIssueObject(it)
return foundIssue.assignee && foundIssue.issueType.name == "Defect"
}
def prevIssue = issueManager.getIssueObject(prevIssueId)

def prevAssignee = prevIssue.assignee
def prevAssigneeIndex = users.indexOf(prevAssignee)

if (prevAssignee == nextAssignee){
def nextAssigneeAfter = users[(lastAssigneeIndex + 2) % users.size()]

issue.setAssignee(nextAssigneeAfter)
originalAssigneeCustomField.updateValue(null, issue, new ModifiedValue(null, nextAssigneeAfter), changeHolder)
}
}
}

And I hear you on the concern about the issues to cycle and reverse through as more issues get created. I assume the JQL will help keep the runtime to a minimum if this just pulls the issues created in the last 30 days, so thank you for that!

I will be testing this tomorrow between the hours of 4AM-4PM UTC and come back with results!

Appreciate the help again as usual. 

Hi @Peter-Dave Sheehan 

After testing today around 4AM-4PM UTC timeframe, I don't think this worked unfortunately :(

So here's how I directly plugged your snippet into the lastIssueWithAssignee block:

// Find the latest created issue id that has an assignee
def lastIssueIdWithAssignee = issueManager.getIssueIdsForProject(issue.projectObject.id)
.sort()
.reverse()
.find {
def foundIssue = issueManager.getIssueObject(it)
def jql = /project = $issue.projectObject.id and "Original Assignee" is not empty and assignee is not empty and issueType=Defect and Created > -30d order by Created desc/
def parseResult = searchService.parseQuery(foundIssue.assignee, jql)
def lastAssigned
if (parseResult.isValid()) {
def results = searchService.search(foundIssue.assignee, parseResult.query, PagerFilter.unlimitedFilter)
log.info results.total
lastAssigned = results.results.find {
def hour = it.created.toInstant().atZone(ZoneOffset.UTC).hour
hour >= 4 && hour < 16
}?.assignee
}
return lastAssigned

This is the list of users in the 'Operations' role that are to be assigned in round robin between 4AM-4PM UTC (in order):

1) Ary D

2) Chen S

3) David Y

4) Dominik M

5) Fernando B

6) Gamal D

7) Rasanga S

 

The last issue that was assigned to someone in that timeframe from the previous day was 'David Y'.

Now today, entering the 4AM-4PM UTC timeframe again, I created another issue and expected that it would auto-assign to 'Dominik M', picking right back up where it left off from the other day. It instead defaulted to the first user in the list of 'Operations' users ('Ary D').

-----

Also I tried running the snippet through console, but doesn't seem to work, stating that lastAssignee has to be declared:

Screen Shot 2021-06-16 at 11.01.52 AM.png

Looks like there was a typo in my snippet.

The last line reads lastAssiginee  and should be lastAssignee (it should match what's on line 13 and 17)

Try to run that in the console again and see if you get the correct user.

Then in your main code, I'm not sure it's wise to have my suggested snipped into the block that will contains all issues (getIssueIdsForProject() will include every issues in your project, the whole point of using JQL is to avoid this.

So instead of 

// Find the latest created issue id that has an assignee
def lastIssueIdWithAssignee = issueManager.getIssueIdsForProject(issue.projectObject.id)
.sort()
.reverse()
.find {
def foundIssue = issueManager.getIssueObject(it)
def jql = /project = $issue.projectObject.id and "Original Assignee" is not empty and assignee is not empty and issueType=Defect and Created > -30d order by Created desc/
def parseResult = searchService.parseQuery(foundIssue.assignee, jql)
def lastAssigned
if (parseResult.isValid()) {
def results = searchService.search(foundIssue.assignee, parseResult.query, PagerFilter.unlimitedFilter)
log.info results.total
lastAssigned = results.results.find {
def hour = it.created.toInstant().atZone(ZoneOffset.UTC).hour
hour >= 4 && hour < 16
}?.assignee
}
return lastAssigned //foundIssue.assignee && foundIssue.issueType.name == "Defect" && foundIssue.getCustomFieldValue(originalAssigneeCustomField)
}

Here is how I would incorporate that JQL snippet into your whole script

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.security.roles.ProjectRoleManager

import java.time.ZoneOffset

import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.bc.issue.search.SearchService

def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.issueManager
def projectRoleManager = ComponentAccessor.getComponent(ProjectRoleManager)
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser


// The role you want assignees to set from
final roleName = 'Operations'

final originalAssigneeCustomFieldName = "customfield_20321"
def originalAssigneeCustomField = customFieldManager.getCustomFieldObject(originalAssigneeCustomFieldName)

// If it is true, the assigned issues will be reassigned
final reassignIssues = false

if (issue.assignee && !reassignIssues ){
//issue is already assigned, and we don't want to re-assign
return
}

def createdUtcHour = issue.created.toInstant().atOffset( ZoneOffset.ofTotalSeconds(0)).hour
if (createdUtcHour <= 4 || createdUtcHour >= 16) {
//issue was not created in the in target time range, nothing to do
return
}

// Get all of the users associated with the specified project role
def projectRole = projectRoleManager.getProjectRole(roleName)

// Sort the users of the project role using the user key
def users = projectRoleManager.getProjectRoleActors(projectRole, issue.projectObject)
.applicationUsers
.toSorted { it.key }

// There are no users in the specific project role
if (!users) {
log.info ("No users for project role $roleName")
return
}

//get recent issues in reverse order of creation
def jql = /project = $issue.projectObject.id and "Original Assignee" is not empty and assignee is not empty and issueType=Defect and Created > -30d order by Created desc/

def parseResult = searchService.parseQuery(currentUser, jql)
def lastAssignee

assert parseResult.isValid() : "The following JQL was not valid: $jql"

def results = searchService.search(currentUser, parseResult.query,PagerFilter.unlimitedFilter)
log.info "Found $results.total assigned defects in the last 30 days for project $issue.projectObject.key"

//find the first isstance of an issue created between 4-16 UTC (this will be the last one created since were order by Created desc)
def lastIssueInTimeRange = results.results.find{
def hour = it.created.toInstant().atZone(ZoneOffset.UTC).hour
hour >=4 && hour < 16
}


def nextAssignee

if(!lastIssueInTimeRange){
log.info "No isssue was found created between 4-16UTC in the last 30 days. Using the first role member"
nextAssignee = users.first()
} else {
//examine the issues's Original Assignee and assignee
lastAssignee = lastIssueInTimeRange.getCustomFieldValue(originalAssigneeCustomField) ?: lastIssueInTimeRange.assignee
//get the next user in line
def lastAssigneeIndex = users.indexOf(lastAssignee)
nextAssignee = users[(lastAssigneeIndex + 1) % users.size()]
}


issue.setCustomFieldValue(originalAssigneeCustomField, nextAssignee)
issue.setAssignee(nextAssignee)


 

This will only run in a postfunction.

 

If you want to run the same thing in the console (for testing).

Add after your issue manager:

issueManager.getIssueObject('somekey')

And at the end:

def issueService =ComponentAccessor.issueService
def input = issueService.newIssueInputParameters()
input.setAssigneeId(nextAssignee.key)
input.addCustomFieldValue(originalAssigneeCustomField.id as Long, nextAssignee.key)
def validationResult = issueService.validateUpdate(currentUser,issue.id, input)
assert validationResult.isValid()
issueService.update(currentUser, validationResult)
Like Ian Balas likes this

@Peter-Dave Sheehan 

I can confirm that your jql snippet solution works perfectly now. Thank you so much!

Also, I've come to realize you're very knowledgeable in coming up with custom-coded solutions like this. To say I'm impressed is an understatement. So I just wanted to inquire if you had references to any learning resources that involve the use of Groovy, whether interpolated with Scriptrunner/any other JIRA plugins, or just the basics. It'd be really helpful as a learning experience!

Again, thanks for all your help on this!

 

-Ian

Thanks!

Sorry... I don't have any good references other than the obvious like:

I have a curious mind and I like exploring and experimenting. Everything I know I learned through examining the Adaptivist documentation and Script Library, examining other snippets posted here in the community, and general google search. 

Suggest an answer

Log in or Sign up to answer
TAGS

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you