fork(8) download
  1. //package com.onresolve.jira.groovy.canned.workflow.postfunctions
  2.  
  3. import com.atlassian.core.ofbiz.CoreFactory
  4. import com.atlassian.crowd.embedded.api.Group
  5. import com.atlassian.crowd.embedded.api.User
  6. import com.atlassian.jira.ComponentManager
  7. import com.atlassian.jira.component.ComponentAccessor
  8. import com.atlassian.jira.config.properties.APKeys
  9. import com.atlassian.jira.config.properties.ApplicationProperties
  10. import com.atlassian.jira.event.issue.IssueEvent
  11. import com.atlassian.jira.issue.Issue
  12. import com.atlassian.jira.issue.MutableIssue
  13. import com.atlassian.jira.issue.RendererManager
  14. import com.atlassian.jira.issue.attachment.Attachment
  15. import com.atlassian.jira.issue.customfields.impl.MultiGroupCFType
  16. import com.atlassian.jira.issue.customfields.impl.MultiUserCFType
  17. import com.atlassian.jira.issue.customfields.impl.UserCFType
  18. import com.atlassian.jira.issue.fields.CustomField
  19. import com.atlassian.jira.issue.history.ChangeItemBean
  20. import com.atlassian.jira.security.groups.GroupManager
  21. import com.atlassian.jira.security.roles.ProjectRole
  22. import com.atlassian.jira.security.roles.ProjectRoleManager
  23. import com.atlassian.jira.util.AttachmentUtils
  24. import com.atlassian.jira.util.ErrorCollection
  25.  
  26. import com.atlassian.jira.util.SimpleErrorCollection
  27. import com.atlassian.mail.Email
  28. import com.atlassian.mail.MailException
  29. import com.atlassian.mail.MailFactory
  30. import com.atlassian.mail.queue.SingleMailQueueItem
  31. import com.onresolve.jira.groovy.CannedScriptRunner
  32. import com.onresolve.jira.groovy.canned.CannedScript
  33. import com.onresolve.jira.groovy.canned.utils.ConditionUtils
  34. import com.opensymphony.module.propertyset.PropertySet
  35. import groovy.text.GStringTemplateEngine
  36. import groovy.xml.MarkupBuilder
  37. import org.apache.log4j.Category
  38. import org.ofbiz.core.entity.GenericValue
  39.  
  40. import javax.activation.DataHandler
  41. import javax.activation.FileDataSource
  42. import javax.mail.BodyPart
  43. import javax.mail.Multipart
  44. import javax.mail.internet.AddressException
  45. import javax.mail.internet.InternetAddress
  46. import javax.mail.internet.MimeBodyPart
  47. import javax.mail.internet.MimeMultipart
  48. import java.sql.Timestamp
  49. import java.util.regex.Matcher
  50.  
  51.  
  52. import com.atlassian.jira.bc.issue.search.SearchService
  53. import com.atlassian.jira.issue.search.SearchRequest
  54. import com.atlassian.jira.issue.search.SearchRequestManager
  55. import com.atlassian.jira.security.JiraAuthenticationContext
  56. import com.atlassian.jira.web.bean.PagerFilter
  57. import java.text.SimpleDateFormat
  58. import com.atlassian.jira.web.bean.BackingI18n
  59. import groovy.time.*
  60. import org.codehaus.groovy.runtime.TimeCategory
  61. import org.apache.commons.lang.StringUtils
  62. import com.atlassian.jira.issue.changehistory.ChangeHistoryManager
  63.  
  64. class SendCustomEmail implements CannedScript{
  65.  
  66. public static String FIELD_PREVIEW_ISSUE = "FIELD_PREVIEW_ISSUE"
  67. public static String FIELD_EMAIL_TEMPLATE = "FIELD_EMAIL_TEMPLATE"
  68. public static String FIELD_EMAIL_FORMAT = "FIELD_EMAIL_FORMAT"
  69. public static String FIELD_TO_ADDRESSES = "FIELD_TO_ADDRESSES"
  70. public static String FIELD_TO_USER_FIELDS = "FIELD_TO_USER_FIELDS"
  71. public static String FIELD_CC_ADDRESSES = "FIELD_CC_ADDRESSES"
  72. public static String FIELD_CC_USER_FIELDS = "FIELD_CC_USER_FIELDS"
  73.  
  74. public static String FIELD_EMAIL_SUBJECT_TEMPLATE = "FIELD_EMAIL_SUBJECT_TEMPLATE"
  75. public static String FIELD_INCLUDE_ATTACHMENTS = "FIELD_INCLUDE_ATTACHMENTS"
  76. public static String FIELD_FROM = "FIELD_FROM"
  77.  
  78. public static String FIELD_INCLUDE_ATTACHMENTS_NONE = "FIELD_INCLUDE_ATTACHMENTS_NONE"
  79. public static String FIELD_INCLUDE_ATTACHMENTS_NEW = "FIELD_INCLUDE_ATTACHMENTS_NEW"
  80. public static String FIELD_INCLUDE_ATTACHMENTS_ALL = "FIELD_INCLUDE_ATTACHMENTS_ALL"
  81. public static String FIELD_INCLUDE_ATTACHMENTS_CUSTOM = "FIELD_INCLUDE_ATTACHMENTS_CUSTOM"
  82. public static String FIELD_INCLUDE_ATTACHMENTS_CALLBACK = "FIELD_INCLUDE_ATTACHMENTS_CALLBACK"
  83.  
  84. ComponentManager componentManager = ComponentManager.getInstance()
  85. def issueManager = ComponentAccessor.getIssueManager()
  86. def watcherManager = ComponentAccessor.getWatcherManager()
  87. def customFieldManager = ComponentAccessor.getCustomFieldManager()
  88. def projectRoleManager = ComponentAccessor.getComponent(ProjectRoleManager.class)
  89. def groupManager = ComponentAccessor.getComponent(GroupManager.class)
  90. def userUtil = ComponentAccessor.getUserUtil()
  91. def mailServerManager = ComponentAccessor.getMailServerManager()
  92. def mailServer = mailServerManager.getDefaultSMTPMailServer()
  93.  
  94. Category log = Category.getInstance(SendCustomEmail.class)
  95.  
  96. String getName() {
  97. "Send a custom email"
  98. }
  99.  
  100. public String getHelpUrl() {
  101. "https://j...content-available-to-author-only...n.net/wiki/display/GRV/Built-In+Scripts#Built-InScripts-Sendacustomemail"
  102. }
  103.  
  104. String getDescription() {
  105. "Send an email based on the provided template if conditions are met"
  106. }
  107.  
  108. List getCategories() {
  109. ["Function","ADMIN", "Listener"]
  110. }
  111.  
  112.  
  113. List getParameters(Map<String,String> params) {
  114.  
  115. def toFieldDesc = """Who to send the email to - valid issue fields such as assignee, reporter, projectLead,
  116. componentLead, watchers, user or user group custom fields,<br> or custom fields holding valid
  117. email address, eg <i>customfield_12345</i>,<br>or role:<i>Rolename</i>, e.g role:Developers,
  118. or group:<i>Groupname</i>. Use space to delimit."""
  119.  
  120. def outputs = [
  121. ConditionUtils.getConditionParameter(),
  122. [
  123. Name:FIELD_EMAIL_TEMPLATE,
  124. Label:"Email template",
  125. Description:"""Write a template. Can be plain text or use the
  126. <a href=\"http://d...content-available-to-author-only...s.org/display/GROOVY/Groovy+Templates#GroovyTemplates-GStringTemplateEngine\">GStringTemplateEngine</a>.
  127. See wiki for examples.""",
  128. Type:"text",
  129. ],
  130. [
  131. Name:FIELD_EMAIL_SUBJECT_TEMPLATE,
  132. Label:"Subject template",
  133. Description:"""Subject template. Can be plain text or use the
  134. <a href=\"http://d...content-available-to-author-only...s.org/display/GROOVY/Groovy+Templates#GroovyTemplates-GStringTemplateEngine\">GStringTemplateEngine</a>.
  135. See wiki for examples or click below.""",
  136. Examples: [
  137. """Issue XYZ-1 requires your approval""" : "Issue \$issue requires your approval"
  138. ]
  139. ],
  140. [
  141. Name:FIELD_EMAIL_FORMAT,
  142. Label:"Email format",
  143. Description:"Whether to send as plain text or HTML.",
  144. Type:"list",
  145. Values: [TEXT:'Plain text', HTML:'HTML'],
  146. ],
  147. [
  148. Name:FIELD_TO_ADDRESSES,
  149. Label:"To addresses",
  150. Description:" Who to send the email to. Use commas/space to delimit.",
  151. ],
  152. [
  153. Name:FIELD_TO_USER_FIELDS,
  154. Label:"To issue fields",
  155. Description: toFieldDesc,
  156. ],
  157. [
  158. Name:FIELD_CC_ADDRESSES,
  159. Label:"CC addresses",
  160. Description:" Who to include in CC. Use commas/space to delimit.",
  161. ],
  162. [
  163. Name:FIELD_CC_USER_FIELDS,
  164. Label:"CC issue fields",
  165. Description: toFieldDesc.replaceAll(" send ", " CC "),
  166. ],
  167.  
  168. [
  169. Name:FIELD_INCLUDE_ATTACHMENTS,
  170. Label:"Include attachments",
  171. Type:"radio",
  172. Values: [
  173. (FIELD_INCLUDE_ATTACHMENTS_NONE): "None",
  174. (FIELD_INCLUDE_ATTACHMENTS_NEW): "New",
  175. (FIELD_INCLUDE_ATTACHMENTS_ALL): "All",
  176. (FIELD_INCLUDE_ATTACHMENTS_CUSTOM): "Custom",
  177. ],
  178. Description: """Include the issue attachments in the email. You can specify none, or only attachments
  179. that were added in the transition pertaining to this event, or all attachments."""
  180. ],
  181. [
  182. Name: FIELD_INCLUDE_ATTACHMENTS_CALLBACK,
  183. Label: "Custom attachment callback",
  184. Description: "Enter a closure which will be called with each Attachment object. Only relevant if you " +
  185. "choose 'Custom' above.",
  186. Type: "text",
  187. Examples: [
  188. "Include only PDFs" : '{a -> a.filename.toLowerCase().endsWith(".pdf")}',
  189. "Include only PDFs added this transition" : '{a -> a.isNew() && a.filename.toLowerCase().endsWith(".pdf")}',
  190. "Only files less than 1 Mb" : '{a -> a.filesize < 1024**2}',
  191. ]
  192. ],
  193. [
  194. Name:FIELD_FROM,
  195. Label:"From email address",
  196. Description:""" What email address to send the mail from, eg jamie@example.com.
  197. Leave blank for default (<i>${mailServer?.getDefaultFrom()}</i>).""",
  198. ],
  199. [
  200. Name:FIELD_PREVIEW_ISSUE,
  201. Label:"Preview Issue Key",
  202. Description:"""Issue key for previewing what the mail will look like.
  203. ONLY used when previewing from the Admin section""",
  204. ],
  205. ]
  206.  
  207.  
  208. outputs
  209. }
  210.  
  211. ErrorCollection doValidate(Map params, boolean forPreview) {
  212. ErrorCollection errorCollection = new SimpleErrorCollection()
  213. String prvwIssueKey = params[FIELD_PREVIEW_ISSUE] as String
  214. if (forPreview) {
  215. if (! issueManager.getIssueObject(prvwIssueKey as String))
  216. errorCollection.addError(FIELD_PREVIEW_ISSUE, "This issue doesn't exist.")
  217.  
  218. String cond = params[ConditionUtils.FIELD_CONDITION] as String
  219. if (cond) {
  220. try {
  221. MutableIssue issue = issueManager.getIssueObject(prvwIssueKey)
  222. ConditionUtils.processCondition(cond, issue, true,
  223. [event: new IssueEvent(issue, [:] , null, 1)])
  224. /*
  225.   {
  226.   def getChangeLog() {
  227.   [getRelated : {[]}]
  228.   }
  229.   })
  230. */
  231. }
  232. catch (Exception e) {
  233. errorCollection.addError(ConditionUtils.FIELD_CONDITION, e.getMessage())
  234. log.debug(e.getMessage())
  235. }
  236. }
  237. }
  238. if (! params[FIELD_EMAIL_TEMPLATE]) {
  239. errorCollection.addError(FIELD_EMAIL_TEMPLATE, "You must provide a template.")
  240. }
  241. if (! params[FIELD_EMAIL_SUBJECT_TEMPLATE]) {
  242. errorCollection.addError(FIELD_EMAIL_SUBJECT_TEMPLATE, "You must provide a subject.")
  243. }
  244. if (! (params[FIELD_TO_ADDRESSES] || params[FIELD_TO_USER_FIELDS])) {
  245. errorCollection.addErrorMessage ("You must provide either fields or addresses to send emails to.")
  246. }
  247.  
  248. // todo: validation of "field" addresses, to and cc
  249.  
  250. [FIELD_TO_ADDRESSES, FIELD_CC_ADDRESSES].each {fieldParam ->
  251. if (params[fieldParam]) {
  252. def List invalid = getTextAddresses(params[fieldParam] as String).asList().findAll {String a ->
  253. ! validateEmail(a)
  254. }
  255. if (invalid)
  256. errorCollection.addError(fieldParam, "These don't look like valid email addresses: " + invalid.join(", "))
  257. }
  258. }
  259.  
  260. if (params[FIELD_FROM]) {
  261. if (! validateEmail(params[FIELD_FROM] as String)) {
  262. errorCollection.addError(FIELD_FROM, "Email is not valid: ${params[FIELD_FROM]}")
  263. }
  264. }
  265.  
  266. if ((params[FIELD_INCLUDE_ATTACHMENTS] == FIELD_INCLUDE_ATTACHMENTS_CUSTOM) && ! params[FIELD_INCLUDE_ATTACHMENTS_CALLBACK]) {
  267. errorCollection.addError(FIELD_INCLUDE_ATTACHMENTS_CALLBACK, "You must provide a callback if you select Custom.")
  268. }
  269.  
  270. errorCollection
  271. }
  272.  
  273. private boolean validateEmail (String email) {
  274. // don't use EmailValidator.getInstance().isValid, not worth the trouble of importing the classes
  275. try {
  276. new InternetAddress(email).validate()
  277. }
  278. catch (AddressException ae) {
  279. return false
  280. }
  281. true
  282. }
  283.  
  284. public Map sendMail (Map params) {
  285. MutableIssue issue = params['issue'] as MutableIssue
  286.  
  287. // preview mode
  288. if (!issue) {
  289. issue = issueManager.getIssueObject(params[FIELD_PREVIEW_ISSUE] as String)
  290. }
  291.  
  292. Boolean doIt = ConditionUtils.processCondition(params[ConditionUtils.FIELD_CONDITION] as String, issue, false, params)
  293. if (! doIt) {
  294. return [:]
  295. }
  296.  
  297. // do validation again
  298. String emailFormat = params[FIELD_EMAIL_FORMAT]
  299. log.debug("emailFormat: $emailFormat")
  300.  
  301. if (mailServer && ! MailFactory.isSendingDisabled()) {
  302. log.debug("Preparing to send the mail");
  303. def template = mergeEmailTemplateBody(params)
  304. def body = template.toString()
  305.  
  306. // no space when using separator: http://stackoverflow.com/a/12120203/1018918
  307. String toRecipientList = getAllToAddresses(issue, params).join(',');
  308. log.debug("To Recipientlist = ${toRecipientList}");
  309.  
  310. def ccRecipientList = getAllCCAddresses(issue, params).join(',');
  311. log.debug("CC Recipientlist = ${ccRecipientList}");
  312.  
  313. def email = new Email(toRecipientList, ccRecipientList, "");
  314.  
  315. // Now the message body.
  316. addAttachmentsToMail(params, issue, email)
  317.  
  318. email.setFrom(params[FIELD_FROM] as String ?: mailServer.getDefaultFrom())
  319. email.setSubject(mergeEmailTemplateSubject(params).toString())
  320. email.setMimeType(emailFormat == "HTML" ? "text/html" : "text/plain")
  321. email.setBody(body)
  322. try {
  323. log.debug ("Sending mail to ${email.getTo()}")
  324. log.debug ("with body ${email.getBody()}")
  325. log.debug ("from template ${params[FIELD_EMAIL_TEMPLATE]}")
  326. SingleMailQueueItem item = new SingleMailQueueItem(email);
  327. ComponentAccessor.getMailQueue().addItem(item);
  328. }
  329. catch (MailException e) {
  330. log.warn ("Error sending email", e)
  331. }
  332. }
  333. else {
  334. log.warn ("No mail server or sending disabled.")
  335. }
  336.  
  337. params.remove("event")
  338. return params
  339. }
  340.  
  341. Map doScript(Map params) {
  342. Thread executorThread = new Thread(new Runnable() {
  343. void run() {
  344. Thread.sleep(500)
  345. sendMail(params)
  346. }
  347. })
  348.  
  349. // run in separate thread so mail handler has a chance to assemble all attachments
  350. // https://j...content-available-to-author-only...n.net/browse/GRV-284
  351. executorThread.start()
  352. [:]
  353. }
  354.  
  355. private def addAttachmentsToMail(Map<String,String> params, MutableIssue issue, Email email) {
  356. if (params[FIELD_INCLUDE_ATTACHMENTS] && params[FIELD_INCLUDE_ATTACHMENTS] != FIELD_INCLUDE_ATTACHMENTS_NONE) {
  357. Multipart mp = new MimeMultipart("mixed");
  358. List<Long> attachmentIds = issue.getAttachments()*.id
  359. List<Long> newAttachmentIds = []
  360.  
  361. if (params[FIELD_INCLUDE_ATTACHMENTS] == FIELD_INCLUDE_ATTACHMENTS_NEW) {
  362. if (!(params["event"] || params["transientVars"])) {
  363. params.put("event", fakeLatestEvent(issue))
  364. log.debug("No event or transient vars - must be admin screen mode - creating latest event: " + params.get("event"))
  365. }
  366. }
  367.  
  368. if (params["event"]) { // listener
  369. List changeItems = params["event"].getChangeLog()?.getRelated("ChildChangeItem")
  370. log.debug("changeItems: $changeItems")
  371. changeItems.each {GenericValue gv ->
  372. if (gv["field"] == "Attachment" && gv["newvalue"]) {
  373. newAttachmentIds.add(gv["newvalue"] as Long)
  374. }
  375. }
  376. }
  377. else if (params["transientVars"]) {
  378. List changeItems = params["transientVars"]["changeItems"] as List
  379. changeItems.each {ChangeItemBean cib ->
  380. if (cib.getField() == "Attachment" && cib.getTo()) {
  381. newAttachmentIds.add(cib.getTo() as Long)
  382. }
  383. }
  384. }
  385.  
  386. attachmentIds.each {attachmentId ->
  387.  
  388. if (params[FIELD_INCLUDE_ATTACHMENTS] == FIELD_INCLUDE_ATTACHMENTS_NEW) {
  389. if (! newAttachmentIds.contains(attachmentId)) {
  390. return
  391. }
  392. }
  393.  
  394. def attachment = issue.attachments.find {attachment ->
  395. attachment.id == attachmentId
  396. }
  397.  
  398. if (params[FIELD_INCLUDE_ATTACHMENTS] == FIELD_INCLUDE_ATTACHMENTS_CUSTOM) {
  399. def gse = CannedScriptRunner.getGse()
  400.  
  401. try {
  402. def callback = gse.eval(params[FIELD_INCLUDE_ATTACHMENTS_CALLBACK])
  403.  
  404. // todo: isNew method - see previous versions for ideas
  405. def mailAttachment = new MailAttachment(attachment)
  406. mailAttachment.setIsNew(newAttachmentIds.contains(attachment.id))
  407. def result = callback.call(mailAttachment)
  408.  
  409. // Attachment.metaClass.
  410. log.debug "callback result for ${attachment.filename}: " + (result as Boolean)
  411. if (! result) {
  412. return
  413. }
  414. } catch (e) {
  415. log.error("Failed to evaluate closure for adding attachment to email", e)
  416. return
  417. }
  418. }
  419.  
  420.  
  421. File attFile = AttachmentUtils.getAttachmentFile(attachment)
  422. BodyPart attPart = new MimeBodyPart()
  423. FileDataSource attFds = new FileDataSource(attFile)
  424. attPart.setDataHandler(new DataHandler(attFds))
  425. attPart.setFileName(attachment.filename)
  426. log.debug("Attaching ${attachment.filename} to mail")
  427. mp.addBodyPart(attPart)
  428. }
  429.  
  430. email.setMultipart(mp)
  431. }
  432. }
  433.  
  434. static Set getTextAddresses(String toConfig) {
  435.  
  436. Set addresses = new HashSet()
  437.  
  438. if (! toConfig) {
  439. return addresses
  440. }
  441.  
  442. if (toConfig) {
  443. for (String f in toConfig.split(/[\s,;]+/)) {
  444. addresses.add(f)
  445. }
  446. }
  447. addresses
  448. }
  449.  
  450. List getAllToAddresses(Issue issue, Map params) {
  451. String toUserFields = params[FIELD_TO_USER_FIELDS]
  452. Set addresses = getAddressesFromFields(issue, toUserFields)
  453.  
  454. addresses.addAll(getTextAddresses(params[FIELD_TO_ADDRESSES] as String))
  455. addresses.toList()
  456. }
  457.  
  458. List getAllCCAddresses(Issue issue, Map params) {
  459. String ccUserFields = params[FIELD_CC_USER_FIELDS]
  460. Set addresses = getAddressesFromFields(issue, ccUserFields)
  461.  
  462. addresses.addAll(getTextAddresses(params[FIELD_CC_ADDRESSES] as String))
  463. addresses.toList()
  464. }
  465.  
  466. Set getAddressesFromFields(Issue issue, String toConfig) {
  467. Set addresses = new HashSet()
  468.  
  469. if (! toConfig) {
  470. return addresses
  471. }
  472.  
  473. // for testing: "reporter,assignee, watchers, customfield_10020, customfield_10040, customfield_10041, customfield_10042, customfield_10043, group:"a b""
  474.  
  475. String patStr = /([^\s]*"[^"]*")|([^\s"']+)/
  476. Matcher matcher = toConfig =~ patStr
  477. log.debug("toConfig: \"$toConfig\"")
  478.  
  479. if (matcher.getCount() == 0) {
  480. return []
  481. }
  482. for (int i in 0..matcher.getCount()-1) {
  483. String f = matcher[i][0]
  484.  
  485. if (f) {
  486. f = f.trim()
  487. log.debug ("field f: \"$f\"")
  488. if(['reporter', 'assignee'].contains(f)) {
  489. addresses.add((issue.getAt(f) as User)?.emailAddress)
  490. }
  491. else if (f == "watchers") {
  492. watcherManager.getCurrentWatcherUsernames(issue.genericValue).each {String username ->
  493. addresses.add(userUtil.getUser(username).emailAddress)
  494. }
  495. // doesn't work in jira 4.x
  496. // addresses.addAll(watcherManager.getCurrentWatchList(issue.genericValue).collect {it.email})
  497. }
  498. else if (f.toLowerCase() == "projectlead") {
  499. addresses.add(issue.projectObject?.lead?.emailAddress)
  500. }
  501. else if (f.toLowerCase() == "componentlead") {
  502. issue.componentObjects*.lead.each {
  503. addresses.add(userUtil.getUser(it)?.emailAddress)
  504. }
  505. }
  506. else if (f.toLowerCase().startsWith("customfield_")) {
  507. CustomField cf = customFieldManager.getCustomFieldObjects(issue).find {it.id == f} as CustomField
  508. if (cf.getCustomFieldType() instanceof UserCFType) {
  509. addresses.add(cf.getValue(issue)?.emailAddress)
  510. }
  511. else if (cf.getCustomFieldType() instanceof MultiUserCFType) {
  512. addresses.addAll(cf.getValue(issue)?.collect{it.emailAddress})
  513. }
  514. else if (cf.getCustomFieldType() instanceof MultiGroupCFType) {
  515. addresses.addAll (cf.getValue(issue).collect {Group group ->
  516. groupManager.getUsersInGroup(group).collect {userUtil.getUser(it as String)?.emailAddress}
  517. }.flatten())
  518. }
  519. else if (isTextField(cf)) {
  520. // todo: need null check here
  521. addresses.addAll((cf.getValue(issue) as String).split(/[\s,;]+/))
  522. }
  523. else {
  524. log.debug("Unhandled custom field type $f, but will have a go anyway")
  525. addresses.addAll((cf.getValue(issue) as String).split(/[\s,;]+/))
  526. }
  527. }
  528. else if (f.toLowerCase().startsWith("role:")) {
  529. f = f.replaceFirst("role:", "")
  530. f = f.replaceAll("\"", "")
  531. ProjectRole role = projectRoleManager.getProjectRole(f)
  532. if (role) {
  533. addresses.addAll (projectRoleManager.getProjectRoleActors(role, issue.projectObject).getUsers()*.emailAddress)
  534. }
  535. else {
  536. log.warn ("Could not find role named \"$f\"")
  537. }
  538. }
  539. else if (f.toLowerCase().startsWith("group:")) {
  540. f = f.replaceFirst("group:", "")
  541. f = f.replaceAll("\"", "")
  542.  
  543. Group group = userUtil.getGroup(f)
  544. if (group) {
  545. addresses.addAll(groupManager.getUsersInGroup(group)*.emailAddress)
  546. log.debug "addresses: $addresses"
  547. }
  548. else {
  549. log.warn ("Could not find group named \"$f\"")
  550. }
  551. }
  552. else {
  553. log.warn ("Could not handle field $f")
  554. }
  555. }
  556. }
  557. addresses.minus([null])
  558. }
  559.  
  560. // https://s...content-available-to-author-only...n.com/browse/GRV-102
  561. Boolean isTextField (CustomField cf) {
  562. return ["com.atlassian.jira.issue.customfields.impl.TextCFType",
  563. "com.atlassian.jira.issue.customfields.impl.GenericTextCFType"].any {
  564. try {
  565. return Class.forName(it).isAssignableFrom(cf.getCustomFieldType().class)
  566. }
  567. catch (Exception e) {
  568. return false
  569. }
  570. }
  571. }
  572.  
  573. String getDescription(Map params, boolean forPreview) {
  574. if (!forPreview) {
  575. return getDescription()
  576. }
  577.  
  578. // remove the event otherwise it goes in to the page as a String
  579. params.remove("event")
  580. Writable template = mergeEmailTemplateBody(params, true)
  581.  
  582. String emailFormat = params[FIELD_EMAIL_FORMAT]
  583.  
  584. MutableIssue prvwIssue = issueManager.getIssueObject(params[FIELD_PREVIEW_ISSUE] as String)
  585.  
  586. // add the changeitems for the most recent event so listeners can be better tested
  587. params.put("event", fakeLatestEvent(prvwIssue))
  588.  
  589. def sendTo = getAllToAddresses(prvwIssue, params)
  590. def sendCc = getAllCCAddresses(prvwIssue, params)
  591.  
  592. // todo: should do all work on this email object, and display attributes in the table
  593. def email = new Email("dummy@example.com")
  594. addAttachmentsToMail(params, prvwIssue, email)
  595.  
  596. def attachmentsDisplay = ""
  597. if (email.getMultipart()?.getCount()) {
  598. attachmentsDisplay = (0 .. email.getMultipart().getCount() - 1).collect {kount ->
  599. email.getMultipart().getBodyPart(kount).getFileName()
  600. }.join(", ")
  601. }
  602.  
  603. Boolean condition = ConditionUtils.processCondition(params[ConditionUtils.FIELD_CONDITION] as String, prvwIssue, false)
  604.  
  605. StringWriter writer = new StringWriter()
  606. MarkupBuilder builder = new MarkupBuilder(writer)
  607.  
  608. builder.table {
  609. tr{
  610. td{b("Condition")}
  611. td("The condition evaluated to $condition (note that for listeners the condition can't normally be tested)")
  612. }
  613. tr{
  614. td{b("To")}
  615. td(style:'background-color:#ffffff', sendTo.join(", "))
  616. }
  617. tr{
  618. td{b("Cc")}
  619. td(style:'background-color:#ffffff', sendCc.join(", "))
  620. }
  621. tr{
  622. td{b("From")}
  623. td(style:'background-color:#ffffff', params[FIELD_FROM] as String ?: mailServer.getDefaultFrom())
  624. }
  625. tr{
  626. td{b("Subject")}
  627. td(style:'background-color:#ffffff', mergeEmailTemplateSubject(params, true).toString())
  628. }
  629. tr{
  630. td{b("Attachments")}
  631. td(style:'background-color:#ffffff', attachmentsDisplay)
  632. }
  633. tr{
  634. td(valign:'top') {
  635. b("Body")
  636. }
  637. td(style:'background-color:#ffffff'){
  638. if (emailFormat == 'TEXT') {
  639. pre(template.toString())
  640. }
  641. else {
  642. p(mkp.yieldUnescaped (template.toString()))
  643. }
  644. }
  645. }
  646. }
  647.  
  648. writer.toString()
  649. }
  650.  
  651. private IssueEvent fakeLatestEvent(MutableIssue prvwIssue) {
  652. Collection<GenericValue> changeGroups = CoreFactory.getGenericDelegator().findByAnd("ChangeGroup", ["issue": prvwIssue.getId()]);
  653. IssueEvent event
  654. if (changeGroups) {
  655. def changeGroup = changeGroups ? changeGroups.last() : null
  656. event = new IssueEvent(prvwIssue, null, null, null, changeGroup, [:], 1, false)
  657. }
  658. else {
  659. event = new IssueEvent(prvwIssue, [:] , null, 1)
  660. }
  661. return event
  662. }
  663.  
  664. public Writable mergeEmailTemplateBody(Map params, Boolean isPreview = false) {
  665. mergeEmailTemplate(params, params[FIELD_EMAIL_TEMPLATE] as String, isPreview)
  666. }
  667.  
  668. public Writable mergeEmailTemplateSubject(Map params, Boolean isPreview = false) {
  669. mergeEmailTemplate(params, params[FIELD_EMAIL_SUBJECT_TEMPLATE] as String, isPreview)
  670. }
  671.  
  672. private Writable mergeEmailTemplate(Map params, String template, Boolean isPreview = false) {
  673. GStringTemplateEngine engine = new GStringTemplateEngine()
  674. Map binding = [:]
  675. binding.putAll(params)
  676. MutableIssue issue
  677. if (params["issue"]) {
  678. issue = params["issue"] as MutableIssue
  679. }
  680. else {
  681. issue = issueManager.getIssueObject(params[FIELD_PREVIEW_ISSUE] as String)
  682. }
  683. // todo document new things in the binding
  684. ApplicationProperties applicationProperties = componentManager.getApplicationProperties()
  685. binding.put("baseUrl", applicationProperties.getString(APKeys.JIRA_BASEURL))
  686. binding.put("baseurl", applicationProperties.getString(APKeys.JIRA_BASEURL))
  687. binding.put("componentManager", componentManager)
  688. binding.put("issue", issue)
  689.  
  690. // add last comment to binding - render according to render method chosen
  691. def commentManager = ComponentAccessor.getCommentManager()
  692. def comments = commentManager.getComments(issue)
  693. def lastComment = null
  694. if (comments) {
  695. lastComment = comments.last().body
  696.  
  697. if (params[FIELD_EMAIL_FORMAT] == "HTML") {
  698.  
  699. def rendererManager = ComponentAccessor.getComponent(RendererManager.class)
  700. def fieldLayoutItem = ComponentAccessor.getFieldLayoutManager().getFieldLayout(issue).getFieldLayoutItem("comment")
  701. def renderer = rendererManager.getRendererForField(fieldLayoutItem)
  702. lastComment = renderer.render(lastComment, null)
  703. }
  704. }
  705. binding.put("lastComment", lastComment)
  706.  
  707. binding.putAll(ConditionUtils.setupBinding(issue, binding))
  708.  
  709. engine.createTemplate(template).make(binding)
  710. }
  711.  
  712. public Boolean isFinalParamsPage(Map params) {
  713. true
  714. }
  715. }
  716.  
  717. public class MailAttachment {
  718. private Attachment delegate
  719. private boolean isNew
  720.  
  721. boolean isNew() {
  722. return isNew
  723. }
  724.  
  725. void setIsNew(boolean aNew) {
  726. isNew = aNew
  727. }
  728.  
  729. MailAttachment(Attachment delegate) {
  730. this.delegate = delegate
  731. }
  732.  
  733. PropertySet getProperties() {
  734. return delegate.getProperties()
  735. }
  736.  
  737. Issue getIssueObject() {
  738. return delegate.getIssueObject()
  739. }
  740.  
  741. GenericValue getIssue() {
  742. return delegate.getIssue()
  743. }
  744.  
  745. Long getId() {
  746. return delegate.getId()
  747. }
  748.  
  749. String getMimetype() {
  750. return delegate.getMimetype()
  751. }
  752.  
  753. String getFilename() {
  754. return delegate.getFilename()
  755. }
  756.  
  757. Timestamp getCreated() {
  758. return delegate.getCreated()
  759. }
  760.  
  761. Long getFilesize() {
  762. return delegate.getFilesize()
  763. }
  764.  
  765. String getAuthor() {
  766. return delegate.getAuthor()
  767. }
  768. }
  769.  
  770. //SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss")
  771. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd")
  772.  
  773.  
  774.  
  775. template = '''
  776. <!-- ################################################################### -->
  777.  
  778.  
  779. <!DOCTYPE html>
  780. <html>
  781. <head>
  782. <meta charset="utf-8">
  783. <style>
  784. table {
  785. border-collapse: collapse;
  786. font-size: 10px;
  787. }
  788. td, th {
  789. border: 1px solid #000;
  790. padding:4px;
  791. }
  792. .centerCol {
  793. text-align: center;
  794. }
  795.  
  796. .highlightRed {
  797. background-color: #FF3333;
  798. }
  799. .highlightYellow {
  800. background-color: #FFFF66;
  801. }
  802. .highlightGreen {
  803. background-color: #66FF99;
  804. }
  805. .highlightGrey {
  806. background-color: lightgrey;
  807. }
  808. </style>
  809. </head>
  810. <body>
  811.  
  812. <b>List of SLA relevant Incidents</b><br/>
  813. <br/>
  814. <table>
  815. <tr class=highlightGrey>
  816. <th>Prio</th>
  817. <th>Key</th>
  818. <th>Summary</th>
  819. <th>Status</th>
  820. <th>Created date</th>
  821. <th>1st Resolved</th>
  822. <th>1st INT</th>
  823. <th>1st PROD</th>
  824. <th>SLA due date</th>
  825. <th>Remaining</th>
  826. <th>Asignee</th>
  827. <th>RE [MD]</th>
  828. </tr>
  829.  
  830. <%
  831.  
  832. issues.each { issue ->
  833. def cssClass = 'highlightGreen'
  834. cssClass = ( issue.escalation == 2 ? 'highlightYellow' : cssClass)
  835. cssClass = ( issue.escalation == 3 ? 'highlightRed' : cssClass)
  836.  
  837. out << "<tr class='" << cssClass << "'>"
  838. out << "<td class='centerCol'>" << issue.priority_id << "</td>"
  839. out << "<td><a href='https://w...content-available-to-author-only...d.de/browse/" << issue.key << "'>" << issue.key << "</a></td>"
  840. out << "<td>" << issue.summary << "</td>"
  841. out << "<td>" << issue.status << "</td>"
  842. out << "<td>" << issue.created << "</td>"
  843. out << "<td>" << issue.resolved_date << "</td>"
  844. out << "<td>" << issue.integration_date << "</td>"
  845. out << "<td>" << issue.production_date << "</td>"
  846. out << "<td>" << issue.due << "</td>"
  847. out << "<td>" << issue.remaining << "</td>"
  848. out << "<td>" << issue.assignee << "</td>"
  849. out << "<td class='centerCol'>" << issue.estimate << "</td>"
  850. out << "</tr>\\n"
  851. }
  852.  
  853. %>
  854. </table><br/>
  855.  
  856. <br/>
  857. Visit Jira to see all incidents <a href='https://w...content-available-to-author-only...d.de/issues/?filter=12106'>link</a><br/>
  858.  
  859. <br/>
  860. <b>SLA due date (Open -> 1st Resolved)</b>
  861. <br/>
  862. <table width="250" >
  863. <tr class=centerCol>
  864. <td class=highlightGrey> Prio 1 </td>
  865. <td > 3 days </td>
  866. </tr>
  867. <tr class=centerCol>
  868. <td class=highlightGrey> Prio 2 </td>
  869. <td > 10 days </td>
  870. </tr>
  871.  
  872. </table>
  873.  
  874. <br/>
  875. <b>Escalation</b>
  876. <br/>
  877. <table width="250" >
  878. <tr class=centerCol>
  879. <td class=highlightGrey> remaining > 1 day </td>
  880. <td class=highlightGreen> ok </td>
  881. </tr>
  882. <tr class=centerCol>
  883. <td class=highlightGrey>
  884. remaining <= 1 day<br/>
  885. remaining > - 5 days</td>
  886. <td class=highlightYellow> warrning </td>
  887. </tr>
  888. <tr class=centerCol>
  889. <td class=highlightGrey> remaining <= - 5 days </td>
  890. <td class=highlightRed> escalation </td>
  891. </tr>
  892. </table>
  893.  
  894.  
  895. </body>
  896. </html>
  897.  
  898.  
  899. <!-- ################################################################### -->
  900.  
  901. '''
  902. Long filterId = 12106
  903. def secInHour = 28800
  904. //def dFormat = 'yyyy-MM-dd HH:mm:ss'
  905. def dFormat = 'yyyy-MM-dd HH:mm'
  906. def maxLen = 50
  907.  
  908. def issues = []
  909.  
  910. toResolved = 'Resolved'
  911. toIntegration = 'Integration'
  912. toProduction = 'Production'
  913. toDocumentation = 'Documentation'
  914. toClosed = 'Closed'
  915. Date resolved = new Date(0)
  916. String resolved_date = ""
  917. String integration_date = ""
  918. String production_date = ""
  919. String closed_date = ""
  920.  
  921. def myIssueManager = ComponentAccessor.getIssueManager()
  922. //BackingI18n i18n = ComponentAccessor.getJiraAuthenticationContext().getI18nHelper()
  923. MutableIssue myIssue = myIssueManager.getIssueObject('KTINC-2086')
  924.  
  925. SearchService searchService = ComponentAccessor.getComponent(SearchService.class);
  926. JiraAuthenticationContext jiraAuthenticationContext = ComponentAccessor.getJiraAuthenticationContext()
  927.  
  928. user = jiraAuthenticationContext.getLoggedInUser()
  929. //jqlQuery = 'project = "KT INC" AND priority in ("Showstopper (Prio1)", "Severe (Prio2) ") AND status not in (Closed) ORDER BY priority, createdDate'
  930. SearchRequestManager searchRequestManager = componentManager.getSearchRequestManager()
  931.  
  932. SearchRequest filter = searchRequestManager.getSharedEntity(filterId)
  933. jqlQuery = filter.query.getQueryString()
  934. //jqlQuery = 'project = "KT-Incident Management DC KSC/TSC" AND priority in ("Showstopper (Prio1)", "Severe (Prio2) ") AND status not in (Closed, Documentation) ORDER BY priority, createdDate'
  935.  
  936. SearchService.ParseResult parseResult = searchService.parseQuery(user, jqlQuery);
  937. ChangeHistoryManager changeHistoryManager = ComponentAccessor.getChangeHistoryManager()
  938.  
  939. if (parseResult.isValid()) {
  940. def searchResult = searchService.search(user, parseResult.getQuery(), PagerFilter.getUnlimitedFilter())
  941. //issues = searchResult.issues
  942.  
  943. use (TimeCategory) {
  944. searchResult.issues.each { el ->
  945. Map issue = [:]
  946.  
  947. changeItems = changeHistoryManager.getAllChangeItems(el)
  948. resolved_date = ""
  949. integration_date = ""
  950. production_date = ""
  951. closed_date = ""
  952.  
  953. changeItems.findAll{it.getField() == 'status'}.each{
  954. // get only first transition to given status
  955. if (it.getTo() == toResolved && it.getTo() != it.getFrom() && resolved_date == "") {
  956. resolved = it.created
  957. resolved_date = it.created.format(dFormat)
  958. }
  959. if (it.getTo() == toIntegration && it.getTo() != it.getFrom() && integration_date == "") {
  960. integration_date = it.created.format(dFormat)
  961. }
  962. if (it.getTo() == toProduction && it.getTo() != it.getFrom() && production_date == "") {
  963. production_date = it.created.format(dFormat)
  964. }
  965. if (it.getTo() == toClosed && it.getTo() != it.getFrom() && closed_date == "") {
  966. closed_date = it.created.format(dFormat)
  967. }
  968. }
  969.  
  970. // if given status was skiped then use closed date
  971. if (resolved_date == "" && closed_date != "") {
  972. resolved_date = closed_date
  973. }
  974. if (integration_date == "" && closed_date != "") {
  975. integration_date = closed_date
  976. }
  977. if (toProduction == "" && closed_date != "") {
  978. production_date = closed_date
  979. }
  980.  
  981. def due = (el.priority.id == '1' ? (el.getCreated() + 3.days) : (el.getCreated() + 10.days) )
  982. def now = new Date()
  983. if (resolved.format('YYYY') != '1970') {
  984. now = resolved
  985. }
  986. def escalation = 1
  987. escalation = ( now > due - 1.days ? 2 : 1)
  988. escalation = ( now > due + 5.days ? 3 : escalation)
  989.  
  990. def remaining = (due - now)
  991. remaining = (remaining.getMinutes() > 30 ? remaining + 1.hours : remaining)
  992. remaining = (remaining.getHours() == 24 ? remaining - 24.hours + 1.days: remaining)
  993. remaining = new DatumDependentDuration(0, 0, remaining.getDays(), remaining.getHours(), 0, 0, 0)
  994. remaining = ( due > now ? '+ ' + remaining.toString() : '- ' + remaining.toString().replaceAll('-', '') )
  995.  
  996. issue.put('key', el.key)
  997. issue.put('summary', (el.summary.length() > maxLen ? el.summary[0..maxLen-1] + '...' : el.summary))
  998. issue.put('status', el.status.name)
  999. issue.put('priority_id', el.priority.id)
  1000. issue.put('priority_name', el.priority?.name)
  1001. issue.put('created', el.getCreated().format(dFormat) )
  1002. issue.put('resolved_date', resolved_date )
  1003. issue.put('integration_date', integration_date )
  1004. issue.put('production_date', production_date )
  1005. issue.put('due', due.format(dFormat) )
  1006. issue.put('remaining', remaining )
  1007. issue.put('escalation', escalation )
  1008. issue.put('assignee', el.assignee?.displayName.replaceAll("\\(Extern\\)", "") )
  1009. issue.put('estimate', (el.estimate != null ? Math.round(el.estimate.div(secInHour) * 10) / 10 : 'n/a') )
  1010.  
  1011. issues.add(issue)
  1012. }
  1013. }
  1014. } else {
  1015. results = "Invalid JQL: $jqlQuery"
  1016. return results
  1017. }
  1018.  
  1019. Map params = [:]
  1020. //params.put('issue', myIssue)
  1021. params.put('issues', issues)
  1022. params.put('TimeCategory', TimeCategory)
  1023. params.put('StringUtils', StringUtils)
  1024. //params.put('i18n', i18n)
  1025. params.put('FIELD_PREVIEW_ISSUE', 'KTINC-2086')
  1026. params.put('FIELD_EMAIL_TEMPLATE', template)
  1027. params.put('FIELD_EMAIL_SUBJECT_TEMPLATE', 'SLA relevant Incidents')
  1028. //params.put('FIELD_TO_ADDRESSES', 'Hubert.Nafalski.extern@KabelDeutschland.de')
  1029. params.put('FIELD_TO_ADDRESSES', 'Hubert.Nafalski.extern@KabelDeutschland.de, Martin.Feichtmair@KabelDeutschland.de, Vassili.Andrianov1@KabelDeutschland.de, Sebastian.Hinderer@KabelDeutschland.de, VLNSCEntwicklungDCKSCTSC@KabelDeutschland.de, Artur.Mlynarski.extern@KabelDeutschland.de')
  1030. params.put('FIELD_EMAIL_FORMAT', 'HTML')
  1031. params.put('FIELD_INCLUDE_ATTACHMENTS', 'FIELD_INCLUDE_ATTACHMENTS_NONE')
  1032.  
  1033. SendCustomEmail script = new SendCustomEmail()
  1034. script.sendMail(params)
  1035. res = script.getDescription(params, true)
  1036.  
  1037. return res
  1038.  
