Epic Sum Up provides its user with a Java API. You can use this API to extract data from Epic Sum Up to use with other business components however needed.
This enables vast varieties of customization.
To access this data you can use a script extension like ScriptRunner within your Jira instance.
General To access our Java API in ScriptRunner, you can follow the ScriptRunner Documentation
We use the following annotation
GROOVY
@WithPlugin("aptis.plugins.epicSumUp")
The available @PluginModule can be found in our API Documentation
Basic custom field examples Calculate time spent for an issue and all of its children ProgressTimeResult
is used to access values that are specifically used as time values.
Custom Script Field
Code
GROOVY
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.ApplicationUser
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import aptis.plugins.epicSumUp.api.ProgressProvider
import aptis.plugins.epicSumUp.api.model.ProgressTimeResult
@WithPlugin("aptis.plugins.epicSumUp")
@PluginModule
ProgressProvider progressProvider
ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
ProgressTimeResult progress = progressProvider.getTimeProgress(
issue,
currentUser
)
return (long)progress.getTimeSpent()
Calculate story points for an issue and all of its children The ProgressNumberWithStatusResult
can be used to process the results of number custom fields (e.g. story points).
In contrast to ProgressResolutionResult , ProgressNumberWithStatusResult
will return a decimal number and even a budget, if possible.
Custom Script Field
Code
GROOVY
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import aptis.plugins.epicSumUp.api.ProgressProvider
import aptis.plugins.epicSumUp.api.model.ProgressNumberWithStatusResult
@WithPlugin("aptis.plugins.epicSumUp")
@PluginModule
ProgressProvider progressProvider
CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager()
ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
CustomField storyPointsField = customFieldManager.getCustomFieldObject("customfield_10106")
ProgressNumberWithStatusResult progress = progressProvider.getNumberProgress(
issue,
storyPointsField,
currentUser
)
return (double)progress.getResolved()
Calculate number of resolved issues for an issue and all of its children The ProgressResolutionResult
is a class that can be used to display the progress of issues. It contains the amount of issues as well as the number of resolved or unresolved issues.
Custom Script Field
Code
GROOVY
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.ApplicationUser
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import aptis.plugins.epicSumUp.api.ProgressProvider
import aptis.plugins.epicSumUp.api.model.ProgressResolutionResult
@WithPlugin("aptis.plugins.epicSumUp")
@PluginModule
ProgressProvider progressProvider
ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
ProgressResolutionResult progress = progressProvider.getProgressByResolution(
issue,
currentUser
)
return progress.getResolved()
Render the progress bar as SVG (Server Side) By using the ProgressBarRenderer
you can use our progress bars outside of the Jira system or where our client side rendered progress bars don’t work out of the box.
Custom Script Field
Code
GROOVY
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.ApplicationUser;
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import aptis.plugins.epicSumUp.api.ProgressProvider
import aptis.plugins.epicSumUp.api.ProgressBarRenderer
import aptis.plugins.epicSumUp.api.model.ProgressTimeResult
@WithPlugin("aptis.plugins.epicSumUp")
@PluginModule
ProgressProvider progressProvider
@PluginModule
ProgressBarRenderer progressBarRenderer
ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
ProgressTimeResult progress = progressProvider.getTimeProgress(
issue,
currentUser
)
return progressBarRenderer.renderAsSvg(progress, 200, 20)
Advanced examples Cost calculation base on time spent * cost factor + number fields Calculate costs for the issue and all of its children based on time spent and a custom field that contains additional costs.
Be aware that this script is not designed to run in structures with 1000+ child issues. Please contact our support when this is required.
Custom Script Field
Code
CODE
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.ApplicationUser;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.CustomFieldManager;
import com.atlassian.jira.issue.fields.CustomField;
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import aptis.plugins.epicSumUp.api.HierarchyProvider
@WithPlugin("aptis.plugins.epicSumUp")
@PluginModule
HierarchyProvider hierarchyProvider
CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager();
ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();
// define work costs factor ($ / hour)
Double timeCostPerHour = 80.5
// define customfield of type number that contains additional costs in $
CustomField costsField = customFieldManager.getCustomFieldObject("customfield_10525") //Number field that contains e.g. costs in $
if (costsField == null) {
throw new Exception("costs field does not exist")
}
List<Issue> issues = hierarchyProvider.getChildIssues(issue, currentUser);
issues.add(issue);
def value = issues.sum { issueItem ->
// Costs calculation
// timeSpent * timeCostPerHour + addionalCosts
Long timeSpent = issueItem.getTimeSpent() ?: 0
Double timeSpentInHours = timeSpent / 60 / 60
Double timeCost = timeSpentInHours * timeCostPerHour
Double cost = costsField.getValue(issueItem) ?: 0
cost + timeCostPerHour
}
return value + "\$"
Time lozenges optical warning This example will render different colored lozenge based on time spent and estimates
Lozenge Documentation
grey background: When there is less than 10m time spent
red background: When more than 1h is already spent over the original estimate
white background, red text: When more than 1h is remaining over the original estimate
green background: When no above condition is true
Example output:
Custom Script Field
Template
CODE
#if (!$value || $value.getTimeSpent() < 600)
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-default">
$jiraDurationUtils.getShortFormattedDuration($value.getTimeSpent())
</span>
#elseif ($value && $value.getTimeSpentOverEstimate() > 3600)
<span class="aui-lozenge aui-lozenge-error">
$jiraDurationUtils.getShortFormattedDuration($value.getTimeSpentOverEstimate())
</span>
#elseif ($value && $value.getRemainingTimeOverEstimate() > 3600)
<span class="aui-lozenge aui-lozenge-subtle aui-lozenge-error">
$jiraDurationUtils.getShortFormattedDuration($value.getRemainingTimeOverEstimate())
</span>
#else
<span class="aui-lozenge aui-lozenge-success">
$jiraDurationUtils.getShortFormattedDuration($value.getTimeSpent())
</span>
#end
Code
CODE
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.ApplicationUser;
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import aptis.plugins.epicSumUp.api.ProgressProvider
import aptis.plugins.epicSumUp.api.model.ProgressTimeResult
@WithPlugin("aptis.plugins.epicSumUp")
@PluginModule
ProgressProvider progressProvider
ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();
ProgressTimeResult progress = progressProvider.getTimeProgress(
issue,
currentUser
);
return progress
Searchable duration: Time spent over estimation Create a JQL searchable custom field that contains the time spent over the original estimate of the issue and all of its children.
It may cause performance issues as it will reindex all parent issues (Epic or Portfolio Hierarchy) when a child is updated. Only use with caution and reduce context (Project / IssueTypes) as much as possible.
Instead of a Custom Event Handler, you can use a custom scheduled job to re-index the relevant issues (epic) only at specific intervals.
Scriptrunner Documentation
Custom Script Field
Template
Duration (time-tracking)
Code
GROOVY
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.user.ApplicationUser;
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import aptis.plugins.epicSumUp.api.ProgressProvider
import aptis.plugins.epicSumUp.api.model.ProgressTimeResult
@WithPlugin("aptis.plugins.epicSumUp")
@PluginModule
ProgressProvider progressProvider
ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();
ProgressTimeResult progress = progressProvider.getTimeProgress(
issue,
currentUser
);
return progress.getTimeSpentOverEstimate()
Custom Listener
Events
issue updated
work logged on issue
issue worklog updated
issue worklog deleted
Script
CODE
import org.apache.log4j.Logger
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.index.IssueIndexManager
import com.atlassian.jira.user.ApplicationUser;
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import aptis.plugins.epicSumUp.api.HierarchyProvider
@WithPlugin("aptis.plugins.epicSumUp")
@PluginModule
HierarchyProvider hierarchyProvider
Issue issue = event.issue // event is an IssueEvent
ApplicationUser currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();
def issueIndexManager = ComponentAccessor.getComponent(IssueIndexManager)
Issue[] parentIssues = hierarchyProvider.getParents(issue, currentUser)
issueIndexManager.reIndexIssueObjects(parentIssues.collect())