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

Custom listener removing from sprints

Maciej Olszewski May 10, 2018

Hello,

We are using our own issuetype "Sprint Goal" for scrum purpose, unfortunately there are some people which don;t understand that sprint goal cannot be in more then one sprint. I am trying to use custom listner to delete issue from sprint expect first one. This is test code to just look how it works, but it doesn't work and i don;t know why. Listener works on IssueUpdate event

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserManager
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.greenhopper.service.sprint.Sprint
import com.atlassian.greenhopper.service.sprint.SprintIssueService

SprintIssueService sprintIssueService

def issue = event.issue
def issueUpdate = (MutableIssue) issue
log.warn "Custom Listener Check! " + issue
log.warn "Custom Listener " + event.getChangeLog().getRelated("ChildChangeItem").find{it.field == "Sprint"}
def igor = ComponentAccessor.getComponent(UserManager).getUserByName("igot")
def cfManager = ComponentAccessor.getComponent(CustomFieldManager)
def sprint = cfManager.getCustomFieldObjectByName("Sprint")
def sprintValue = issue.getCustomFieldValue(sprint)
ArrayList<Sprint> sprints = (ArrayList<Sprint>)issue.getCustomFieldValue(sprint)
log.warn "Custom Listener "+sprints[0]
def primarySprint = sprints[0]
sprints.each(){
if(it != primarySprint){
sprintIssueService.removeIssuesFromSprint(igor, it, [issue] as Collection)
}
}

thanks in advance for help,

Maciej O. 

2 answers

1 accepted

0 votes
Answer accepted
Praveen May 10, 2018

Hi Maciej,

Just an observation from my side, the name of the user says "igot" in your code but the variable that you defined says "igor". Maybe you made a typo in the username. Please ignore if it isnt.

-Praveen

Maciej Olszewski May 10, 2018

unfortunately it's not, igor is a name of our bot and his username in JIRA is igot

Maciej Olszewski May 10, 2018 • edited

Oh right i forget to add errors...
2018-05-10 11:58:09,582 ERROR [runner.AbstractScriptListener]: Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: <inline script>
java.lang.ClassCastException: com.atlassian.greenhopper.service.sprint.Sprint cannot be cast to java.util.Collection
at com.atlassian.greenhopper.customfield.sprint.SprintCFType.valuesEqual(SprintCFType.java:83)
at com.atlassian.jira.issue.fields.ImmutableCustomField.valuesEqual(ImmutableCustomField.java:1548)
at com.atlassian.jira.issue.fields.ImmutableCustomField.updateValue(ImmutableCustomField.java:423)
at com.atlassian.jira.issue.fields.ImmutableCustomField.updateValue(ImmutableCustomField.java:395)
at com.atlassian.jira.issue.managers.DefaultIssueManager.updateFieldValues(DefaultIssueManager.java:704)
at com.atlassian.jira.issue.managers.DefaultIssueManager.updateIssue(DefaultIssueManager.java:669)
at com.atlassian.jira.issue.managers.DefaultIssueManager.updateIssue(DefaultIssueManager.java:655)
at com.atlassian.jira.issue.managers.RequestCachingIssueManager.updateIssue(RequestCachingIssueManager.java:214)
at com.atlassian.jira.issue.IssueManager$updateIssue$0.call(Unknown Source)
at Script246.run(Script246.groovy:23)
Maybe this will help

Praveen May 10, 2018

Hi,

Can you try this code, instead of the current if loop,

if(!it.equals(primarySprint))
Maciej Olszewski May 10, 2018

ok i got other error:

 2018-05-10 12:06:28,960 ERROR [runner.AbstractScriptListener]: Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: <inline script>
java.lang.NullPointerException: Cannot invoke method removeIssuesFromSprint() on null object
at Script283$_run_closure2.doCall(Script283.groovy:27)
at Script283.run(Script283.groovy:25)

Praveen May 10, 2018 • edited

Hi Maciej,

Looks like you have to pass correct parameters for the "removeIssuesFromSprint()"  method.

removeIssuesFromSprint(com.atlassian.crowd.embedded.api.User user, Sprint sprint, java.util.Set<com.atlassian.jira.issue.Issue> issues) 

 please chekc the below link for clarifications.

https://docs.atlassian.com/jira-software/6.2.2/com/atlassian/greenhopper/service/sprint/SprintIssueServiceImpl.html

 

-Praveen

Maciej Olszewski May 11, 2018
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserManager
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.greenhopper.service.sprint.Sprint
import com.atlassian.greenhopper.service.sprint.SprintIssueService

SprintIssueService sprintIssueService