Runtime error #stdin #stdout #stderr 1.96s 333888KB
stdin
Standard input is empty
stdout
Standard output is empty
stderr
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/home/aM8J40/prog.groovy: 24: unable to resolve class com.atlassian.jira.util.ErrorCollection
 @ line 24, column 1.
   import com.atlassian.jira.util.ErrorCollection
   ^

/home/aM8J40/prog.groovy: 42: unable to resolve class javax.mail.BodyPart
 @ line 42, column 1.
   import javax.mail.BodyPart
   ^

/home/aM8J40/prog.groovy: 16: unable to resolve class com.atlassian.jira.issue.customfields.impl.MultiUserCFType
 @ line 16, column 1.
   import com.atlassian.jira.issue.customfields.impl.MultiUserCFType
   ^

/home/aM8J40/prog.groovy: 43: unable to resolve class javax.mail.Multipart
 @ line 43, column 1.
   import javax.mail.Multipart
   ^

/home/aM8J40/prog.groovy: 11: unable to resolve class com.atlassian.jira.issue.Issue
 @ line 11, column 1.
   import com.atlassian.jira.issue.Issue
   ^

/home/aM8J40/prog.groovy: 27: unable to resolve class com.atlassian.mail.Email
 @ line 27, column 1.
   import com.atlassian.mail.Email
   ^

