Confluence has been updated to version 6.15.9

Page tree
Skip to end of metadata
Go to start of metadata


Problem

You have a table grid with the data that has to be validated on issue transition.

Solution

Since Table Grid Editor does not yet support specifying of the custom validation rules, it can be done now using Java API (available since TGE v.1.18.0) and ScriptRunner. We will implement scripted validator where check if grid data matches our requirements.

Environment

  • Install the Script Runner add-on from the Atlassian marketplace
  • Create a workflow with a transition 'All done'
    • This transition should have a dialog box containing the grid

  • Add a validator function of type 'Script Validator' and choose 'Custom Script validator'
  • Point the script file parameter to the script location. 
    • you can use relative paths but this goes beyond the scope of this faq.
    • using a script file instead of an inline script allows you to easily change the content, without editing the workflow.

Script

We have two Java API calls that can be usefull for validation of the grid data during the transition: Grid Row Count in Edit Mode and Read Grid Data in Edit Mode.

See examples of usage of the Java API here: Java API: Groovy script exampleshttps://wiki.idalko.com/docs/overview.action.

Let's suppose, that you don't want to allow empty grids. The validator script cab be built around the Grid Row Count in Edit Mode call.

Validate empty grids
/**
 * Since TGE 1.19 only! Can be used only as scripted validator of issue transition.
 * Cannot be run from the Script Console.
 */
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.security.JiraAuthenticationContext
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.plugin.PluginAccessor
import com.opensymphony.workflow.InvalidInputException

// find TGE custom field
String tgeCustomFieldName = "TGE_TEST";
CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager();
CustomField tgeCustomField = customFieldManager.getCustomFieldObjectByName(tgeCustomFieldName);
String tgeCustomFieldId = tgeCustomField.getId();

// get user
PluginAccessor pluginAccessor = ComponentAccessor.getPluginAccessor();
JiraAuthenticationContext jiraAuthenticationContext = ComponentAccessor.getOSGiComponentInstanceOfType(JiraAuthenticationContext.class);
Object userObject = jiraAuthenticationContext.getLoggedInUser();
User user = userObject instanceof ApplicationUser ? ((ApplicationUser) userObject).getDirectoryUser() : (User) userObject;

// get row count
Class dataManagerClass = pluginAccessor.getClassLoader().findClass("com.idalko.jira.plugins.igrid.api.data.TGEGridTableDataManager");
def tgeGridDataManager = ComponentAccessor.getOSGiComponentInstanceOfType(dataManagerClass);
Integer rowCount = 0;
StringBuilder result = new StringBuilder();
try {
    rowCount = tgeGridDataManager.getRowCountInEditMode(issue, tgeCustomField, user);
    result.append("TGE row count: " + rowCount);
} catch (Exception e) {
    result.append("Grid ID=" + tgeCustomFieldId + " row count for issue ID=" + issue.getId() + " cannot be retrieved: " + e.getMessage() + "\n");
}

// check if the grid is empty
if (rowCount == 0) {
    // throw an exception with the field ID and an error message for TGE field
    // it will prevent transition with the noted error, user will stay on the transition screen
    invalidInputException = new InvalidInputException(tgeCustomFieldId, "Add at least one record to TGE grid to proceed");
    throw invalidInputException;
}

println(result.toString());
return result.toString();

If case if you have more specific requirements to the data of the specific columns, use Read Grid Data in Edit Mode call like the following script.

Validate grid column values
/**
 * Since TGE 1.19 only! Can be used only as scripted validator of issue transition.
 * Cannot be run from the Script Console.
 */
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.security.JiraAuthenticationContext
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.plugin.PluginAccessor
import com.opensymphony.workflow.InvalidInputException

// find the TGE custom field
String tgeCustomFieldName = "TGE_TEST";
CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager();
CustomField tgeCustomField = customFieldManager.getCustomFieldObjectByName(tgeCustomFieldName);
Long tgeCustomFieldId = tgeCustomField.getIdAsLong();

// get user
PluginAccessor pluginAccessor = ComponentAccessor.getPluginAccessor();
JiraAuthenticationContext jiraAuthenticationContext = ComponentAccessor.getOSGiComponentInstanceOfType(JiraAuthenticationContext.class);
Object userObject = jiraAuthenticationContext.getLoggedInUser();
User user = userObject instanceof ApplicationUser ? ((ApplicationUser) userObject).getDirectoryUser() : (User) userObject;

// read grid data
Class dataManagerClass = pluginAccessor.getClassLoader().findClass("com.idalko.jira.plugins.igrid.api.data.TGEGridTableDataManager");
def tgeGridDataManager = ComponentAccessor.getOSGiComponentInstanceOfType(dataManagerClass);
List<Map<String, Object>> gridDataList = new ArrayList<Map<String, Object>>();
StringBuilder result = new StringBuilder();
try {
    def gridData = tgeGridDataManager.readGridDataInEditMode(issue.getId(), tgeCustomFieldId, null, null, 0, 10, user);
    gridDataList = gridData.getValues();
    result.append("Grid ID=" + tgeCustomFieldId + " data in edit mode: " + gridDataList);
} catch (Exception e) {
    result.append("Grid ID=" + tgeCustomFieldId + " data in edit mode cannot be retrieved: " + e.getMessage() + "\n");
}

// check if grid is empty
if (gridDataList.isEmpty()) {
    // throw an exception with the field ID and an error message for TGE field
    // it will prevent transition with the noted error, user will stay on the transition screen
    invalidInputException = new InvalidInputException(tgeCustomFieldId, "Add at least one record to TGE grid to proceed")
    throw invalidInputException;
}

// check if any row has an 'Open' status selected
for (int rowNumber = 0; rowNumber < gridDataList.size(); rowNumber++) {
    Map<String, Object> rowData = gridDataList.get(rowNumber);
    Map<String, Object> status = rowData.get(
            "istatus" // name of the column from TGE configuration
    );
    String statusName = status.get(
            "name" // pre-defined property for 'list', 'userlist', 'radio' column types
    );
    if ("Open" // name of the status I want to forbid
            .equals(statusName)) {
        // throw an exception with the field ID and an error message for TGE field
        // it will prevent transition with the noted error, user will stay on the transition screen
        invalidInputException = new InvalidInputException(tgeCustomFieldId, "Row #" + (rowNumber + 1) + " of TGE grid has an 'Open' status selected");
        throw invalidInputException;
    }
}

println(result.toString());
return result.toString();