def issue = event.issue
def issueUpdate = (MutableIssue) issue
//log.warn "Custom Listener Check! " + issue
//log.warn "Custom Listener " + event.getChangeLog().getRelated("ChildChangeItem").find{it.field == "Sprint"}
def igor = ComponentAccessor.getComponent(UserManager).getUserByName("igot")
def cfManager = ComponentAccessor.getComponent(CustomFieldManager)
def sprint = cfManager.getCustomFieldObjectByName("Sprint")
def sprintValue = issue.getCustomFieldValue(sprint)
ArrayList<Sprint> sprints = (ArrayList<Sprint>)issue.getCustomFieldValue(sprint)
log.warn "Custom Listener "+sprints[0]
def primarySprint = sprints[0]
Set<Issue> setI
setI.add(issue)
sprints.each(){
if(!it.equals(null)){
log.warn "Custom Listener " + it
log.warn "Custom Listener " + [issue] as Collection
if(!it.equals(primarySprint)){
sprintIssueService.removeIssuesFromSprint(igor, it, setI)
}
}
}
ComponentAccessor.getComponent(IssueManager).updateIssue(igor, issueUpdate, EventDispatchOption.ISSUE_UPDATED, false)

Ok i added them as set and now error is 

java.lang.NoClassDefFoundError: groovy/lang/GroovyObject
 at Script382$_run_closure1.doCall(Script382.groovy:30)
 at Script382.run(Script382.groovy:25)
 at com.onresolve.scriptrunner.runner.ScriptRunnerImpl.runScriptAndGetContext(ScriptRunnerImpl.groovy:159)
 at com.onresolve.scriptrunner.runner.ScriptRunner$runScriptAndGetContext$3.callCurrent(Unknown Source)
 at com.onresolve.scriptrunner.runner.ScriptRunner$runScriptAndGetContext$3.callCurrent(Unknown Source)
 at com.onresolve.scriptrunner.runner.ScriptRunnerImpl.runStringAsScript(ScriptRunnerImpl.groovy:148)
 at com.onresolve.scriptrunner.runner.ScriptRunner$runStringAsScript$2.call(Unknown Source)
 at com.onresolve.scriptrunner.canned.jira.workflow.listeners.CustomListener.doScript(CustomListener.groovy:120)
Caused by: java.lang.ClassNotFoundException: groovy.lang.GroovyObject

 I also added note with each sprint and it shows that cause may be a closed sprints. Do you think this is it?

Cheers,
Maciej

Maciej Olszewski May 11, 2018

Maybe it detects "[issue] as Set" as null?

Praveen May 11, 2018

Can you try returning sprints arraylist and check if it is null?

-Praveen

Praveen May 11, 2018

I have a trick to troubleshoot easily in scriptrunner, you can create a dummy script field and paste the above code (dont save it). Then you can return the values that you wanted to see using the preview option. You just have to provide the issue key as input.

//** Ignoring the imports
SprintIssueService sprintIssueService

//def issue = event.issue
def issueUpdate = (MutableIssue) issue
def igor = ComponentAccessor.getComponent(UserManager).getUserByName("igot")
def cfManager = ComponentAccessor.getComponent(CustomFieldManager)
def sprint = cfManager.getCustomFieldObjectByName("Sprint")
def sprintValue = issue.getCustomFieldValue(sprint)
ArrayList<Sprint> sprints = (ArrayList<Sprint>)issue.getCustomFieldValue(sprint)

return sprints //this will show you the values in the arraylist

def primarySprint = sprints[0]
Set<Issue> setI
setI.add(issue)
sprints.each(){
if(!it.equals(null)){
if(!it.equals(primarySprint)){
sprintIssueService.removeIssuesFromSprint(igor, it, setI)
}
}
}
ComponentAccessor.getComponent(IssueManager).updateIssue(igor, issueUpdate, EventDispatchOption.ISSUE_UPDATED, false)

Note:

1. You dont have to delare the issue in the code

2. The preview will return only the first return value

3. This is only for trubleshooting (once code is ready just close the sccript field)

-Praveen

Maciej Olszewski May 11, 2018

sprint test.pngWell it doesn't look null at all :D, I think error is with Set Issue :( but i don't know why

Maciej Olszewski May 11, 2018

I also checked [issue] as Set and it doesn't return null it returns [JIRA-5082] <example issue>

Maciej Olszewski May 11, 2018

Just to be sure i checked igor but it return igot(igot) so i really don;t know where is null....

Praveen May 11, 2018

Check by returning these three after entering the loop.  you can return them all at one time with the code below

return "User: "+igor.toString()+" Sprint: "+it.toString()+" Issue:"+setI.toString() 