/home/aM8J40/prog.groovy: 33: unable to resolve class com.onresolve.jira.groovy.canned.utils.ConditionUtils
 @ line 33, column 1.
   import com.onresolve.jira.groovy.canned.utils.ConditionUtils
   ^

/home/aM8J40/prog.groovy: 14: unable to resolve class com.atlassian.jira.issue.attachment.Attachment
 @ line 14, column 1.
   import com.atlassian.jira.issue.attachment.Attachment
   ^

/home/aM8J40/prog.groovy: 10: unable to resolve class com.atlassian.jira.event.issue.IssueEvent
 @ line 10, column 1.
   import com.atlassian.jira.event.issue.IssueEvent
   ^

/home/aM8J40/prog.groovy: 20: unable to resolve class com.atlassian.jira.security.groups.GroupManager
 @ line 20, column 1.
   import com.atlassian.jira.security.groups.GroupManager
   ^

/home/aM8J40/prog.groovy: 62: unable to resolve class com.atlassian.jira.issue.changehistory.ChangeHistoryManager
 @ line 62, column 1.
   import com.atlassian.jira.issue.changehistory.ChangeHistoryManager
   ^

/home/aM8J40/prog.groovy: 54: unable to resolve class com.atlassian.jira.issue.search.SearchRequestManager
 @ line 54, column 1.
   import com.atlassian.jira.issue.search.SearchRequestManager
   ^

