/
Use the table grid editor to track subtasks (and get notified about it)

Use the table grid editor to track subtasks (and get notified about it)

 

Introduction

 

Use TGE as a storage of simple sub-tasks and get email notification on change of the task assignee

 

 

This article details out how to setup a table grid to track 'sub' tasks, and be notified when something is assigned to yourself.  It is using Script Listeners (from ScriptRunner) to send out the notifications.

 



The table grid where the subtasks are created

 

 

The Notifications sent by the Script Listener

Setup the environment

 

  • Configure a grid to track tasks

  • Prepare a script to send out notifications

  • Configure a script listener to send out notifications

 

 

Configure a grid

This configuration makes use of such features of TGE as userlists, column hiding.

gd.columns=isummary,iassignee,previousiassignee,istatus,idue gd.tablename=actions gd.ds=jira col.isummary=Summary col.isummary.name=summary col.isummary.type=string col.isummary.required=true col.isummary.maxLength=128 col.isummary.width=400 col.iassignee=Assignee col.iassignee.type=userlist col.iassignee.required=true col.iassignee.autocomplete=true col.iassignee.width=100 col.previousiassignee=previousiassignee col.previousiassignee.type=string col.previousiassignee.hidden=true col.istatus=Status col.istatus.type=list col.istatus.list.size=2 col.istatus.name1=Open col.istatus.value1=O col.istatus.name2=Done col.istatus.value2=D col.istatus.width=60 col.istatus.defaultValue=O col.idue=Date due col.idue.type=date col.idue.defaultDate = +1w

 

Create the TGE Listener

Install latest ScriptRunner add-on, create a file "TgeListener.groovy" with the following content in the "$JIRA_HOME/scripts/com/idalko/scripts" directory.
 

