I've been refining the way I deal with software development workflows for more than 20 years now. I've worked in different positions of seniority, with team/organisation sizes ranging from minuscule to huge, and at various stages of the software development life cycle. This article represents the current state of evolution of the Jira configuration I use.
Team-managed vs company-managed projects
I advise choosing company-managed
, team-managed
projects offer much less customisability, in particular the scrum/kanban board layout is extremely limited and cannot be customised to the degree shown in the configuration suggested by this article.
Custom fields
The following fields are not part of Jira by default but can be useful for tracking important information. These fields don't create any additional maintenance burden as they are updated automatically by the workflow suggestion shown later in the article.
-
Developer
: The Jira user associated with the person who developed the ticket. -
Tester
: The Jira user associated with the person who tested the ticket. -
Start time
: The date and time the ticket started being actively worked on (post refinement). -
End time
: The date and time the ticket was closed.
Workflow
Statuses
Draft
The initial state of any ticket to show it hasn't been refined. After a refinement session:
-
Discard
orMark as duplicate
sets the status toDone
with a correspondingResolution
. -
Finish refinement
sets the status toTo do
.
To do
The ticket is ready to work on unless there is are tickets related to it with an is blocked by
association that are not yet Done
. Generally issues with a status of To do
won't have an Assignee
but a person can set this if they want to claim the ticket to show that they would like to be the one to develop it in the future.
-
Redraft
reverts the status toDraft
in case something was missed or life changed. -
Start
updates the status toIn Progress
and:- Assign the ticket to the user who performed the transition.
- Set the
Developer
custom field to the assignee. - Set the
Start time
custom field to the current date and time.
In Progress
-
Pause
transitions the ticket back toTodo
. -
Request review
transitions the ticket toIn Review
.
In Review
-
Merge code and skip testing
can be useful for setting a ticket toDone
when it doesn't require any testing. This is usually not a good idea but an escape hatch when life doesn't make sense. This transition performs all the actions that thePass testing
transition does but also sets theTester
to theDeveloper
field to indicate that the developer tested their own ticket (which is generally bad practice but can be useful in limited circumstances). -
Merge code
sets the status toReady for review
and clears theAssignee
. TheDeveloper
custom field will still be there for use later.
Ready for QA
The ticket is ready for QA and won't have an Assignee
, but a person may set the Assignee
to claim the ticket to show that they would like to be the one to test the ticket in the future.
-
Start testing
updates the status toIn testing
and:- Set the
Assignee
to the user who performed the transition. - Set the
Tester
custom field to the assignee.
- Set the
-
Request change
sets the status back toTo do
in case something was noticed before testing started, when this happens a comment should be left in the ticket indicating what should change.
In testing
The ticket is actively being tested.
-
Request change
sets the status back toTo do
, if this is done the tester should leave a comment in the ticket to say what bugs were found or what changes should be made. -
Pass testing
updates the status toDone
with aCompleted
resolution and:- Updates the
Assignee
field so it's the same as theDeveloper
custom field. It's useful to quickly associated a closed ticket with the person who developed it and theTester
custom field is still there for deep diving. - Sets the
End time
custom field to the current date and time.
- Updates the
Done
The ticket is most likely done with forever but may be reopened in case something comes up at a later date. The Reopen
transition will:
- Clear the
Assignee
. - Clear the
End time
custom field. - Clear the
Resolution
which also has the effect of clearing theresolutiondate
. Resetting theresolutiondate
is particularly useful for ordering kanban/scrum board columns other thanDone
sensibly, how to do this will be shown later.
Automations
Jira automations can be used to prevent naughty things:
Do not assign QA issues to developer
A developer shouldn't test their own code, any attempts to assign a ticket in Ready for QA
or In testing
statuses to the user in the Developer
custom field will revert the assignation.
Prevent Developer transitioning issue to "In Test"
A developer shouldn't be the one to transition an issue to In testing
, any attempts to do this will be reverted. Here the Edit issue fields advanced
option has the following content:
{
"fields": {
"Tester": null
}
}
The board
The following filter creates a nice order for the kanban/scrum board:
project = YOURPROJECT
and (
status != Done
or resolution not in (Duplicate, "Cannot Reproduce", "Won't Do")
)
order by resolutiondate desc, fixVersion asc, priority desc
- Don't show issues which were discarded.
- Order the tickets in the
Done
column by the resolution date in descending order (i.e. most recently resolved tickets at the top).resolutiondate
won't be set for tickets without a status ofDone
(as enforced by the workflow) so thisorder by
clause won't have any effect for tickets in other columns. - For columns other than
Done
tickets will be shown in order of theirfixVersion
(i.e. tickets for the upcoming release will be shown at the top of the board), and within each release, tickets will be ordered by priority.
Releases
Assigning tickets to releases is a great way to keep track of progress of a particular release and for ordering the scrum/kanban board. Note that the releases list should be in descending date order (i.e. the most recent release should be at the top of the list and the oldest release should be at the bottom of the list). Releases can be dragged and dropped to reorder the list.
Filters
The following filters can be useful:
Open issues sorted by release then priority
project = MYPROJECT
and type != Epic and status != Done
order by fixVersion ASC, priority desc
Closed issues ordered by resolution date
project = MYPROJECT
and resolution not in (Duplicate, "Cannot Reproduce", "Won't Do")
order by resolutiondate
Issues in progress
project = MYPROJCT
and type != Epic
and status in ("In progress", "In review", "In testing")
order by fixVersion ASC, priority desc
Issues for next release
project = MYPROJECT
and fixVersion = earliestUnreleasedVersion()
and (
status != Done
or resolution not in (Duplicate, "Cannot Reproduce", "Won't Do")
)
order by resolutiondate desc, fixVersion asc, priority desc
Issues to be tested
project = MYPROJECT
and type != Epic and status = "Ready for QA"
order by fixVersion ASC, priority desc
Github
If the project shortcode in Jira is PJ
then issue identifiers will look like PJ-1
, PJ-42
etc. When creating a branch to begin work it's useful to name the branch after the Jira ticket with an optional prefix to show the type of work e.g.
-
fix/PJ-1
: A fix relating to the issuePJ-1
. -
chore/PJ-2
: A task relating to the issuePJ-2
. -
feature/PJ-3
: A feature/story relating to the issuePJ-3
. -
PJ-4
: If you don't care to mark the task type in the branch name.
Assuming this convention has been used (or that the PR title begins with PJ-42:
) a github action can be used to:
- Set the
pull request
title to the Jira ticket title. - Add a text block to the
pull request
description with a link to the Jira ticket (this block can be updated when the Jira content changes as updating text blocks is a supported feature ofinsidewhy/actions-body-fields
).
To do this the following file can be created within the github repository at a file location such as .github/workflows/jira-linker.yml
:
name: jira-linker
on:
pull_request:
types:
- opened
- reopened
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
action-jira-linker:
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
steps:
- name: determine associated issue id
id: get-ticket-id
run: |
ticket_match='grep -Eqx [A-Z]+-[0-9]+'
# first try to get the ticket id from the title
title="${{github.event.pull_request.title}}"
ticket_id=${title%%:*}
if ! echo $ticket_id | $ticket_match; then
# otherwise try to get the ticket id from the branch
branch=${{ github.head_ref || github.ref_name }}
ticket_id=${branch#*/}
ticket_id=${ticket_id%%_*}
if ! echo $ticket_id | $ticket_match; then
exit 0
fi
fi
echo "ticket-id=$ticket_id" >> $GITHUB_OUTPUT
- uses: insidewhy/action-get-jira-issue@v1
id: jira
if: steps.get-ticket-id.outputs.ticket-id
with:
user: ${{ secrets.JIRA_USER }}
token: ${{ secrets.JIRA_TOKEN }}
base-url: ${{ secrets.JIRA_BASE_URL }}
ticket-id: ${{ steps.get-ticket-id.outputs.ticket-id }}
- uses: insidewhy/action-body-fields@v1
if: steps.get-ticket-id.outputs.ticket-id
with:
prepend: true
title: '${{ steps.get-ticket-id.outputs.ticket-id }}: ${{ steps.jira.outputs.summary }}'
header: '## Status'
fields: |
Jira: [${{ steps.get-ticket-id.outputs.ticket-id }}: ${{ steps.jira.outputs.summary }}](${{ steps.jira.outputs.link }})
For this to work a Jira token must be created with permissions to update issues and set in the github secrets:
-
JIRA_USER
: The user that created the Jira token. -
JIRA_TOKEN
: The token itself. -
JIRA_BASE_URL
: The URL of the Jira instance e.g.https://my-kitten-project.atlassian.net
Top comments (0)