/home/aM8J40/prog.groovy: 47: unable to resolve class javax.mail.internet.MimeMultipart
 @ line 47, column 1.
   import javax.mail.internet.MimeMultipart
   ^

/home/aM8J40/prog.groovy: 44: unable to resolve class javax.mail.internet.AddressException
 @ line 44, column 1.
   import javax.mail.internet.AddressException
   ^

/home/aM8J40/prog.groovy: 52: unable to resolve class com.atlassian.jira.bc.issue.search.SearchService
 @ line 52, column 1.
   import com.atlassian.jira.bc.issue.search.SearchService
   ^

/home/aM8J40/prog.groovy: 9: unable to resolve class com.atlassian.jira.config.properties.ApplicationProperties
 @ line 9, column 1.
   import com.atlassian.jira.config.properties.ApplicationProperties
   ^

/home/aM8J40/prog.groovy: 15: unable to resolve class com.atlassian.jira.issue.customfields.impl.MultiGroupCFType
 @ line 15, column 1.
   import com.atlassian.jira.issue.customfields.impl.MultiGroupCFType
   ^

/home/aM8J40/prog.groovy: 32: unable to resolve class com.onresolve.jira.groovy.canned.CannedScript
 @ line 32, column 1.
   import com.onresolve.jira.groovy.canned.CannedScript
   ^