TgeListener.groovy
import com.atlassian.crowd.embedded.api.User import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.config.properties.APKeys import com.atlassian.jira.config.properties.ApplicationProperties import com.atlassian.jira.event.issue.AbstractIssueEventListener import com.atlassian.jira.event.issue.IssueEvent import com.atlassian.jira.issue.CustomFieldManager import com.atlassian.jira.issue.Issue import com.atlassian.jira.issue.fields.CustomField import com.atlassian.jira.security.JiraAuthenticationContext import com.atlassian.jira.user.ApplicationUser import com.atlassian.jira.user.UserUtils import com.atlassian.mail.Email import com.atlassian.mail.queue.MailQueue import com.atlassian.mail.queue.SingleMailQueueItem import com.atlassian.plugin.PluginAccessor import org.apache.commons.lang3.ObjectUtils import org.apache.log4j.Logger import org.ofbiz.core.entity.GenericValue class TgeListener extends AbstractIssueEventListener { // configuration private static final String TGE_FIELD_NAME = "TGE"; private static final String TRACKED_USERLIST_COLUMNS_NAME = "iassignee"; private static final String PREVIOUS_USERLIST_COLUMNS_NAME = "previousiassignee"; private static final Logger LOG = Logger.getLogger("com.idalko.scripts"); private final CustomFieldManager customFieldManager; private final PluginAccessor pluginAccessor; private final JiraAuthenticationContext jiraAuthenticationContext; private final ApplicationProperties applicationProperties; private final MailQueue mailQueue; private final def tgeGridDataManager; public TgeListener() { customFieldManager = get(CustomFieldManager.class); pluginAccessor = get(PluginAccessor.class); jiraAuthenticationContext = get(JiraAuthenticationContext.class); Class dataManagerClass = pluginAccessor.getClassLoader().findClass("com.idalko.jira.plugins.igrid.api.data.TGEGridTableDataManager"); tgeGridDataManager = get(dataManagerClass) applicationProperties = get(ApplicationProperties.class); mailQueue = get(MailQueue.class); } private <T> T get(Class<T> clazz) { return ObjectUtils.firstNonNull( ComponentAccessor.getComponent(clazz), ComponentAccessor.getComponentOfType(clazz), ComponentAccessor.getOSGiComponentInstanceOfType(clazz) ); } @Override void issueUpdated(IssueEvent event) { Set<Long> updatedCustomFieldIds = getUpdatedCustomFieldsIds(event); LOG.debug("Updated custom fields IDs are: " + updatedCustomFieldIds); CustomField tgeCustomField = customFieldManager.getCustomFieldObjectByName(TGE_FIELD_NAME); Long tgeCustomFieldId = tgeCustomField.getIdAsLong(); LOG.debug("TGE custom fields ID is: " + tgeCustomFieldId); if (updatedCustomFieldIds.contains(tgeCustomFieldId)) { Issue issue = event.getIssue() processTgeFieldChange(issue, tgeCustomFieldId); } } private Set<Long> getUpdatedCustomFieldsIds(IssueEvent event) { List<GenericValue> changeItems = event.getChangeLog().getRelated("ChildChangeItem"); Set<Long> updatedCustomFields = new HashSet<Long>(); for (GenericValue changeItem : changeItems) { String changeType = changeItem.getString("fieldtype"); if (changeType.equalsIgnoreCase("custom")) { String customFieldName = changeItem.getString("field"); CustomField customFieldObject = customFieldManager.getCustomFieldObjectByName(customFieldName); Long customFieldId = customFieldObject.getIdAsLong() updatedCustomFields.add(customFieldId); } } return updatedCustomFields; } private void processTgeFieldChange(Issue issue, long tgeCustomFieldId) { User user = getCurrentUser(); Long issueId = issue.getId(); def result = tgeGridDataManager.readGridData(issueId, tgeCustomFieldId, null, null, 0, 10, user); List<Map<String, Object>> gridData = result.getValues(); for (Map<String, Object> row : gridData) { String selectedUserName = getSelectedUserNameFromRow(row); String previousUserName = getPreviousUserNameFromRow(row); if (selectedUserName != null && !selectedUserName.equals(previousUserName)) { sendEmailNotificationToUser(selectedUserName, issue); setPreviousUserEqualToSelected(row, issueId, tgeCustomFieldId, user); } } } private User getCurrentUser() { Object userObject = jiraAuthenticationContext.getLoggedInUser(); User user = userObject instanceof ApplicationUser ? ((ApplicationUser) userObject).getDirectoryUser() : (User) userObject; return user; } private String getSelectedUserNameFromRow(Map<String, Object> row) { Map<String, String> selectedUser = (Map<String, String>) row.get(TRACKED_USERLIST_COLUMNS_NAME) String selectedUserName = selectedUser.get("value"); return selectedUserName; } private String getPreviousUserNameFromRow(Map<String, Object> row) { return row.get(PREVIOUS_USERLIST_COLUMNS_NAME); } private void sendEmailNotificationToUser(String userName, Issue issue) { // prepare data User user = UserUtils.getUser(userName); String userEmail = user.getEmailAddress(); String baseUrl = applicationProperties.getString(APKeys.JIRA_BASEURL); String issueKey = issue.getKey(); String issueLink = baseUrl + "/browse/" + issueKey; String userDisplayName = user.getDisplayName(); // build email Email email = new Email(userEmail); email.setSubject("TGE notification"); email.setBody("Hi " + userDisplayName + "! A new task has been assigned to you in issue " + issueKey + "! Please visit <a href=\"" + issueLink + "\">" + issueKey + "</a>."); email.setMimeType("text/html"); // send it SingleMailQueueItem mailItem = new SingleMailQueueItem(email); mailQueue.addItem(mailItem); } private void setPreviousUserEqualToSelected(Map<String, Object> row, Long issueId, Long tgeCustomFieldId, User user) { List changes = new ArrayList(); // get row values Long rowId = (Long) row.get("id"); String selectedUserName = getSelectedUserNameFromRow(row); // prepare new values Map<String, Object> newRowValues = new HashMap<String, Object>(); newRowValues.put(PREVIOUS_USERLIST_COLUMNS_NAME, selectedUserName); // create changeset Class changeSetClass = pluginAccessor.getClassLoader().loadClass("com.idalko.jira.plugins.igrid.api.data.dto.TGEChangeSet"); def changeSet = changeSetClass.newInstance(); changeSet.setRowId(rowId); changeSet.setValues(newRowValues); changes.add(changeSet); // update row tgeGridDataManager.applyChanges(issueId, tgeCustomFieldId, changes, user); } }

Then Configure Scripted Listener

Usage

Create new rows in the grid, edit existing ones. On the change of the value of the "Assignee" column new Assignee will get a TGE email notification like this

Notification
Subject: TGE notification Body: Hi Samuel Stone! A new task has been assigned to you in issue TGE-1! Please visit http://seriouscopr.com/browse/TGE-1.