Bob Swift's "Create on Transition" integration

Usecase

You have "Create on Transition" add-on by Bob Swift installed on your Jira. It is configured to create a copy of an issue during the transition. Issue also has a TGE grid with "gd.query" in grid configuration, that depends on custom field values that are not yet set on issue creation moment. Data in original issue is loaded correctly in original issue, but fails in copied issue.

Solution

With TGE API released in 1.18.0 you can perform grid reload from groovy code, i.e. from ScriptRunner post-function. ScriptRunner is the add-on allows to execute arbitrary groovy code on specific JIRA events. To use the suggested solution you have to install ScriptRunner add-on on your JIRA first.

More examples of TGE API usage you can find here: Java API: Groovy script examples.

You want to check the "Reload Grid" script. It performs grid reload and takes issue and TGE custom field ID as arguments. TGE custom field IDs can be got using another methods of API. The problem is to find cloned issue to pass it to the reload API call.

Fortunately, Bob Swift cares about his customers and allows to define links that will be applied to newly created issue in configuration of 'Create on transition' post-function. You can configure it to create a link to original issue of type, let's say, 'Cloners'. All the types of links configured can be viewed in Jira Configuration. Also you can configure "Create on Transition" to copy custom field values from original issue to a new one.

Read more about the configuration of the "Create on Transition" add-on: https://bobswift.atlassian.net/wiki/display/CSOT/How+to+link+issues+on+transition.

Ok, now the cloned issue is linked with the original with link of type "Cloners". So we can use this knowledge in another scripted post-function, that will be executed after the issue creation, caused by Bob's add-on post-function. It is possible to find all that links using Jira API in that script, find TGE custom field IDs, and perform a grid reload based on copied value of custom fields, TGE query relies on. Here is the script of post-function, that does the job (is't a bit modified "Reload Grid" script from TGE API examples):

Reload Grids
import com.atlassian.crowd.embedded.api.User import com.atlassian.jira.bc.issue.link.IssueLinkService import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.Issue import com.atlassian.jira.issue.IssueManager import com.atlassian.jira.issue.link.LinkCollection import com.atlassian.jira.security.JiraAuthenticationContext import com.atlassian.jira.user.ApplicationUser import com.atlassian.jira.user.ApplicationUsers import com.atlassian.plugin.PluginAccessor // save debug messages StringBuilder result = new StringBuilder(); // get an issue IssueManager issueManager = ComponentAccessor.getOSGiComponentInstanceOfType(IssueManager.class); Issue issue = issueManager.getIssueObject("TEST-1"); // find TGE custom fields PluginAccessor pluginAccessor = ComponentAccessor.getPluginAccessor(); Class tgeConfigManagerClass = pluginAccessor.getClassLoader().findClass("com.idalko.jira.plugins.igrid.api.config.grid.TGEGridConfigManager"); def tgeConfigManager = ComponentAccessor.getOSGiComponentInstanceOfType(tgeConfigManagerClass); List<Long> tgeCustomFieldIds = tgeConfigManager.getGridCustomFieldIds(); // get current user JiraAuthenticationContext jiraAuthenticationContext = ComponentAccessor.getOSGiComponentInstanceOfType(JiraAuthenticationContext.class); Object userObject = jiraAuthenticationContext.getLoggedInUser(); ApplicationUser applicationUser = userObject instanceof ApplicationUser ? (ApplicationUser) userObject : ApplicationUsers.from(userObject); User user = userObject instanceof ApplicationUser ? ((ApplicationUser) userObject).getDirectoryUser() : (User) userObject; // find cloned issues by link reference "Cloners" IssueLinkService issueLinkService = ComponentAccessor.getOSGiComponentInstanceOfType(IssueLinkService.class); IssueLinkService.IssueLinkResult issueLinks = issueLinkService.getIssueLinks(userObject, issue); LinkCollection linkCollection = issueLinks.getLinkCollection(); List<Issue> clonedIssues = linkCollection.getInwardIssues("Cloners"); result.append(clonedIssues.size() + " cloned issues found for issue " + issue.getKey() + "\n"); // reload grids Class dataManagerClass = pluginAccessor.getClassLoader().findClass("com.idalko.jira.plugins.igrid.api.data.TGEGridTableDataManager"); def tgeGridDataManager = ComponentAccessor.getOSGiComponentInstanceOfType(dataManagerClass); for (Issue clonedIssue : clonedIssues) { try { tgeGridDataManager.reloadGrids(clonedIssue.getId(), new HashSet<Long>(tgeCustomFieldIds), user); result.append("Grids " + tgeCustomFieldIds + " are reloaded on issue " + clonedIssue.getKey() + "!\n"); } catch (Exception e) { result.append("Grids cannot be reloaded: " + e.getMessage() + "\n"); } } println(result.toString()); return result.toString();