/home/aM8J40/prog.groovy: 55: unable to resolve class com.atlassian.jira.security.JiraAuthenticationContext
 @ line 55, column 1.
   import com.atlassian.jira.security.JiraAuthenticationContext
   ^

/home/aM8J40/prog.groovy: 26: unable to resolve class com.atlassian.jira.util.SimpleErrorCollection
 @ line 26, column 1.
   import com.atlassian.jira.util.SimpleErrorCollection
   ^

/home/aM8J40/prog.groovy: 60: unable to resolve class org.codehaus.groovy.runtime.TimeCategory
 @ line 60, column 1.
   import org.codehaus.groovy.runtime.TimeCategory
   ^

/home/aM8J40/prog.groovy: 8: unable to resolve class com.atlassian.jira.config.properties.APKeys
 @ line 8, column 1.
   import com.atlassian.jira.config.properties.APKeys
   ^

/home/aM8J40/prog.groovy: 19: unable to resolve class com.atlassian.jira.issue.history.ChangeItemBean
 @ line 19, column 1.
   import com.atlassian.jira.issue.history.ChangeItemBean
   ^

/home/aM8J40/prog.groovy: 17: unable to resolve class com.atlassian.jira.issue.customfields.impl.UserCFType
 @ line 17, column 1.
   import com.atlassian.jira.issue.customfields.impl.UserCFType
   ^