-Praveen

Praveen May 11, 2018

Got it! change the getting the first spring to something like below, just sprints[0] will have a whole lot of info along with the name of the sprint. YOu have to use the getName() method to get the name alone.

def primarySprint = sprints[0].getName()
Maciej Olszewski May 11, 2018

To something like that?

SprintIssueService sprintIssueService

def issue = event.issue
def issueUpdate = (MutableIssue) issue
def igor = ComponentAccessor.getComponent(UserManager).getUserByName("igot")
def cfManager = ComponentAccessor.getComponent(CustomFieldManager)
def sprint = cfManager.getCustomFieldObjectByName("Sprint")
def sprintValue = issue.getCustomFieldValue(sprint)
ArrayList<Sprint> sprints = (ArrayList<Sprint>)issue.getCustomFieldValue(sprint)
log.warn "Custom Listener "+sprints[0]
def primarySprint = sprints[0].getName()
sprints.each(){
if(!it.equals(null) && issue != null){
log.warn "Custom Listener " + it
if(!it.getName().equals(primarySprint)){
sprintIssueService.removeIssuesFromSprint(igor, it, [issue] as Set)
}
}
}

Still null error :( 

Praveen May 11, 2018
SprintIssueService sprintIssueService

def issue = event.issue
def issueUpdate = (MutableIssue) issue
def igor = ComponentAccessor.getComponent(UserManager).getUserByName("igot")
def cfManager = ComponentAccessor.getComponent(CustomFieldManager)
def sprint = cfManager.getCustomFieldObjectByName("Sprint")
def sprintValue = issue.getCustomFieldValue(sprint)
ArrayList<Sprint> sprints = (ArrayList<Sprint>)issue.getCustomFieldValue(sprint)
log.warn "Custom Listener "+sprints[0]
def primarySprint = sprints[0].getName()
def issueSet = []
issueSet.add(issue)
for(it in sprints){
if(!(it.getName()).equals(primarySprint)){
sprintIssueService.removeIssuesFromSprint(igor, it, issueSet)
}
}


Praveen May 11, 2018

Try this, and check if you are still getting the same error :(

-Praveen

Maciej Olszewski May 11, 2018

Unfortunately still same :(

2018-05-11 14:26:02,648 ERROR [runner.AbstractScriptListener]: *************************************************************************************
2018-05-11 14:26:02,648 ERROR [runner.AbstractScriptListener]: Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: <inline script>
java.lang.NullPointerException: Cannot invoke method removeIssuesFromSprint() on null object
 at Script509$_run_closure1.doCall(Script509.groovy:29)
 at Script509.run(Script509.groovy:25)
Praveen May 11, 2018

Are those 3 parameters returning values inside the if loop? If yes, I am not sure on why this method is givving null pointer :(

-Praveen

Maciej Olszewski May 11, 2018

Ok i think we are close!

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserManager
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.greenhopper.service.sprint.Sprint
import com.atlassian.greenhopper.service.sprint.SprintIssueService

SprintIssueService sprintIssueService

def issue = event.issue
def issueUpdate = (MutableIssue) issue
def igor = ComponentAccessor.getComponent(UserManager).getUserByName("igot")
def cfManager = ComponentAccessor.getComponent(CustomFieldManager)
def sprint = cfManager.getCustomFieldObjectByName("Sprint")
def sprintValue = issue.getCustomFieldValue(sprint)
ArrayList<Sprint> sprints = (ArrayList<Sprint>)issue.getCustomFieldValue(sprint)
def primarySprint = sprints[0].getName()
def issueSet = []
issueSet.add(issue)
log.warn "Loop"
for(it in sprints){
log.warn "In loop"
log.warn it['state']
if(it['state'] == 'ACTIVE'){
log.warn "In in"
sprintIssueService.removeIssuesFromSprint(igor, it, issueSet)
}
}

green light but i don;t know why it didn't get into if here are logs 

2018-05-11 15:43:28,665 WARN [runner.ScriptRunnerImpl]: Loop
2018-05-11 15:43:28,665 WARN [runner.ScriptRunnerImpl]: In loop
2018-05-11 15:43:28,665 WARN [runner.ScriptRunnerImpl]: CLOSED
2018-05-11 15:43:28,665 WARN [runner.ScriptRunnerImpl]: In loop
2018-05-11 15:43:28,666 WARN [runner.ScriptRunnerImpl]: ACTIVE

and i am really thankful for your time!  

Maciej Olszewski May 11, 2018

Ok it got in...

2018-05-11 16:30:07,053 WARN [runner.ScriptRunnerImpl]: Loop
2018-05-11 16:30:07,055 WARN [runner.ScriptRunnerImpl]: com.atlassian.greenhopper.service.sprint.Sprint@3c161bcc[id=5108,rapidViewId=678,state=CLOSED,name=ASUM 49,startDate=2018-01-30T11:36:08.041+01:00,endDate=2018-02-13T12:01:00.000+01:00,completeDate=2018-05-11T15:30:38.414+02:00,sequence=5108]
2018-05-11 16:30:07,055 WARN [runner.ScriptRunnerImpl]: In loop
2018-05-11 16:30:07,055 WARN [runner.ScriptRunnerImpl]: CLOSED
2018-05-11 16:30:07,055 WARN [runner.ScriptRunnerImpl]: com.atlassian.greenhopper.service.sprint.Sprint@20fe1d6b[id=5213,rapidViewId=1376,state=ACTIVE,name=Test sprint,startDate=2018-05-11T15:37:11.640+02:00,endDate=2018-05-25T16:02:00.000+02:00,completeDate=<null>,sequence=5213]
2018-05-11 16:30:07,055 WARN [runner.ScriptRunnerImpl]: In loop
2018-05-11 16:30:07,055 WARN [runner.ScriptRunnerImpl]: ACTIVE
2018-05-11 16:30:07,056 WARN [runner.ScriptRunnerImpl]: In in
2018-05-11 16:30:07,058 ERROR [runner.AbstractScriptListener]: *************************************************************************************
2018-05-11 16:30:07,058 ERROR [runner.AbstractScriptListener]: Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: <inline script>
java.lang.NullPointerException: Cannot invoke method removeIssuesFromSprint() on null object
 at Script632.run(Script632.groovy:31)
Praveen May 13, 2018

Hi Maciej,

So it works only for active sprnts! Good that we found it out.

-Praveen

Maciej Olszewski May 14, 2018

it still doesn't work :(

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserManager
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.greenhopper.service.sprint.Sprint
import com.atlassian.greenhopper.service.sprint.SprintIssueService

SprintIssueService sprintIssueService

def issue = event.issue
def issueUpdate = (MutableIssue) issue
def igor = ComponentAccessor.getComponent(UserManager).getUserByName("igot")
def cfManager = ComponentAccessor.getComponent(CustomFieldManager)
def sprint = cfManager.getCustomFieldObjectByName("Sprint")
def sprintValue = issue.getCustomFieldValue(sprint)
ArrayList<Sprint> sprints = (ArrayList<Sprint>)issue.getCustomFieldValue(sprint)
def primarySprint = sprints[0].getName()
def issueSet = []
issueSet.add(issue)
log.warn "Loop"
for(it in sprints){
log.warn it
log.warn "In loop"
log.warn it['state']
if(it.active){
log.warn "In if"
sprintIssueService.removeIssuesFromSprint(igor, it, issueSet)
}
}

 Logs:

2018-05-11 16:30:46,226 WARN [runner.ScriptRunnerImpl]: Loop
2018-05-11 16:30:46,226 WARN [runner.ScriptRunnerImpl]: com.atlassian.greenhopper.service.sprint.Sprint@3c161bcc[id=5108,rapidViewId=678,state=CLOSED,name=ASUM 49,startDate=2018-01-30T11:36:08.041+01:00,endDate=2018-02-13T12:01:00.000+01:00,completeDate=2018-05-11T15:30:38.414+02:00,sequence=5108]
2018-05-11 16:30:46,226 WARN [runner.ScriptRunnerImpl]: In loop
2018-05-11 16:30:46,227 WARN [runner.ScriptRunnerImpl]: CLOSED
2018-05-11 16:30:46,227 WARN [runner.ScriptRunnerImpl]: com.atlassian.greenhopper.service.sprint.Sprint@20fe1d6b[id=5213,rapidViewId=1376,state=ACTIVE,name=Test sprint,startDate=2018-05-11T15:37:11.640+02:00,endDate=2018-05-25T16:02:00.000+02:00,completeDate=<null>,sequence=5213]
2018-05-11 16:30:46,227 WARN [runner.ScriptRunnerImpl]: In loop
2018-05-11 16:30:46,227 WARN [runner.ScriptRunnerImpl]: ACTIVE
2018-05-11 16:30:46,227 WARN [runner.ScriptRunnerImpl]: In in
2018-05-11 16:30:46,229 ERROR [runner.AbstractScriptListener]: *************************************************************************************
2018-05-11 16:30:46,229 ERROR [runner.AbstractScriptListener]: Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: <inline script>
java.lang.NullPointerException: Cannot invoke method removeIssuesFromSprint() on null object
 at Script632.run(Script632.groovy:31)

Active check is working properly but even still it doesn't work when it comes to remove from sprint function :/ 

Maciej Olszewski May 14, 2018 • edited

Ok i finally made it work!

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserManager
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.greenhopper.service.sprint.Sprint
import com.atlassian.greenhopper.service.sprint.SprintIssueService
import com.onresolve.scriptrunner.runner.customisers.JiraAgileBean

@JiraAgileBean
SprintIssueService sprintIssueService

def issue = ComponentAccessor.getIssueManager().getIssueObject("JIRA-6418") as Issue
def issueUpdate = (MutableIssue) issue
def igor = ComponentAccessor.getComponent(UserManager).getUserByName("igot")
def cfManager = ComponentAccessor.getComponent(CustomFieldManager)
def sprints = sprintIssueService.getSprintsForIssue(igor, issue)
def issueSet = []
issueSet.add(issue)
log.warn "Loop"
for(it in sprints.getValue()){
log.warn it
log.warn "In loop"
log.warn it['state']
if(it.active){
log.warn "In in"
sprintIssueService.removeIssuesFromSprint(igor, it, issueSet)
}
}

I just needed to add JiraAgileBean, but i don;t know how it works now ^^"

0 votes
Maciej Olszewski May 11, 2018
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.user.util.UserManager
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.greenhopper.service.sprint.Sprint
import com.atlassian.greenhopper.service.sprint.SprintIssueService

SprintIssueService sprintIssueService

def issue = event.issue
def issueUpdate = (MutableIssue) issue
def igor = ComponentAccessor.getComponent(UserManager).getUserByName("igot")
def cfManager = ComponentAccessor.getComponent(CustomFieldManager)
def sprint = cfManager.getCustomFieldObjectByName("Sprint")
def sprintValue = issue.getCustomFieldValue(sprint)
ArrayList<Sprint> sprints = (ArrayList<Sprint>)issue.getCustomFieldValue(sprint)
def primarySprint = sprints[0].getName()
def issueSet = []
issueSet.add(issue)
for(it in sprints){
if(!(it.getName()).equals(primarySprint)){
log.warn "Igot check: "+igor
log.warn "Sprint check: "+it
log.warn "Issue set check: "+issueSet
sprintIssueService.removeIssuesFromSprint(igor, it, issueSet)
}
}

here are logs:

2018-05-11 15:02:58,768 WARN [runner.ScriptRunnerImpl]: Custom Listener com.atlassian.greenhopper.service.sprint.Sprint@6242e5f9[id=299,rapidViewId=201,state=ACTIVE,name=AS - Sprint 0,startDate=2014-06-09T15:22:45.411+02:00,endDate=2014-06-23T15:22:00.000+02:00,completeDate=<null>,sequence=299]
2018-05-11 15:02:58,768 WARN [runner.ScriptRunnerImpl]: Igot check: igot(igot)
2018-05-11 15:02:58,768 WARN [runner.ScriptRunnerImpl]: Sprint check: com.atlassian.greenhopper.service.sprint.Sprint@2f8e1a8a[id=3526,rapidViewId=678,state=CLOSED,name=ASUM 30,startDate=2017-04-24T16:55:41.192+02:00,endDate=2017-05-07T17:25:00.000+02:00,completeDate=2017-05-08T16:26:41.364+02:00,sequence=3526]
2018-05-11 15:02:58,769 WARN [runner.ScriptRunnerImpl]: Issue set check: [JIRA-5082]
2018-05-11 15:02:58,771 ERROR [runner.AbstractScriptListener]: *************************************************************************************
2018-05-11 15:02:58,771 ERROR [runner.AbstractScriptListener]: Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: <inline script>
java.lang.NullPointerException: Cannot invoke method removeIssuesFromSprint() on null object
 at Script544.run(Script544.groovy:30)

Everything seems right and definitely not null :/

Praveen May 11, 2018

I'm not sure what the issue is, but I guess the sprint should be active (check the below link),

https://community.atlassian.com/t5/Jira-questions/Removing-Issues-from-Sprint-using-ScriptRunner-or-the-Java-API/qaq-p/657524 

-Praveen

Suggest an answer

Log in or Sign up to answer
This widget could not be displayed.
This widget could not be displayed.
Community showcase
Published in Next-gen

Introducing subtasks for breaking down work in next-gen projects

Teams break work down in order to help simplify complex tasks. This is often done iteratively, with tasks being broken down into smaller tasks and so on until the work is accurately captured in well-...

7,459 views 23 35
Read article

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