The list below contains some use cases that can be realized using Smart Fields for Jira.
Make sure to come by once in a while since the number of use cases will grow over time.
Use cases and examples
Use case | Feature | Expression |
---|---|---|
All email addresses mentioned in the issue description | %{findPattern(%{issue.description},"(?<=mailto:)([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)")} | |
Assignee groups | issue.assignee?.groups || "" | |
Attachment authors | issue?.attachments?.reduce((result,att) => result.set(att.author.displayName,""), new Map()).entries().flatten().filter(entry => entry != "") | |
Attachment filenames | %{issue.attachments} | |
Attachment mimetypes | issue?.attachments?.reduce((result,att) => result.set(att.mimeType,""), new Map()).entries().flatten().filter(entry => entry != "") | |
Average time since creation of sub-tasks | %{floor(avg(toNumberList(jiraExpression("issue.subtasks.map(i=>(Number(new Date())-Number(i.created))/1000/60/60/24)"))))} | |
Comment authors | issue.comments.reduce((result, comment) => result.set(comment.author.displayName, ""), new Map()).entries().flatten().filter(entry => entry != "") | |
Completion percentage by status | %{%{issue.status} = "Open" ? 0 : ( %{issue.status} = "Planning" ? 0.15 : ( %{issue.status} = "In Progress" ? 0.6 : ( %{issue.status} = "In Review" ? 0.6 : ( %{issue.status} = "Implementing" ? 0.85 : 1 ))))} Please, replace the status names as well as the completion percentages with the ones of your choice. Any status not included in the sequence will return 1. | |
Components from all sub-tasks | %{distinct(fieldValue(%{issue.components}, subtasks()))} | |
Creation date of the linked epic | issue?.epic ? issue?.epic.created.toCalendarDate().toString() : "" | |
Difference between the current day and the earliest sub-task due date | %{max(fieldValue({issue.dueDate},subtasks())) != null ? ceil((min(filterByValue(fieldValue({issue.dueDate},subtasks()),!=, null )) - {system.currentDateTime}) / DAY) : null} | |
Elapsed time between creation and resolution | %{{issue.resolutionDate} != null ? (({issue.resolutionDate} - {issue.created}) / DAY ) : null} The duration can be retuned in years, weeks, days, hours or minutes if the DAY keyword is replaced in the above expression with the corresponding time constant. | |
Field value validation | %{matches(%{issue.cfnnnnn},"6|7|8|9") ? "Valid":"Invalid"} The values entered as arguments in the matches() parser function can be replaced with different values or with a regular expressions to validate differently the value of the custom field. Please, note that it is necessary to replace nnnnn with the ID of the custom field. | |
First comment | issue?.comments[0]?.body?.plainText == null ? '' : issue?.comments[0]?.body?.plainText The text will be returned using wiki markup, e.g. bold text will be shown as *bold text*. | |
First comment author | issue?.comments[0]?.author?.displayName | |
Highest value out of several fields | %{max([{issue.cfAAAAA}, {issue.cfBBBBB}, {issue.cfCCCCC}, {issue.cfDDDDD}, {issue.cfEEEEE}])} Please, replace the field codes with the ones of your choice. | |
Historical due dates | issue.changelogs.filter(changelog => changelog.items.some(item => item.fieldId == 'duedate' && item.from != null)). map(changelog => {duedateentry: changelog.items.filter(item => item.fieldId == 'duedate' )}). map(e => new CalendarDate(e.duedateentry[0].from).toString()).join(", ") | |
ICE score | (issue?.customfield_IIIII || 0) * (issue?.customfield_CCCCC || 0) * (issue?.customfield_EEEEE || 0) Replace the IDs with the ones of your number custom fields Impact (IIIII), Confidence (CCCCC) and Ease (EEEEE). | |
Issue ID | %{issue.id} | |
Issue type description | issue?.issueType?.description | |
Keys of linked issues in current project | %{filterByProject(linkedIssues(), %{issue.project.key})} | |
Keys of non-estimated sub-tasks | issue.subtasks.filter(s => s?.originalEstimate == null && s?.customfield_nnnnn == null).map(i => i.key).join(", ") Please, note that it is necessary to replace nnnnn with the ID of the custom field displaying the original estimate or story points. | |
Last assignee change author | issue.changelogs.filter(changelog => changelog.items.some(item => item.fieldId == 'assignee')).map(changelog => changelog.author.displayName)[0] || "" | |
Last comment | %{issue.lastComment} | |
Last comment author | issue?.comments[issue?.comments?.length-1]?.author?.displayName || "" | |
Last status change author | issue.changelogs.filter(c=>c.items.some(i=>i.field=="status"))[0]?.author?.displayName || "" | |
Last status change date | issue.changelogs.filter(changelog => changelog.items.some(item => item.fieldId == 'status')).map(changelog => changelog.created).concat(issue.created)[0].toString() | |
Lowest date and time from linked issues | let minDate = issue?.links.map(link => link.linkedIssue.customfield_nnnnn).filter( d => d != null); minDate.length > 0 ? new Date(minDate.reduce((a, b) => a < b ? a : b )).toString() : "" Please, replace nnnnn with the ID of a Date Time Picker custom field. | |
Lowest date from linked issues | let minDate = issue?.links.map(link => link.linkedIssue.customfield_nnnnn).filter( d => d != null); minDate.length > 0 ? new CalendarDate(minDate.reduce((a, b) => a < b ? a : b )).toString() : "" Please, replace nnnnn with the ID of a Date Picker custom field. | |
Months elapsed | %{{issue.cfnnnnn} != null ? ((year({system.currentDateTime}, RUN_AS_LOCAL) - year({issue.cfnnnnn}, RUN_AS_LOCAL))*12 + month({system.currentDateTime}, RUN_AS_LOCAL) - (month({issue.cfnnnnn}, RUN_AS_LOCAL))) : null} Please, replace both nnnnn in the above expression with the ID of your Date Picker custom field. | |
Number of affected versions | %{issue.versions.length} | |
Number of assignee changes | issue.changelogs.filter(changelog => changelog.items.some(item => item.fieldId == 'assignee')).length | |
Number of attachment authors | issue.attachments.map(attachment => attachment.author.accountId).reduce((a, b) => a.includes(b) ? a : a.concat(b), []).length | |
Number of attachments | %{issue.attachments.length} | |
Number of attachments in sub-tasks | issue.subtasks.map(subtask => subtask.attachments.length).reduce((a, b) => a + b, 0) | |
Number of comment authors | issue.comments.map(comment => comment.author.displayName).reduce((a, b) => a.includes(b) ? a : a.concat(b), []).length | |
Number of comments | issue?.comments?.length | |
Number of components | issue.components.length | |
Number of days in previous status | let statusChangelogs = issue.changelogs.filter(changelog => changelog.items.some(item => item.fieldId == 'status')); statusChangelogs[0] ? (statusChangelogs[0].created.getTime() - (statusChangelogs[1] ? statusChangelogs[1].created : issue.created).getTime()) / (24*60*60*1000) : null | |
Number of fix versions | %{issue.fixVersions.length} | |
Number of incoming issue links | issue?.links?.filter(link => link.direction == "inward").length | |
Number of issues under epic | issue.stories?.length | |
Number of issues under epic including sub-tasks | %{%{issue.issueType} = "Epic" ? count(append(issuesUnderEpic(),subtasks(issuesUnderEpic()))) : null } | |
Number of issues with the same fix versions | %{%{issue.fixVersions} != null ? count(issuesFromJQL("fixVersion in ('" + jiraExpression( "issue?.fixVersions?.map(v => v?.name).join(\"','\") ") + "') ")) : null} | |
Number of labels | %{issue.labels.length} | |
Number of linked issues | %{issue.links.length} | |
Number of open child issues | %{count(issuesFromJQL("statusCategory in ('To Do','In Progress') and parent = " + %{issue.key}))} | |
Number of outgoing issue links | issue?.links?.filter(link => link.direction == "outward").length | |
Number of status changes | issue.changelogs.filter(changelog => changelog.items.some(item => item.fieldId == 'status')).length | |
Number of sub-tasks | %{issue.subtasks.length} | |
Number of sub-tasks based on status | %{count(filterByStatus(subtasks(), "Done, Completed"))} Please, note that by changing "Done, Completed" into other statuses and resolutions you can adapt this use case for your needs. | |
Number of sub-tasks based on status and issue type | %{count(filterByIssueType(filterByStatus(subtasks(), "Done, Completed"), "Sub-task, Sub-story"))} Please, note that by changing "Done, Completed" into other statuses and "Sub-task, Sub-story" into other issue types, you can adapt this use case for your needs. | |
Number of times issue was changed to the current status | issue.changelogs.filter(changelog => changelog.items.some(item => item.fieldId == 'status' && item.toString == issue?.status?.name)).length | |
Number of times that a custom field has been changed | issue.changelogs.filter(changelog => changelog.items.some(item => item.fieldId == 'customfield_nnnnn')).length Please, replace nnnnn with the ID of your custom field in the above expression. | |
Number of times that an issue was rejected | issue.changelogs.filter(changelog => changelog.items.some(item => item.fieldId == 'resolution' && item.toString == 'Rejected')).length In case you have other statuses and resolutions, feel free to change these arguments: " item.fieldId == 'resolution' && item.toString == 'Rejected'" | |
Number of unresolved blocking issues | %{count(filterByResolution(linkedIssues("is blocked by"), ""))} | |
Operations with field values from sub-tasks | issue.subtasks.length && issue.subtasks.map( s => (s.customfield_nnnnn || 0) * (s.customfield_ppppp || 0)).reduce((a, b) => a + b) Please, replace nnnnn and ppppp in the above expression with the IDs of the Number fields. | |
Parent assignee | %{parent.assignee.displayName} | |
Parent priority | %{parent.priority} | |
Parent reporter | %{parent.reporter.displayName} | |
Parent status | %{parent.status} | |
Percentage of completed child issues | let children = (issue.isEpic ? issue.stories : issue.subtasks); children.length ? (children.filter(child => child.resolution).length / children.length * 100 + '').slice(0, 4) + '%' : '' | |
Previous assignee | issue.changelogs.map(i=>i.items).flatten().filter(i=>i.field=="assignee").length>0 ? issue.changelogs.map(i=>i.items).flatten().filter(i=>i.field=="assignee").map(a=>a.fromString)[0] : "" | |
Previous issue status | %{issue.status.previous} | |
Previous reporter | let reporters= issue.changelogs.map(i=>i.items).flatten().filter(i=>i.field=="reporter"); reporters.length>0 ? reporters.map(a=>a.fromString)[0] == null ? "" :reporters.map(a=>a.fromString)[0] : "" | |
Priority assessment based on field values | %{sum([%{issue.cfnnnnn} = "Option 1" ? 1 : 0, %{issue.cfppppp} = "Option 1" ? 1 : 0 ])} Please, replace nnnnn and ppppp with the IDs of the custom fields, e.g. Select List (single choice). | |
Priority description | issue?.priority?.description | |
Project category of issue | %{issue.project.category} | |
Project description | %{issue.project.description} | |
Project lead | %{issue.project.leadDisplayName} | |
Remaining budget | %{{issue.cfnnnnn} = null ? null : {issue.cfnnnnn} - sum(append(fieldValue({issue.cfppppp}, subtasks()), [0]))} | |
Status description | issue?.status?.description | |
Sub-task assignees | %{distinct(fieldValue(%{issue.assignee.displayName}, subtasks()))} | |
Sub-task labels | %{distinct(fieldValue(%{issue.labels}, subtasks()))} | |
Sub-task reporters | %{distinct(fieldValue(%{issue.reporter.displayName}, subtasks()))} | |
Sum of field values from issues in JQL query | %{sum(fieldValue({issue.cfnnnnn},issuesFromJQL("project = DEMO and 'fieldName' is not empty")))} Please, replace nnnnn in the above expression with the ID of the Number custom field and fieldName with its name. | |
Sum of original estimates of issues under epic's parent | %{%{issue.issueType} = "Initiative" ? sum( append(fieldValue( {issue.originalEstimate} , union( issuesFromJQL("issuekey in portfolioChildIssuesOf("+ %{issue.key} +")"), issuesUnderEpic(issuesFromJQL("issuekey in portfolioChildIssuesOf("+ %{issue.key} +")")))), [0] )) / 60 : null} We consider the epic's parent to be a Initiatiave in this expression, but it could differ depending on the Advanced Roadmaps issue type hierarchy. To adapt to your issue type hierarchy change the following part of the expression: " %{issue.issueType} = "Your Custom Issue Type" " | |
Sum of story points of completed child issues | %{sum(fieldValue({issue.cfnnnnn},issuesFromJQL("statusCategory = Done AND 'parent' = "+%{issue.key})))} Replace "nnnnn" with the ID of the story points field. Keep in mind, that this smart fields should be displayed on a parent issue. | |
Sum of story points of completed sibling issues | %{sum(fieldValue({issue.cfnnnnn},issuesFromJQL("statusCategory = Done AND 'parent' = "+%{parent.key})))} Replace "nnnnn" with the ID of the story points field. Keep in mind, that this smart fields should be displayed on a child issue. | |
Sum of time spent of blocking issues | %{sum(append(fieldValue({issue.timeSpent},linkedIssues("is blocked by")), [0]))/60} If the resulting number from the expression contains too many decimal numbers, feel free to select one of the available display formats or to configure it manually. | |
Sum of time spent of epic and issues under it | %{%{issue.issueType} = "Epic" ? (sum(append(fieldValue({issue.timeSpent}, issuesUnderEpic()), [{issue.timeSpent}] )) / 60) : null} If the resulting number from the expression contains too many decimal numbers, feel free to select one of the available display formats or to configure it manually. | |
Sum of time spent of issues under epic | %{%{issue.issueType} = "Epic" ? sum(append(fieldValue({issue.timeSpent}, issuesUnderEpic()), [0])) / 60 : null} If the resulting number from the expression contains too many decimal numbers, feel free to select one of the available display formats or to configure it manually. | |
Sum of time spent of linked issues | %{sum(append(fieldValue({issue.timeSpent}, linkedIssues()), [0])) / 60} | |
Sum of time spent of the current and linked issues | %{sum(append(fieldValue({issue.timeSpent}, union(linkedIssues(), issueKeysToIssueList(%{issue.key}) )), [0])) / 60} | |
Time left until due date | %{{issue.dueDate} != null ? max({issue.dueDate} - {system.currentDateTime}, 0) / DAY : null} | |
Timely resolution assessment | %{{issue.resolutionDate} != null AND {issue.dueDate} != null ? ({issue.resolutionDate} <= {issue.dueDate} ? "Yes" : "No") : (datePart({system.currentDateTime}, RUN_AS_LOCAL) > {issue.dueDate} ? "No" : "")} | |
Time since creation | %{floor(({system.currentDateTime} - {issue.created})/DAY)} The duration can be retuned in years, weeks, days, hours or minutes if the DAY keyword is replaced in the above expression with the corresponding time constant. | |
Time spent ratio | %{%{issue.originalEstimate} != null AND %{issue.timeSpent} != null ? {issue.timeSpent} / {issue.originalEstimate} * 100 : null} | |
Total attachment size in MB | issue.attachments.reduce((result, att) => result + att.size, 0) / 1024 / 1024 | |
Total original estimate of blocking issues | %{sum(fieldValue({issue.originalEstimate},linkedIssues("is blocked by")))/60} If the resulting number from the expression contains too many decimal numbers, feel free to select one of the available display formats or to configure it manually. | |
Total remaining estimate of blocking issues | %{sum(append(fieldValue({issue.remainingEstimate},linkedIssues("is blocked by")),[0]))/60} If the resulting number from the expression contains too many decimal numbers, feel free to select one of the available display formats or to configure it manually. | |
Total remaining estimate of issues under epic | %{%{issue.issueType} = "Epic" ? sum(append(fieldValue({issue.remainingEstimate}, issuesUnderEpic()), [0])) / 60 : null} | |
Total remaining estimate of issues under epic and sub-tasks | %{%{issue.issueType} = "Epic" ? sum(append(fieldValue({issue.remainingEstimate}, union(issuesUnderEpic(), subtasks(issuesUnderEpic())) ), [0])) / 60 : null} | |
Total story points of issues in an epic | %{sum(fieldValue({issue.cfNNNNN}, issuesUnderEpic()))} Replace the ID with the one of your 'Story Points' custom field. |