import org.apache.log4j.Logger; import org.apache.log4j.Level; import com.atlassian.jira.component.ComponentAccessor; import com.atlassian.jira.issue.ModifiedValue; import com.atlassian.jira.issue.util.DefaultIssueChangeHolder; import com.atlassian.jira.bc.issue.search.SearchService; import com.atlassian.jira.jql.parser.JqlQueryParser; import com.atlassian.jira.web.bean.PagerFilter; import com.atlassian.jira.bc.issue.IssueService; import com.atlassian.jira.issue.IssueManager; import com.atlassian.jira.event.type.EventDispatchOption; import com.atlassian.jira.issue.index.IssueIndexingService; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; // ------------------------------------------------------------------------------------ // ---------------- VALUES TO BE COMPLETED BEFORE EXECUTING THE SCRIPT ---------------- // ------------------------------------------------------------------------------------ // A. JQL returning issues to be processed def jqlQuery = "A_VALID_JQL_QUERY"; // B. Generate a log report to share with the support team if a problem occurs (Yes = true, No = false) boolean createLogReport = true; // C. Field mapping: // - 1st column: Connect field ID (source field) // - 2nd column: Jira Text field ID (target field) // - 3nd column: Corresponds to the value you want to copy into the Jira Text field, and depends on the Connect field type: // - If type is "Live Text", you can specify 'key' or 'template' (if not specified, 'template' will be copied by default) // - If type is "Live User", you can specify 'mail', 'key' or 'template' (if not specified, 'mail' will be copied by default) // - If type is "Snapshot", template will be copied // Warning: 3nd column is case sensitive so you must write 'template', 'key', or 'mail' in lowercase // Example : ['customfield_11111','customfield_22222','key'] def fieldsMapping = [ ['customfield_XXXXX','customfield_YYYYY','VALUE_TO_COPY'] ]; // ------------------------------------------------------------------------------------ // -------------------------------------- SCRIPT -------------------------------------- // ------------------------------------------------------------------------------------ FileWriter writer = null; try { log.setLevel(Level.INFO); if (createLogReport) { String dateTime = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String fileName = "elements_connect_log_" + dateTime + ".txt"; String filePath = System.getProperty("java.io.tmpdir") + File.separator + fileName; try { writer = new FileWriter(filePath, true); logAndWrite("SCRIPT - START - "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), writer); log.info("SCRIPT - File - Log report created at: " + filePath); } catch (IOException e) { log.error("SCRIPT - File - Error when creating the log report", e); } } // Jira Components def issueManager = ComponentAccessor.getIssueManager(); def customFieldManager = ComponentAccessor.getCustomFieldManager(); def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser); def searchService = ComponentAccessor.getComponent(SearchService); def issueService = ComponentAccessor.getIssueService(); def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser(); logAndWrite("SCRIPT - Script triggered by ${user}", writer); logAndWrite("--------------------------------------------", writer); // Generate the optimized JQL query def conditions = fieldsMapping.collect { "cf[${it[0].replace('customfield_', '')}] IS NOT EMPTY" }; def jqlQueryOptimized = jqlQuery + " AND (" + conditions.join(" OR ") + ")"; logAndWrite("SCRIPT - JQL Query => ${jqlQueryOptimized}", writer); // Execute the JQL query def results = searchService.search(user, jqlQueryParser.parseQuery(jqlQueryOptimized), PagerFilter.getUnlimitedFilter()); logAndWrite("SCRIPT - Total issues to be updated => ${results.total}", writer); logAndWrite("--------------------------------------------", writer); // Processing issues results.getResults().eachWithIndex { issue, index -> def changeHolder = new DefaultIssueChangeHolder(); def issueInputParameters = issueService.newIssueInputParameters(); def hasUpdates = false; logAndWrite("SCRIPT - ${issue.key} starting to be processed - "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), writer); logAndWrite("SCRIPT - ${issue.key} ---------------------", writer); //Processing fields fieldsMapping.each { fields -> def connectFieldId = fields[0]; def textFieldId = fields[1]; def valueToBeCopied = fields[2]; String fieldValue; def connectField = customFieldManager.getCustomFieldObject(connectFieldId); def textField = customFieldManager.getCustomFieldObject(textFieldId); if (connectField == null) { logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - source field does not exist => skipping.", writer); logAndWrite("SCRIPT - ${issue.key} ---------------------", writer); return; } def type = connectField.getCustomFieldType().getName() if (textField == null) { logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - target (${textFieldId}) does not exist => skipping.", writer); logAndWrite("SCRIPT - ${issue.key} ---------------------", writer); return; } logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - type: ${type}", writer); if (issue.getCustomFieldValue(connectField)) { if(type == "Elements Connect - Snapshot - Text" || type == "Elements Connect - Snapshot - Date" || type == "Elements Connect - Snapshot - Datetime") { logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - value to copy: type is Snapshot => template will be copied by default", writer); } else if(valueToBeCopied && (type == "Elements Connect - Live - Text" || type == "Elements Connect - Live - User")){ logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - value to copy: ${valueToBeCopied}", writer); } else{ if(type == "Elements Connect - Live - Text") logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - value to copy: undefined => template will be copied by default", writer); else if(type == "Elements Connect - Live - User") logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - value to copy: undefined => mail will be copied by default", writer); } if (type == "Elements Connect - Live - Text") { if(valueToBeCopied == 'key') { fieldValue = returnKey(connectFieldId, issue.key, ComponentAccessor, writer); } else { fieldValue = returnTemplate(connectFieldId, issue.key, ComponentAccessor, writer); } } else if (type == "Elements Connect - Live - User") { if(valueToBeCopied == 'key') { fieldValue = returnKey(connectFieldId, issue.key, ComponentAccessor, writer); } else if(valueToBeCopied == 'template') { fieldValue = returnTemplate(connectFieldId, issue.key, ComponentAccessor, writer); } else { fieldValue = returnMail(connectFieldId, issue.key, ComponentAccessor, writer); } } else if (type == "Elements Connect - Snapshot - Text" || type == "Elements Connect - Snapshot - Datetime") { def storedValue = issue.getCustomFieldValue(connectField) logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - value stored in Jira: '${storedValue}'", writer); fieldValue = storedValue; } else if (type == "Elements Connect - Snapshot - Date") { def storedValue = issue.getCustomFieldValue(connectField) logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - value stored in Jira: '${storedValue}'", writer); def dateFormat = new SimpleDateFormat("yyyy-MM-dd") storedValue = dateFormat.format(storedValue) logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - value formatted to: '${storedValue}'", writer); fieldValue = storedValue; } else { logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - type is not valid => skipping.", writer); logAndWrite("SCRIPT - ${issue.key} ---------------------", writer); return; } if (fieldValue?.trim()) { issueInputParameters.addCustomFieldValue(textFieldId, fieldValue); hasUpdates = true; logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - value has been copied to ${textFieldId}", writer); } else { logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - fieldValue is empty or undefined, skipping update for ${textFieldId}", writer); logAndWrite("SCRIPT - ${issue.key} ---------------------", writer); return; } } else { logAndWrite("SCRIPT - ${issue.key} - ${connectFieldId} - field is empty => skipping", writer); logAndWrite("SCRIPT - ${issue.key} ---------------------", writer); return; } logAndWrite("SCRIPT - ${issue.key} ---------------------", writer); } if (hasUpdates) { processIssueUpdates(issue, issueInputParameters, user, issueService, issueManager, customFieldManager, fieldsMapping, writer, index, results.total) } logAndWrite("SCRIPT --------------------------------", writer); } logAndWrite("SCRIPT - END - "+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), writer); if (writer != null) { try { writer.close(); } catch (IOException e) { log.error("SCRIPT - File - Error when closing the file", e); } } } catch (Exception e) { logAndWrite("ERROR - " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " - " + e.getMessage(), writer); log.error("An unexpected error occurred", e); } // ------------------------------------------------------------------------------------ // ------------------------------------ FUNCTIONS ------------------------------------- // ------------------------------------------------------------------------------------ String returnKey(def fieldId, def issueKey, def ComponentAccessor, FileWriter writer) { def pluginAccessor = ComponentAccessor.getPluginAccessor(); def plugin = pluginAccessor.getPlugin("com.valiantys.jira.plugins.SQLFeed"); def serviceClass = plugin.getClassLoader().loadClass("com.valiantys.nfeed.api.IFieldValueService"); def fieldValueService = ComponentAccessor.getOSGiComponentInstanceOfType(serviceClass); def fieldValues = fieldValueService.getFieldValues(issueKey, fieldId); def fieldValue = ""; if (fieldValues instanceof List) { fieldValue = fieldValues.join(", "); } else { fieldValue = fieldValues.toString(); } logAndWrite("SCRIPT - ${issueKey} - ${fieldId} - returnKey function returns '${fieldValue}'", writer); return fieldValue; } String returnTemplate(def fieldId, def issueKey, def ComponentAccessor, FileWriter writer) { def pluginAccessor = ComponentAccessor.getPluginAccessor(); def plugin = pluginAccessor.getPlugin("com.valiantys.jira.plugins.SQLFeed"); def serviceClass = plugin.getClassLoader().loadClass("com.valiantys.nfeed.api.IFieldDisplayService"); def fieldDisplayService = ComponentAccessor.getOSGiComponentInstanceOfType(serviceClass); def displayResult = fieldDisplayService.getDisplayResult(issueKey, fieldId); def fieldValue = displayResult?.getDisplay()?.replace('
', ', ') ?: ""; logAndWrite("SCRIPT - ${issueKey} - ${fieldId} - returnTemplate function returns '${fieldValue}'", writer); return fieldValue; } String returnMail(def fieldId, def issueKey, def ComponentAccessor, FileWriter writer) { def pluginAccessor = ComponentAccessor.getPluginAccessor(); def plugin = pluginAccessor.getPlugin("com.valiantys.jira.plugins.SQLFeed"); def serviceClass = plugin.getClassLoader().loadClass("com.valiantys.nfeed.api.IFieldValueService"); def fieldValueService = ComponentAccessor.getOSGiComponentInstanceOfType(serviceClass); def fieldValues = fieldValueService.getFieldValues(issueKey, fieldId); def userManager = ComponentAccessor.getUserManager(); def emails = []; if (fieldValues instanceof List) { fieldValues.each { userObj -> def username = userObj.toString().replaceAll(/\(.*\)/, "").trim(); def matcher = (userObj.toString() =~ /\((.*?)\)/); def userKey = matcher.find() ? matcher.group(1) : null; def user = userManager.getUserByName(username); if (!user && userKey) { user = userManager.getUserByKey(userKey); } if (user) { emails.add(user.getEmailAddress()); } else { logAndWrite("SCRIPT - ${issueKey} - ${fieldId} - User '${username}' or key '${userKey}' not found", writer); } } } else { def userObj = fieldValues.toString(); def username = userObj.replaceAll(/\(.*\)/, "").trim(); def matcher = (userObj =~ /\((.*?)\)/); def userKey = matcher.find() ? matcher.group(1) : null; def user = userManager.getUserByName(username); if (!user && userKey) { user = userManager.getUserByKey(userKey); } if (user) { emails.add(user.getEmailAddress()); } else { logAndWrite("SCRIPT - ${issueKey} - ${fieldId} - User '${username}' or key '${userKey}' not found", writer); } } def emailString = emails.join(", "); logAndWrite("SCRIPT - ${issueKey} - ${fieldId} - returnMail function returns '${emailString}'", writer); return emailString; } void logAndWrite(String message, FileWriter writer) { log.info(message); if (writer != null) { try { writer.write(message + "\n"); writer.flush(); } catch (IOException e) { log.error("SCRIPT - File - Error when writing in the file", e); } } } def processIssueUpdates(issue, issueInputParameters, user, issueService, issueManager, customFieldManager, fieldsMapping, writer, index, total) { /* def validateUpdateResult = issueService.validateUpdate(user, issue.id, issueInputParameters) if (!validateUpdateResult.isValid()) { logAndWrite("SCRIPT - Issue ${issue.key} update failed: ${validateUpdateResult.errorCollection}", writer) return } */ def mutableIssue = issueManager.getIssueObject(issue.id) def issueIndexingService = ComponentAccessor.getComponent(IssueIndexingService) def changeHolder = new DefaultIssueChangeHolder() fieldsMapping.each { fields -> def (connectFieldId, textFieldId) = fields def textField = customFieldManager.getCustomFieldObject(textFieldId) if (textField == null) { return } def newValue = issueInputParameters.getCustomFieldValue(textFieldId) def oldValue = mutableIssue.getCustomFieldValue(textField) if (newValue) { newValue = (newValue instanceof String[]) ? newValue[0] : newValue textField.updateValue(null, mutableIssue, new ModifiedValue(oldValue, newValue), changeHolder) } } issueManager.updateIssue(user, mutableIssue, EventDispatchOption.DO_NOT_DISPATCH, false) issueIndexingService.reIndex(mutableIssue) logAndWrite("SCRIPT - ${issue.key} has been updated (${index + 1} of ${total})", writer) }