Snippets
Created by
Francis Martens
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 | package com.onresolve.scriptrunner.canned.jira.workflow.postfunctions
import com.atlassian.core.ofbiz.CoreFactory
import com.atlassian.crowd.embedded.api.Group
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.IssueEvent
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.RendererManager
import com.atlassian.jira.issue.attachment.Attachment
import com.atlassian.jira.issue.changehistory.ChangeHistoryManager
import com.atlassian.jira.issue.customfields.impl.MultiGroupCFType
import com.atlassian.jira.issue.customfields.impl.MultiUserCFType
import com.atlassian.jira.issue.customfields.impl.UserCFType
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.history.ChangeItemBean
import com.atlassian.jira.security.groups.GroupManager
import com.atlassian.jira.security.roles.ProjectRole
import com.atlassian.jira.security.roles.ProjectRoleManager
import com.atlassian.jira.util.AttachmentUtils
import com.atlassian.mail.Email
import com.atlassian.mail.MailException
import com.atlassian.mail.MailFactory
import com.atlassian.mail.queue.SingleMailQueueItem
import com.onresolve.scriptrunner.canned.CannedScript
import com.onresolve.scriptrunner.canned.jira.utils.ConditionUtils
import com.onresolve.scriptrunner.canned.util.BuiltinScriptErrors
import com.onresolve.scriptrunner.canned.util.SimpleBuiltinScriptErrors
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl
import com.onresolve.scriptrunner.runner.customisers.ScriptListener
import groovy.text.GStringTemplateEngine
import groovy.xml.MarkupBuilder
import org.codehaus.groovy.runtime.StackTraceUtils
import org.ofbiz.core.entity.GenericValue
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import javax.activation.DataHandler
import javax.activation.FileDataSource
import javax.mail.BodyPart
import javax.mail.Multipart
import javax.mail.internet.AddressException
import javax.mail.internet.InternetAddress
import javax.mail.internet.MimeBodyPart
import javax.mail.internet.MimeMultipart
import javax.script.ScriptEngine
import java.util.regex.Matcher
@ScriptListener
class SendCustomEmailUniqueAttachment implements CannedScript{
public static String FIELD_PREVIEW_ISSUE = "FIELD_PREVIEW_ISSUE"
public static String FIELD_EMAIL_TEMPLATE = "FIELD_EMAIL_TEMPLATE"
public static String FIELD_EMAIL_FORMAT = "FIELD_EMAIL_FORMAT"
public static String FIELD_TO_ADDRESSES = "FIELD_TO_ADDRESSES"
public static String FIELD_TO_USER_FIELDS = "FIELD_TO_USER_FIELDS"
public static String FIELD_CC_ADDRESSES = "FIELD_CC_ADDRESSES"
public static String FIELD_CC_USER_FIELDS = "FIELD_CC_USER_FIELDS"
public static String FIELD_EMAIL_SUBJECT_TEMPLATE = "FIELD_EMAIL_SUBJECT_TEMPLATE"
public static String FIELD_INCLUDE_ATTACHMENTS = "FIELD_INCLUDE_ATTACHMENTS"
public static String FIELD_FROM = "FIELD_FROM"
public static String FIELD_INCLUDE_ATTACHMENTS_NONE = "FIELD_INCLUDE_ATTACHMENTS_NONE"
public static String FIELD_INCLUDE_ATTACHMENTS_NEW = "FIELD_INCLUDE_ATTACHMENTS_NEW"
public static String FIELD_INCLUDE_ATTACHMENTS_ALL = "FIELD_INCLUDE_ATTACHMENTS_ALL"
public static String FIELD_INCLUDE_ATTACHMENTS_CUSTOM = "FIELD_INCLUDE_ATTACHMENTS_CUSTOM"
public static String FIELD_INCLUDE_ATTACHMENTS_CALLBACK = "FIELD_INCLUDE_ATTACHMENTS_CALLBACK"
private ChangeHistoryManager changeHistoryManager = ComponentAccessor.getChangeHistoryManager();
def issueManager = ComponentAccessor.getIssueManager()
def watcherManager = ComponentAccessor.getWatcherManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def projectRoleManager = ComponentAccessor.getComponent(ProjectRoleManager.class)
def groupManager = ComponentAccessor.getComponent(GroupManager.class)
def userUtil = ComponentAccessor.getUserUtil()
def mailServerManager = ComponentAccessor.getMailServerManager()
def mailServer = mailServerManager.getDefaultSMTPMailServer()
Logger log = LoggerFactory.getLogger(SendCustomEmailUniqueAttachment.class)
String getName() {
"Send a custom email but with unique attachments"
}
public String getHelpUrl() {
"https://www.idalko.com"
}
String getDescription() {
"Send an email based on the provided template if conditions are met, but only unique attachments"
}
List getCategories() {
["Function","ADMIN", "Listener"]
}
List getParameters(Map params) {
def toFieldDesc = """Who to send the email to - valid issue fields such as assignee, reporter, projectLead,
componentLead, watchers, user or user group custom fields,<br> or custom fields holding valid
email address, eg <i>customfield_12345</i>,<br>or role:<i>Rolename</i>, e.g role:Developers,
or group:<i>Groupname</i>. Use space to delimit."""
def outputs = [
ConditionUtils.getConditionParameter(),
[
Name:FIELD_EMAIL_TEMPLATE,
Label:"Email template",
Description:"""Write a template. Can be plain text or use the
<a href=\"http://docs.codehaus.org/display/GROOVY/Groovy+Templates#GroovyTemplates-GStringTemplateEngine\">GStringTemplateEngine</a>.
See wiki for examples.""",
Type:"textarea",
cssClass: "CodeMirror CodeMirrorSmall",
],
[
Name:FIELD_EMAIL_SUBJECT_TEMPLATE,
Label:"Subject template",
Description:"""Subject template. Can be plain text or use the
<a href=\"http://docs.codehaus.org/display/GROOVY/Groovy+Templates#GroovyTemplates-GStringTemplateEngine\">GStringTemplateEngine</a>.
See wiki for examples or click below.""",
Examples: [
"""Issue XYZ-1 requires your approval""" : "Issue \$issue requires your approval"
],
Type:"textarea",
cssClass: "CodeMirror CodeMirrorSmall",
],
[
Name:FIELD_EMAIL_FORMAT,
Label:"Email format",
Description:"Whether to send as plain text or HTML.",
Type:"list",
Values: [TEXT:'Plain text', HTML:'HTML'],
],
[
Name:FIELD_TO_ADDRESSES,
Label:"To addresses",
Description:" Who to send the email to. Use commas/space to delimit.",
],
[
Name:FIELD_TO_USER_FIELDS,
Label:"To issue fields",
Description: toFieldDesc,
],
[
Name:FIELD_CC_ADDRESSES,
Label:"CC addresses",
Description:" Who to include in CC. Use commas/space to delimit.",
],
[
Name:FIELD_CC_USER_FIELDS,
Label:"CC issue fields",
Description: toFieldDesc.replaceAll(" send ", " CC "),
],
[
Name:FIELD_INCLUDE_ATTACHMENTS,
Label:"Include attachments",
Type:"radio",
Values: [
(FIELD_INCLUDE_ATTACHMENTS_NONE): "None",
(FIELD_INCLUDE_ATTACHMENTS_NEW): "New",
(FIELD_INCLUDE_ATTACHMENTS_ALL): "All",
(FIELD_INCLUDE_ATTACHMENTS_CUSTOM): "Custom",
],
Description: """Include the issue attachments in the email. You can specify none, or only attachments
that were added in the transition pertaining to this event, or all attachments."""
],
[
Name: FIELD_INCLUDE_ATTACHMENTS_CALLBACK,
Label: "Custom attachment callback",
Description: "Enter a closure which will be called with each Attachment object. Only relevant if you " +
"choose 'Custom' above.",
Type:"textarea",
cssClass: "CodeMirror CodeMirrorSmall",
Examples: [
"Include only PDFs" : "{a -> a.filename.toLowerCase().endsWith('.pdf')}",
"Include only PDFs added this transition" : "{a -> a.isNew() && a.filename.toLowerCase().endsWith('.pdf')}",
"Only files less than 1 Mb" : "{a -> a.filesize < 1024**2}",
]
],
[
Name:FIELD_FROM,
Label:"From email address",
Description:""" What email address to send the mail from, eg jamie@example.com.
Leave blank for default (<i>${mailServer?.getDefaultFrom()}</i>).""",
],
[
Name:FIELD_PREVIEW_ISSUE,
Label:"Preview Issue Key",
Description:"""Issue key for previewing what the mail will look like.
ONLY used when previewing from the Admin section""",
],
]
outputs
}
BuiltinScriptErrors doValidate(Map params, boolean forPreview) {
def errorCollection = new SimpleBuiltinScriptErrors()
String prvwIssueKey = params[FIELD_PREVIEW_ISSUE] as String
if (! mailServerManager.getDefaultSMTPMailServer()) {
errorCollection.addErrorMessage("Cannot send mail as no outgoing mail set up.")
}
if (forPreview) {
if (! issueManager.getIssueObject(prvwIssueKey as String)) {
errorCollection.addError(FIELD_PREVIEW_ISSUE, "This issue doesn't exist.")
return errorCollection
}
String cond = params[ConditionUtils.FIELD_CONDITION] as String
if (cond) {
try {
MutableIssue issue = issueManager.getIssueObject(prvwIssueKey)
ConditionUtils.processCondition(cond, issue, true,
[event: new IssueEvent(issue, [:] , null, 1)])
/*
{
def getChangeLog() {
[getRelated : {[]}]
}
})
*/
}
catch (Exception e) {
errorCollection.addError(ConditionUtils.FIELD_CONDITION, e.getMessage())
log.debug(e.getMessage())
}
}
}
if (! params[FIELD_EMAIL_TEMPLATE]) {
errorCollection.addError(FIELD_EMAIL_TEMPLATE, "You must provide a template.")
}
if (! params[FIELD_EMAIL_SUBJECT_TEMPLATE]) {
errorCollection.addError(FIELD_EMAIL_SUBJECT_TEMPLATE, "You must provide a subject.")
}
if (! (params[FIELD_TO_ADDRESSES] || params[FIELD_TO_USER_FIELDS])) {
errorCollection.addErrorMessage ("You must provide either fields or addresses to send emails to.")
}
// todo: validation of "field" addresses, to and cc
[FIELD_TO_ADDRESSES, FIELD_CC_ADDRESSES].each {fieldParam ->
if (params[fieldParam]) {
def List invalid = getTextAddresses(params[fieldParam] as String).asList().findAll {String a ->
! validateEmail(a)
}
if (invalid)
errorCollection.addError(fieldParam, "These don't look like valid email addresses: " + invalid.join(", "))
}
}
if (params[FIELD_FROM]) {
if (! validateEmail(params[FIELD_FROM] as String)) {
errorCollection.addError(FIELD_FROM, "Email is not valid: ${params[FIELD_FROM]}")
}
}
if ((params[FIELD_INCLUDE_ATTACHMENTS] == FIELD_INCLUDE_ATTACHMENTS_CUSTOM) && ! params[FIELD_INCLUDE_ATTACHMENTS_CALLBACK]) {
errorCollection.addError(FIELD_INCLUDE_ATTACHMENTS_CALLBACK, "You must provide a callback if you select Custom.")
}
errorCollection
}
private boolean validateEmail (String email) {
// don't use EmailValidator.getInstance().isValid, not worth the trouble of importing the classes
try {
new InternetAddress(email).validate()
}
catch (AddressException ae) {
return false
}
true
}
public Email constructMail(Map params) {
MutableIssue issue = params['issue'] as MutableIssue
// preview mode
if (!issue) {
issue = issueManager.getIssueObject(params[FIELD_PREVIEW_ISSUE] as String)
}
// do validation again
String emailFormat = params[FIELD_EMAIL_FORMAT]
log.debug("emailFormat: $emailFormat")
log.debug("Preparing to send the mail");
def template = mergeEmailTemplateBody(params)
def body = template.toString()
// no space when using separator: http://stackoverflow.com/a/12120203/1018918
String toRecipientList = getAllToAddresses(issue,params).join(',');
log.debug("To Recipientlist = ${toRecipientList}");
def ccRecipientList = getAllCCAddresses(issue,params).join(',');
log.debug("CC Recipientlist = ${ccRecipientList}");
def email = new Email(toRecipientList, ccRecipientList, "");
// Now the message body.
addAttachmentsToMail(params, issue, email)
email.setFrom(params[FIELD_FROM] as String ?: mailServer.getDefaultFrom())
email.setSubject(mergeEmailTemplateSubject(params).toString())
email.setMimeType(emailFormat == "HTML" ? "text/html" : "text/plain")
email.setBody(body)
return email
}
public void sendMail(Map params) {
def email = constructMail(params)
if (! mailServer || MailFactory.isSendingDisabled()) {
log.warn ("No mail server or sending disabled.")
return
}
try {
log.debug("Sending mail to ${email.getTo()}")
log.debug("with body ${email.getBody()}")
log.debug("from template ${params[FIELD_EMAIL_TEMPLATE]}")
SingleMailQueueItem item = new SingleMailQueueItem(email);
ComponentAccessor.getMailQueue().addItem(item);
}
catch (MailException e) {
log.warn("Error sending email", e)
} catch(Throwable e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
StackTraceUtils.printSanitizedStackTrace(e, pw)
log.error(sw.toString())
}
}
Map doScript(Map params) {
MutableIssue issue = params['issue'] as MutableIssue
// preview mode
if (!issue) {
issue = issueManager.getIssueObject(params[FIELD_PREVIEW_ISSUE] as String)
}
Boolean doIt = ConditionUtils.processCondition(params[ConditionUtils.FIELD_CONDITION] as String, issue, false, params)
if (! doIt) {
return [:]
}
Thread executorThread = new Thread(new Runnable() {
void run() {
Thread.sleep(500)
sendMail(params)
}
})
// run in separate thread so mail handler has a chance to assemble all attachments
// https://jamieechlin.atlassian.net/browse/GRV-284
executorThread.start()
[:]
}
private def addAttachmentsToMail(Map<String,String> params, MutableIssue issue, Email email) {
if (params[FIELD_INCLUDE_ATTACHMENTS] && params[FIELD_INCLUDE_ATTACHMENTS] != FIELD_INCLUDE_ATTACHMENTS_NONE) {
Multipart mp = new MimeMultipart("mixed");
List<Long> newAttachmentIds = []
if (params["event"]) { // listener
List changeItems = params["event"].getChangeLog()?.getRelated("ChildChangeItem")
log.debug("changeItems: $changeItems")
changeItems.each {GenericValue gv ->
if (gv["field"] == "Attachment" && gv["newvalue"]) {
newAttachmentIds.add(gv["newvalue"] as Long)
}
}
}
else if (params["transientVars"]) {
List changeItems = params["transientVars"]["changeItems"] as List
changeItems.each {ChangeItemBean cib ->
if (cib.getField() == "Attachment" && cib.getTo()) {
newAttachmentIds.add(cib.getTo() as Long)
}
}
}
Map<String,Attachment> uniqueAttachment = [:]
for (Attachment att:issue.getAttachments()) {
if (uniqueAttachment[att.filename]) {
Attachment otherAttachment = uniqueAttachment[att.filename]
if (otherAttachment.created <= att.created) {
uniqueAttachment.put(att.filename, att)
}
} else {
uniqueAttachment.put(att.filename, att)
}
}
List<Long> attachmentIds = []
for (Attachment att: uniqueAttachment.values()) {
attachmentIds.add(att.id);
}
attachmentIds.each {attachmentId ->
if (params[FIELD_INCLUDE_ATTACHMENTS] == FIELD_INCLUDE_ATTACHMENTS_NEW) {
if (! newAttachmentIds.contains(attachmentId)) {
return
}
}
def attachment = issue.attachments.find {attachment ->
attachment.id == attachmentId
}
if (params[FIELD_INCLUDE_ATTACHMENTS] == FIELD_INCLUDE_ATTACHMENTS_CUSTOM) {
def gse = getGroovyScriptEngine()
try {
def callback = gse.eval(params[FIELD_INCLUDE_ATTACHMENTS_CALLBACK]) as Closure
// todo: isNew method - see previous versions for ideas
def mailAttachment = new MailAttachment(attachment)
mailAttachment.setIsNew(newAttachmentIds.contains(attachment.id))
def result = callback.call(mailAttachment)
// Attachment.metaClass.
log.debug "callback result for ${attachment.filename}: " + (result as Boolean)
if (! result) {
return
}
} catch (e) {
log.error("Failed to evaluate closure for adding attachment to email", e)
return
}
}
File attFile = AttachmentUtils.getAttachmentFile(attachment)
BodyPart attPart = new MimeBodyPart()
FileDataSource attFds = new FileDataSource(attFile)
attPart.setDataHandler(new DataHandler(attFds))
attPart.setFileName(attachment.filename)
log.debug("Attaching ${attachment.filename} to mail")
mp.addBodyPart(attPart)
}
email.setMultipart(mp)
}
}
static Set getTextAddresses(String toConfig) {
Set addresses = new HashSet()
if (! toConfig) {
return addresses
}
if (toConfig) {
for (String f in toConfig.split(/[\s,;]+/)) {
addresses.add(f)
}
}
addresses
}
List getAllToAddresses(Issue issue, Map params) {
String toUserFields = params[FIELD_TO_USER_FIELDS]
Set addresses = getAddressesFromFields(issue, toUserFields)
addresses.addAll(getTextAddresses(params[FIELD_TO_ADDRESSES] as String))
addresses.toList()
}
List getAllCCAddresses(Issue issue, Map params) {
String ccUserFields = params[FIELD_CC_USER_FIELDS]
Set addresses = getAddressesFromFields(issue, ccUserFields)
addresses.addAll(getTextAddresses(params[FIELD_CC_ADDRESSES] as String))
addresses.toList()
}
Set getAddressesFromFields(Issue issue, String toConfig) {
Set addresses = new HashSet()
if (! toConfig) {
return addresses
}
// for testing: "reporter,assignee, watchers, customfield_10020, customfield_10040, customfield_10041, customfield_10042, customfield_10043, group:"a b""
String patStr = /([^\s]*"[^"]*")|([^\s"']+)/
Matcher matcher = toConfig =~ patStr
log.debug("toConfig: \"$toConfig\"")
if (matcher.getCount() == 0) {
return []
}
for (int i in 0..matcher.getCount()-1) {
String f = matcher[i][0]
if (f) {
f = f.trim()
log.debug ("field f: \"$f\"")
if(['reporter', 'assignee'].contains(f)) {
addresses.add((issue.getAt(f) as User)?.emailAddress)
}
else if (f == "watchers") {
watcherManager.getCurrentWatcherUsernames(issue).each {String username ->
addresses.add(userUtil.getUser(username).emailAddress)
}
}
else if (f.toLowerCase() == "projectlead") {
addresses.add(issue.projectObject?.lead?.emailAddress)
}
else if (f.toLowerCase() == "componentlead") {
issue.componentObjects*.lead.each {
addresses.add(userUtil.getUser(it)?.emailAddress)
}
}
else if (f.toLowerCase().startsWith("customfield_")) {
CustomField cf = customFieldManager.getCustomFieldObjects(issue).find {it.id == f} as CustomField
if (cf.getCustomFieldType() instanceof UserCFType) {
addresses.add(cf.getValue(issue)?.emailAddress)
}
else if (cf.getCustomFieldType() instanceof MultiUserCFType) {
def cfEmailAddresses = cf.getValue(issue)?.collect{it.emailAddress}
if (cfEmailAddresses)
addresses.addAll(cfEmailAddresses)
}
else if (cf.getCustomFieldType() instanceof MultiGroupCFType) {
addresses.addAll (cf.getValue(issue).collect {Group group ->
groupManager.getUsersInGroup(group).collect {userUtil.getUser(it as String)?.emailAddress}
}.flatten())
}
else if (isTextField(cf)) {
// todo: need null check here
addresses.addAll((cf.getValue(issue) as String).split(/[\s,;]+/))
}
else {
log.debug("Unhandled custom field type $f, but will have a go anyway")
addresses.addAll((cf.getValue(issue) as String).split(/[\s,;]+/))
}
}
else if (f.toLowerCase().startsWith("role:")) {
f = f.replaceFirst("role:", "")
f = f.replaceAll("\"", "")
ProjectRole role = projectRoleManager.getProjectRole(f)
if (role) {
addresses.addAll (projectRoleManager.getProjectRoleActors(role, issue.projectObject).getUsers()*.emailAddress)
}
else {
log.warn ("Could not find role named \"$f\"")
}
}
else if (f.toLowerCase().startsWith("group:")) {
f = f.replaceFirst("group:", "")
f = f.replaceAll("\"", "")
Group group = userUtil.getGroup(f)
if (group) {
addresses.addAll(groupManager.getUsersInGroup(group)*.emailAddress)
log.debug "addresses: $addresses"
}
else {
log.warn ("Could not find group named \"$f\"")
}
}
else {
log.warn ("Could not handle field $f")
}
}
}
addresses.minus([null])
}
// https://studio.plugins.atlassian.com/browse/GRV-102
Boolean isTextField (CustomField cf) {
return ["com.atlassian.jira.issue.customfields.impl.TextCFType",
"com.atlassian.jira.issue.customfields.impl.GenericTextCFType"].any {
try {
return Class.forName(it).isAssignableFrom(cf.getCustomFieldType().class)
}
catch (Exception e) {
return false
}
}
}
String getDescription(Map params, boolean forPreview) {
if (!forPreview) {
return getDescription() + ", with subject: <pre style='display: inline'>${params[FIELD_EMAIL_SUBJECT_TEMPLATE]}</pre>"
}
// remove the event otherwise it goes in to the page as a String
params.remove("event")
Writable template = mergeEmailTemplateBody(params)
String emailFormat = params[FIELD_EMAIL_FORMAT]
MutableIssue prvwIssue = issueManager.getIssueObject(params[FIELD_PREVIEW_ISSUE] as String)
def sendTo = getAllToAddresses(prvwIssue, params)
def sendCc = getAllCCAddresses(prvwIssue, params)
// todo: should do all work on this email object, and display attributes in the table
def email = new Email("dummy@example.com")
addAttachmentsToMail(params, prvwIssue, email)
def attachmentsDisplay = extractAttachmentNamesForDisplay(email)
Boolean condition = ConditionUtils.processCondition(params[ConditionUtils.FIELD_CONDITION] as String, prvwIssue, false)
StringWriter writer = new StringWriter()
MarkupBuilder builder = new MarkupBuilder(writer)
builder.table {
tr{
td{b("Condition")}
td("The condition evaluated to $condition (note that for listeners the condition can't normally be tested)")
}
tr{
td{b("To")}
td(style:'background-color:#ffffff', sendTo.join(", "))
}
tr{
td{b("Cc")}
td(style:'background-color:#ffffff', sendCc.join(", "))
}
tr{
td{b("From")}
td(style:'background-color:#ffffff', params[FIELD_FROM] as String ?: mailServer.getDefaultFrom())
}
tr{
td{b("Subject")}
td(style:'background-color:#ffffff', mergeEmailTemplateSubject(params).toString())
}
tr{
td{b("Attachments")}
td(style:'background-color:#ffffff', attachmentsDisplay)
}
tr{
td(valign:'top') {
b("Body")
}
td(style:'background-color:#ffffff'){
if (emailFormat == 'TEXT') {
pre(template.toString())
}
else {
p(mkp.yieldUnescaped (template.toString()))
}
}
}
}
writer.toString()
}
public static List<String> extractAttachmentNames(Email email) {
if (email.getMultipart()?.getCount()) {
return (0..email.getMultipart().getCount() - 1).collect { kount ->
email.getMultipart().getBodyPart(kount).getFileName()
}
}
[]
}
public static String extractAttachmentNamesForDisplay(Email email) {
extractAttachmentNames(email).join(", ")
}
public Writable mergeEmailTemplateBody(Map params) {
mergeEmailTemplate(params, params[FIELD_EMAIL_TEMPLATE] as String)
}
public Writable mergeEmailTemplateSubject(Map params) {
mergeEmailTemplate(params, params[FIELD_EMAIL_SUBJECT_TEMPLATE] as String)
}
public String renderComment(Issue issue, String comment, boolean asHtml) {
if (asHtml) {
def rendererManager = ComponentAccessor.getComponent(RendererManager.class)
def fieldLayoutItem = ComponentAccessor.getFieldLayoutManager().getFieldLayout(issue).getFieldLayoutItem("comment")
def renderer = rendererManager.getRendererForField(fieldLayoutItem)
renderer.render(comment, null)
}
else {
comment
}
}
private Writable mergeEmailTemplate(Map params, String template) {
GStringTemplateEngine engine = new GStringTemplateEngine()
Map binding = [:]
binding.putAll(params)
MutableIssue issue
if (params["issue"]) {
issue = params["issue"] as MutableIssue
}
else {
issue = issueManager.getIssueObject(params[FIELD_PREVIEW_ISSUE] as String)
}
// todo document new things in the binding
ApplicationProperties applicationProperties = ComponentAccessor.getApplicationProperties()
binding.put("baseUrl", applicationProperties.getString(APKeys.JIRA_BASEURL))
binding.put("baseurl", applicationProperties.getString(APKeys.JIRA_BASEURL))
binding.put("ComponentAccessor", ComponentAccessor)
binding.put("issue", issue)
// add last comment to binding - render according to render method chosen
def commentManager = ComponentAccessor.getCommentManager()
def comments = commentManager.getComments(issue)
String lastComment // the comment made this transition
String latestPriorComment = null // the most recent comment prior to this transition
String mostRecentComment // the most recent comment, either in this transition or previously added
// if you want the most recent, or last you need to handle it in code
if (comments) {
latestPriorComment = comments.last().body
}
def event = params["event"] as IssueEvent
def transientVars = params["transientVars"]
if (event) {
lastComment = event.getComment()?.body
mostRecentComment = lastComment ?: latestPriorComment
}
else if (transientVars) {
lastComment = transientVars["comment"]
mostRecentComment = lastComment ?: latestPriorComment
}
else {
// preview mode
lastComment = latestPriorComment
mostRecentComment = latestPriorComment
}
// most recent comment should be the last comment, unless not present
// if preview mode, it's always last comment
def isHtml = params[FIELD_EMAIL_FORMAT] == "HTML"
binding.put("lastComment", renderComment(issue, lastComment, isHtml))
binding.put("mostRecentComment", renderComment(issue, mostRecentComment, isHtml))
binding.put("latestPriorComment", renderComment(issue, latestPriorComment, isHtml))
binding.put("comments", comments)
binding.putAll(ConditionUtils.setupBinding(issue, binding))
engine.createTemplate(template).make(binding)
}
public Boolean isFinalParamsPage(Map params) {
true
}
// todo: duplication
private static ScriptEngine getGroovyScriptEngine() {
// todo: this is just for testing
ScriptRunnerImpl.getScriptRunner().shell
}
}
|
Comments (0)
You can clone a snippet to your computer for local editing. Learn more.