/home/aM8J40/prog.groovy: 31: unable to resolve class com.onresolve.jira.groovy.CannedScriptRunner
 @ line 31, column 1.
   import com.onresolve.jira.groovy.CannedScriptRunner
   ^

/home/aM8J40/prog.groovy: 4: unable to resolve class com.atlassian.crowd.embedded.api.Group
 @ line 4, column 1.
   import com.atlassian.crowd.embedded.api.Group
   ^

/home/aM8J40/prog.groovy: 53: unable to resolve class com.atlassian.jira.issue.search.SearchRequest
 @ line 53, column 1.
   import com.atlassian.jira.issue.search.SearchRequest
   ^

/home/aM8J40/prog.groovy: 45: unable to resolve class javax.mail.internet.InternetAddress
 @ line 45, column 1.
   import javax.mail.internet.InternetAddress
   ^

/home/aM8J40/prog.groovy: 5: unable to resolve class com.atlassian.crowd.embedded.api.User
 @ line 5, column 1.
   import com.atlassian.crowd.embedded.api.User
   ^

/home/aM8J40/prog.groovy: 37: unable to resolve class org.apache.log4j.Category
 @ line 37, column 1.
   import org.apache.log4j.Category
   ^

/home/aM8J40/prog.groovy: 23: unable to resolve class com.atlassian.jira.util.AttachmentUtils
 @ line 23, column 1.
   import com.atlassian.jira.util.AttachmentUtils
   ^