Note: script is compatible with JIRA 7 and later. To make it working on JIRA 6 you have to replace ApplicationUser class with User class.

Here is an another example, where grid rows have to be copied from the original issue to newly created.

Copy rows
import com.atlassian.crowd.embedded.api.User import com.atlassian.jira.bc.issue.link.IssueLinkService import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.CustomFieldManager import com.atlassian.jira.issue.Issue import com.atlassian.jira.issue.IssueManager import com.atlassian.jira.issue.fields.CustomField import com.atlassian.jira.issue.link.LinkCollection import com.atlassian.jira.security.JiraAuthenticationContext import com.atlassian.jira.user.ApplicationUser import com.atlassian.jira.user.ApplicationUsers import com.atlassian.plugin.PluginAccessor // save debug messages StringBuilder result = new StringBuilder(); // get an issue IssueManager issueManager = ComponentAccessor.getOSGiComponentInstanceOfType(IssueManager.class); Issue issue = issueManager.getIssueObject("TEST-1"); // find TGE custom field PluginAccessor pluginAccessor = ComponentAccessor.getPluginAccessor(); CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager(); CustomField tgeCustomField = customFieldManager.getCustomFieldObjectByName("TGE_TEST"); Long tgeCustomFieldId = tgeCustomField.getIdAsLong(); // get current user JiraAuthenticationContext jiraAuthenticationContext = ComponentAccessor.getOSGiComponentInstanceOfType(JiraAuthenticationContext.class); Object userObject = jiraAuthenticationContext.getLoggedInUser(); ApplicationUser applicationUser = userObject instanceof ApplicationUser ? (ApplicationUser) userObject : ApplicationUsers.from(userObject); User user = userObject instanceof ApplicationUser ? ((ApplicationUser) userObject).getDirectoryUser() : (User) userObject; // read grid data from the original issue Class dataManagerClass = pluginAccessor.getClassLoader().findClass("com.idalko.jira.plugins.igrid.api.data.TGEGridTableDataManager"); def tgeGridDataManager = ComponentAccessor.getOSGiComponentInstanceOfType(dataManagerClass); List<Map<String, Object>> gridData = null; try { def callResult = tgeGridDataManager.readGridData(issue.getId(), tgeCustomFieldId, null, null, 0, 1000, user); gridData = callResult.getValues(); result.append("Grid ID=" + tgeCustomFieldId + " content: " + gridData + "\n"); } catch (Exception e) { result.append("Grid ID=" + tgeCustomFieldId + " data cannot be retrieved: " + e.getMessage() + "\n"); } if (gridData != null) { // get the list of grid columns Class tgeConfigManagerClass = pluginAccessor.getClassLoader().findClass("com.idalko.jira.plugins.igrid.api.config.grid.TGEGridConfigManager"); def tgeConfigManager = ComponentAccessor.getOSGiComponentInstanceOfType(tgeConfigManagerClass); def tgeGrid = tgeConfigManager.getGridConfigOrNull(tgeCustomFieldId); def columnConfigs = tgeGrid.getColumnConfigs(); List<String> columnConfigNames = new LinkedList<String>(); for (def columnConfig : columnConfigs) { String columnConfigName = columnConfig.getConfigName(); columnConfigNames.add(columnConfigName); } result.append("Grid ID=" + tgeCustomFieldId + " has following columns: " + columnConfigNames + "\n"); // transform fetched data List<Map<String, Object>> gridDataToAdd = new LinkedList<Map<String, Object>>(); for (Map<String, Object> gridRow : gridData) { Map<String, Object> rowToAdd = new HashMap<String, Object>(); for (String columnName : columnConfigNames) { def columnValue = gridRow.get(columnName); if (columnValue instanceof Map) { columnValue = columnValue.get("value"); } rowToAdd.put(columnName, columnValue); } gridDataToAdd.add(rowToAdd); } result.append("Transformed data to add: " + gridDataToAdd + "\n"); // find cloned issues by link reference "Cloners" IssueLinkService issueLinkService = ComponentAccessor.getOSGiComponentInstanceOfType(IssueLinkService.class); IssueLinkService.IssueLinkResult issueLinks = issueLinkService.getIssueLinks(userObject, issue); LinkCollection linkCollection = issueLinks.getLinkCollection(); List<Issue> clonedIssues = linkCollection.getInwardIssues("Cloners"); result.append(clonedIssues.size() + " cloned issues found for issue " + issue.getKey() + "\n"); // add copied grid data to cloned issue for (Issue clonedIssue : clonedIssues) { try { tgeGridDataManager.addRows(clonedIssue.getId(), tgeCustomFieldId, gridDataToAdd, user); result.append("Grid rows are added to " + clonedIssue.getKey() + " issue.\n"); } catch (Exception e) { result.append("Grids rows cannot be added due to error: " + e.getMessage() + "!\n"); } } } println(result.toString()); return result.toString();