/home/aM8J40/prog.groovy: 7: unable to resolve class com.atlassian.jira.component.ComponentAccessor
 @ line 7, column 1.
   import com.atlassian.jira.component.ComponentAccessor
   ^

/home/aM8J40/prog.groovy: 22: unable to resolve class com.atlassian.jira.security.roles.ProjectRoleManager
 @ line 22, column 1.
   import com.atlassian.jira.security.roles.ProjectRoleManager
   ^

/home/aM8J40/prog.groovy: 30: unable to resolve class com.atlassian.mail.queue.SingleMailQueueItem
 @ line 30, column 1.
   import com.atlassian.mail.queue.SingleMailQueueItem
   ^

/home/aM8J40/prog.groovy: 61: unable to resolve class org.apache.commons.lang.StringUtils
 @ line 61, column 1.
   import org.apache.commons.lang.StringUtils
   ^

/home/aM8J40/prog.groovy: 6: unable to resolve class com.atlassian.jira.ComponentManager
 @ line 6, column 1.
   import com.atlassian.jira.ComponentManager
   ^

/home/aM8J40/prog.groovy: 12: unable to resolve class com.atlassian.jira.issue.MutableIssue
 @ line 12, column 1.
   import com.atlassian.jira.issue.MutableIssue
   ^

/home/aM8J40/prog.groovy: 38: unable to resolve class org.ofbiz.core.entity.GenericValue
 @ line 38, column 1.
   import org.ofbiz.core.entity.GenericValue
   ^

/home/aM8J40/prog.groovy: 29: unable to resolve class com.atlassian.mail.MailFactory
 @ line 29, column 1.
   import com.atlassian.mail.MailFactory
   ^

/home/aM8J40/prog.groovy: 56: unable to resolve class com.atlassian.jira.web.bean.PagerFilter
 @ line 56, column 1.
   import com.atlassian.jira.web.bean.PagerFilter
   ^

/home/aM8J40/prog.groovy: 21: unable to resolve class com.atlassian.jira.security.roles.ProjectRole
 @ line 21, column 1.
   import com.atlassian.jira.security.roles.ProjectRole
   ^

/home/aM8J40/prog.groovy: 3: unable to resolve class com.atlassian.core.ofbiz.CoreFactory
 @ line 3, column 1.
   import com.atlassian.core.ofbiz.CoreFactory
   ^

/home/aM8J40/prog.groovy: 58: unable to resolve class com.atlassian.jira.web.bean.BackingI18n
 @ line 58, column 1.
   import com.atlassian.jira.web.bean.BackingI18n
   ^

/home/aM8J40/prog.groovy: 18: unable to resolve class com.atlassian.jira.issue.fields.CustomField
 @ line 18, column 1.
   import com.atlassian.jira.issue.fields.CustomField
   ^

/home/aM8J40/prog.groovy: 34: unable to resolve class com.opensymphony.module.propertyset.PropertySet
 @ line 34, column 1.
   import com.opensymphony.module.propertyset.PropertySet
   ^

/home/aM8J40/prog.groovy: 13: unable to resolve class com.atlassian.jira.issue.RendererManager
 @ line 13, column 1.
   import com.atlassian.jira.issue.RendererManager
   ^

/home/aM8J40/prog.groovy: 28: unable to resolve class com.atlassian.mail.MailException
 @ line 28, column 1.
   import com.atlassian.mail.MailException
   ^

/home/aM8J40/prog.groovy: 46: unable to resolve class javax.mail.internet.MimeBodyPart
 @ line 46, column 1.
   import javax.mail.internet.MimeBodyPart
   ^

/home/aM8J40/prog.groovy: 936: unable to resolve class SearchService.ParseResult 
 @ line 936, column 27.
   SearchService.ParseResult parseResult =  searchService.parseQuery(user, jqlQuery);
                             ^

49 errors