Custom Scheduled Job -HTML Email Template not formatting as it should

kukabgd June 1, 2021

Hi,

I created custom scheduled job with ScriptRunner that is checking all our projects and sending out emails to responsible for those projects. An email is basically a HTML table with relevant data for the responsible.

My problem here is that I can't seem to find the solution to format table the way I want. I can add borders, and change the background colors of the table rows and columns, but I cant make the table columns to have fixed width. It always has adjustable size (it takes the size of the text). 

Is there a workaround for this maybe? Am I doing something wrong here? Take a look at the "table" part of my code bellow.

 

 

if(mapResponsible.get(responsible2.toString()))
{
//style='border:1px solid black'
emailBody =

"<tr>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px; overflow: scroll'>" + issueTypeName + "</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px; overflow: scroll'>${qe_cs.displayName}</td>"+
"</tr>"


mapResponsible.put(responsible2.toString(), mapResponsible.get(responsible2.toString()) + emailBody);

} else // If responsible is not in the list: add responsible, add email body
{

emailBody =
"<table style='max-width: 8500px;'>"+ //style='width: 500px; table-layout:fixed; overflow: hidden;'
"<thead style='background-color: #dddddd'>"+


"<tr>"+
"<th style='width: 500px; overflow: scroll'> T </th>"+
"<th style='width: 500px; overflow: scroll'> QE-CS </th>"+


"</tr>"+
"</div>"+
"</thead>"+

"<tr>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px; overflow: scroll'>" + issueTypeName + "</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px; overflow: scroll'>${qe_cs.displayName}</td>"+
"</tr>"

mapResponsible.put(responsible2.toString(), emailBody);
if(mapCheck.get(responsible2.toString())){
mapCheck.put(responsible2.toString(), mapCheck.get(responsible2.toString()) + "f")
}else{
mapCheck.put(responsible2.toString(), "f")
}
log.debug(responsible2)

}

 

And it's also not working when I add width to for e.g. <tr> or anywhere.

Thanks in advance.

3 answers

0 votes
kukabgd August 16, 2021

Hi,

I was hopping to achieve this without any additional add-ons. Hopefully someone has some other solution for this problem, since I am still trying to resolve this.

BR,

Stefan

0 votes
Boris Berenberg - Atlas Authority
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
August 2, 2021

If you decide that re-building the internal logic of a notification plugin isn't a great use of your time, check out Notification Assistant for Jira where you could set this up. The only item we won't have is the Counter column.

0 votes
Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
June 2, 2021

Hi @kukabgd,

Could you please share the full code that you are using for review?

In regards to the HTML text, could you please try and use Groovy's MarkupBuilder?

Thank you and Kind Regards,

Ram

kukabgd June 5, 2021

Hi Ram,

I'm now trying to tune up the code with Markup Builder, meanwhile here is the whole code.

I know I've probably made it overly complicated. Hopefully you can understand some parts:

  1. So what I do here is -  I am going through all open projects, look into their subtasks and see if there are links (Findings) connected to those subtasks.
  2. if there are, then see which person is "Responsible for findings"
  3. Save his finding info for this person and move on
  4. if some other person is responsible for the finding than save that person's finding info separatly and so on
  5. at the end, all responsibles will get an email with only THEIR findings (however for testing purpose here are all findings sent to one Email, in this case "SampleEmail@address.com")

All of this is done with Custom Scheduled Job:

import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.CustomFieldManager;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.issue.fields.CustomField;
import com.atlassian.jira.issue.link.IssueLink;
import java.text.SimpleDateFormat;
import com.atlassian.jira.mail.Email
import com.atlassian.mail.server.SMTPMailServer
import java.util.Map;
import java.util.Map.Entry;
import com.atlassian.jira.user.ApplicationUser;
import org.apache.log4j.Logger
import org.apache.log4j.Level
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
//if logs are needed use the code below
log.setLevel(Level.DEBUG)
//log.debug("message")


// Create new map for "Responsible for Findings" field. Here will all responsibles be saved, so that they can get emails with only their findings
HashMap<String, String> mapResponsible = new HashMap<String, String>();
HashMap<String, String> mapCheck = new HashMap<String, String>();

//JQL query for only open projects
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchProvider = ComponentAccessor.getComponent(SearchProvider)
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

def searchService = ComponentAccessor.getComponent(SearchService)
def jqlSearch = "project = XY AND issuetype = 'XY Project' AND Status = open"
def query = jqlQueryParser.parseQuery(jqlSearch)

def results = searchService.search(user, query, PagerFilter.getUnlimitedFilter());

results.getResults().each { documentIssue ->

// if you need a mutable issue you can do:
IssueManager issueManager = ComponentAccessor.getIssueManager();
def issue = issueManager.getIssueObject(documentIssue.id)

// do something to the issue...

String emailBody =""

//IssueManager issueManager = ComponentAccessor.getIssueManager();
def issueLinkManager = ComponentAccessor.getIssueLinkManager()
//def issue = event.getIssue()


// Get fields for filter

def projectNumberField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_13021"); // Project Number
def projectNumber = issue.getCustomFieldValue(projectNumberField);
def reviewMsField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_13032"); // Review Milestone
def msReviewTypeField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_13015"); // Ms Review Type
def commentFindingField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_13005"); // Comment/Finding
def responsibleForFindingField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_18604"); // Responsible for findings
def overDueField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_17004"); // OverDue

// Gets all subtasks of the projects.
final Collection < Issue > subTaskObjects = issue.getSubTaskObjects();
if(subTaskObjects != null && subTaskObjects.size() != 0)
{
for (Issue subTaskObject : subTaskObjects)
{
if(subTaskObject.issueType.name == "XY: Subtask")
{
if(subTaskObject != null)
{
// Gets all findings (links) of the subtasks
final List <IssueLink> issuelinks = issueLinkManager.getInwardLinks(subTaskObject.getId());
if(issuelinks != null && issuelinks.size() != 0)
{
for (IssueLink issueLink : issuelinks)
{
def mutablelink = issueManager.getIssueObject(issueLink.getSourceId())
// get the values of the fields
def reviewMs = mutablelink.getCustomFieldValue(reviewMsField);
def msReviewType = mutablelink.getCustomFieldValue(msReviewTypeField);
def commentFinding = mutablelink.getCustomFieldValue(commentFindingField);
def responsible = mutablelink.getCustomFieldValue(responsibleForFindingField);
def overDue = mutablelink.getCustomFieldValue(overDueField);


if (issueLink.getIssueLinkType().name == "XY Subtask finding" && issueLink != null && mutablelink.status.name == "open")
{


ApplicationUser responsible_2 = (ApplicationUser)mutablelink.getCustomFieldValue(responsibleForFindingField)

if(responsible_2){
def responsible2 = responsible_2.getEmailAddress()
}else{
def responsible2 = "SampleEmail@address.com"
}

// If responsible is already in the list: for that responsible, update the email body
if(mapResponsible.get(responsible2.toString()))
{
//style='border:1px solid black'
emailBody =

"<tr>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${mutablelink.key}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${projectNumber}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${reviewMs}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${msReviewType}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${commentFinding}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${responsible_2.displayName}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${overDue}</td>"+
"</tr>"


mapResponsible.put(responsible2.toString(), mapResponsible.get(responsible2.toString()) + emailBody);

} else // If responsible is not in the list: add responsible, add email body
{

emailBody =
"<table style='max-width: 100%;'>"+
"<thead style='background-color: #dddddd'>"+

"<tr>"+
"<th> Key </th>"+
"<th> Project Number </th>"+
"<th> Review MS </th>"+
"<th> MS Review Type </th>"+
"<th> Comment/Finding </th>"+
"<th> Responsible for findings </th>"+
"<th> Overdue </th>"+


"</tr>"+
"</div>"+
"</thead>"+

"<tr>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${mutablelink.key}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${projectNumber}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${reviewMs}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${msReviewType}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${commentFinding}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${responsible_2.displayName}</td>"+
"<td style='border-bottom: 2px solid #e8e3e3; border-right: 2px solid #e8e3e3; width: 500px'>${overDue}</td>"+
"</tr>"
mapResponsible.put(responsible2.toString(), emailBody);


}

}



}
}
}
}
}




}

log.debug(issue.summary);

}


def sendEmail(String emailAddr, String subject, String body)
{
SMTPMailServer mailServer = ComponentAccessor.getMailServerManager().getDefaultSMTPMailServer()
if (mailServer) {
Email email = new Email(emailAddr)
email.setSubject(subject)
email.setBody(body)
email.setMimeType("text/html")// this line is necessary to format email body as HTML
mailServer.send(email)
log.debug("Mail sent")
} else {
log.warn("Please make sure that a valid mailServer is configured")
}
}

def emailSubject = "Your open XY Subtask findings for " + projectNumber


for(String i : mapResponsible.keySet()){
// Close findings table
mapResponsible.put(i.toString(), mapResponsible.get(i.toString()) + "</table>"); // this was necessary to format table in Outlook. this way we only have one table, instead of create new table with new finding
sendEmail("SampleEmail@address.com", emailSubject, mapResponsible.get(i))
}

 

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
June 6, 2021

Hi @kukabgd,

Thank you for sharing your code.

Below is an example working code:-

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.mail.Email
import groovy.xml.MarkupBuilder

def customFieldManager = ComponentAccessor.customFieldManager
def mailServerManager = ComponentAccessor.mailServerManager
def mailServer = mailServerManager.defaultSMTPMailServer
def issueManager = ComponentAccessor.issueManager
def issue = issueManager.getIssueByCurrentKey("COM-3")
def subTasks = issue.subTaskObjects

def sampleList = customFieldManager.getCustomFieldObjectsByName("Sample List")[0]

def writer = new StringWriter()
def markup = new MarkupBuilder(writer)
def builder = new StringBuilder()

def emailSubject = "Sample Mail"

if(subTasks!=null) {

markup.html {
head {
title: "Example table"
}
body() {
table("style":"border: 0.5px solid black;") {
tr {
th ("style":"text-align: left; border: 0.5px solid black;", "class":"header","Issue Key" )
th ("style":"text-align: left; border: 0.5px solid black;", "class":"header","Sample List")
}
subTasks.each {
def key = it.key
def customFieldValue = it.getCustomFieldValue(sampleList)

tr {
td ("style":"text-align: left; border: 0.5px solid black;", "class":"row","${key.toString()}")
td ("style":"text-align: left; border: 0.5px solid black;", "class":"row","${customFieldValue.toString()}")
}
}
}
}
}

builder.append(writer)
}


if (mailServer) {
def email = new Email("rkumar@adaptavist.com")
email.setSubject(emailSubject)
email.setMimeType("text/html")
email.setBody(builder.toString())
def threadClassLoader = Thread.currentThread().contextClassLoader
Thread.currentThread().contextClassLoader = mailServer.class.classLoader
mailServer.send(email)
Thread.currentThread().contextClassLoader = threadClassLoader
}

Please note, this sample code is not 100% exact to your environment. Hence, you will need to make the required modifications.

Below is a print screen of the Custom Job Configuration:-

image1.png

And below is a print screen of the sample output produced:-

image2.png

I hope this helps to answer your question. :)

Thank you and Kind Regards,

Ram

kukabgd August 2, 2021

Hi Ram, 

thank you so much for the code, it helped me a lot with Email formatting. However now I can't quite get the grouping of issue links to work as I wanted.

So the idea was to go through all projects, get open sub-tasks and get the issue links in those sub-tasks.

The issue links should be sorted according to responsible person for those links (see picture Escalation Service Explained.png).

Then each responsible shall get an Email with only his/hers issue links (this is why I needed MarkupBuilder, so that I can format the table in the email).

Escalation Service Explained.png

Now for my previous solution, I have used HashMaps, and the sorting was working correctly, however now I cant get the sorting to work, probably when it comes to MarkupBuilder it is not working as in the example before.

So instead to get the email as shown above, both responsible persons get the same Email (see bellow Escalation Service Email Not Working Correctly.png)

Escalation Service Email Not Working Correctly.png

Notice the counter numbers and the way it is saving them in the table.

And finally, here is my code that I am using at the moment:

import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.CustomFieldManager;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.issue.fields.CustomField;
import com.atlassian.jira.issue.link.IssueLink;
import java.text.SimpleDateFormat;
import com.atlassian.jira.mail.Email
import com.atlassian.mail.server.SMTPMailServer
import java.util.Map;
import java.util.Map.Entry;
import com.atlassian.jira.user.ApplicationUser;
import org.apache.log4j.Logger
import org.apache.log4j.Level
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import groovy.xml.MarkupBuilder
//if logs are needed use the code below
log.setLevel(Level.DEBUG)
log.debug("message")


// Create new map for "Responsible for Findings" field. Here will all responsibles be saved, so that they can get subscriptions with only their findings
HashMap<String, String> mapResponsible = new HashMap<String, String>();
HashMap<String, String> mapResponsibleCountermeasure = new HashMap<String, String>();
HashMap<String, String> mapCheck = new HashMap<String, String>();

def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchProvider = ComponentAccessor.getComponent(SearchProvider)
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

def searchService = ComponentAccessor.getComponent(SearchService)
def jqlSearch = "project = QTOOL AND issuetype = 'QT Project' AND Status = open and key = QTOOL-10587"
def query = jqlQueryParser.parseQuery(jqlSearch)

def writer = new StringWriter()
def markup = new MarkupBuilder(writer)
def builder = new StringBuilder()


//def query = jqlQueryParser.parseQuery("project = QTOOL AND issuetype = 'QT Project' AND Status = open" )// project = QTOOL AND issuetype = "QT Project" AND Status = open
def results = searchService.search(user, query, PagerFilter.getUnlimitedFilter());

log.debug("Total issues: ${results.total}")

results.getResults().each { documentIssue ->
log.debug(documentIssue.key)

// if you need a mutable issue you can do:
IssueManager issueManager = ComponentAccessor.getIssueManager();
def issueLinkManager = ComponentAccessor.getIssueLinkManager()
def issue = issueManager.getIssueObject(documentIssue.id)


// Get fields for filter

def projectNumberField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_13021"); // Project Number
def projectNumber = issue.getCustomFieldValue(projectNumberField);
def reviewMsField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_13032"); // Review Milestone
def msReviewTypeField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_13015"); // Ms Review Type
def commentFindingField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_13005"); // Comment/Finding
def responsibleForFindingField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_18604"); // Responsible for findings [still needs to be implemented for Countermeasure]
def overDueField = ComponentAccessor.customFieldManager.getCustomFieldObject("customfield_17004"); // OverDue

def count = 1

int size = 0;
// Gets all subtasks of the projects.
final Collection < Issue > subTaskObjects = issue.getSubTaskObjects();
if(subTaskObjects != null && subTaskObjects.size() != 0)
{
for (Issue subTaskObject : subTaskObjects)
{
if(subTaskObject.issueType.name == "QT: Milestone Review")
{
if(subTaskObject != null)
{
// Gets all findings of the subtasks
final List <IssueLink> issuelinks = issueLinkManager.getInwardLinks(subTaskObject.getId());
if(issuelinks != null && issuelinks.size() != 0)
{
for (IssueLink issueLink : issuelinks)
{
def mutablelink = issueManager.getIssueObject(issueLink.getSourceId())

def issueTypeName = issueLink.getIssueLinkType().name;
def reviewMs = mutablelink.getCustomFieldValue(reviewMsField);
def msReviewType = mutablelink.getCustomFieldValue(msReviewTypeField);
def commentFinding = mutablelink.getCustomFieldValue(commentFindingField);
def responsible = mutablelink.getCustomFieldValue(responsibleForFindingField);
def overDue = mutablelink.getCustomFieldValue(overDueField);

/**************************************** START MARKUP HTML ****************************************/

markup.html {
head {
title: "Example table"
}


/**************************************** IF MS REVIEW FINDING ****************************************/
if (issueLink.getIssueLinkType().name == "MS Review finding" && issueLink != null && mutablelink.status.name == "open")
{


//SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy")
// def dueDateFormated = sdf.format(mutablelink.dueDate)

ApplicationUser responsible_2 = (ApplicationUser)mutablelink.getCustomFieldValue(responsibleForFindingField)
def responsible2
if(responsible_2){
responsible2 = responsible_2.getEmailAddress()
}else{
responsible2 = "test@test.com"
}




// If responsible is already in the list: for that responsible, update the email body
if(mapResponsible.get(responsible2.toString()))
{
body()
{
table("style":"border: 0.5px solid black;")
{
tr {
//td ("style":"text-align: left; border: 0.5px solid black; width: 100px", "class":"row","${issueTypeName.toString()}")
td ("style":"text-align: left; border: 0.5px solid black; width: 100px", "class":"row","${count++}")
td ("style":"text-align: left; border: 0.5px solid black; width: 100px", "class":"row","${mutablelink.key.toString()}")
td ("style":"text-align: left; border: 0.5px solid black; width: 200px", "class":"row","${projectNumber.toString()}")//responsible_2.displayName
td ("style":"text-align: left; border: 0.5px solid black; width: 200px", "class":"row","${responsible_2.displayName.toString()}")

}
}
}
log.debug("KEY 2")
log.debug(mutablelink.key.toString())
//mapResponsible.put(responsible2.toString(), mapResponsible.get(responsible2.toString()) + builder.append(writer));
mapResponsible.put(responsible2.toString(), mapResponsible.get(responsible2.toString()) + builder.append(writer));
writer.buffer.setLength(0)
log.debug("not first" + mapResponsible.get(responsible2.toString()))

} // If responsible is not in the list: add responsible, add email body*/
else{
body()
{
table("style":"border: 0.5px solid black;")
{
tr {
//th ("style":"text-align: left; border: 0.5px solid black; width: 100px", "class":"header","Issue Type" )
th ("style":"text-align: left; border: 0.5px solid black; width: 100px", "class":"header","Counter" )
th ("style":"text-align: left; border: 0.5px solid black; width: 100px", "class":"header","Issue Key" )
th ("style":"text-align: left; border: 0.5px solid black; width: 200px", "class":"header","Project Number")
th ("style":"text-align: left; border: 0.5px solid black; width: 200px", "class":"header","Responsible")
}
tr {
//td ("style":"text-align: left; border: 0.5px solid black; width: 100px", "class":"row","${issueTypeName.toString()}")
td ("style":"text-align: left; border: 0.5px solid black; width: 100px", "class":"row","${count++}")
td ("style":"text-align: left; border: 0.5px solid black; width: 100px", "class":"row","${mutablelink.key.toString()}")
td ("style":"text-align: left; border: 0.5px solid black; width: 200px", "class":"row","${projectNumber.toString()}")
td ("style":"text-align: left; border: 0.5px solid black; width: 200px", "class":"row","${responsible_2.displayName.toString()}")

}
}
log.debug("KEY 1")
log.debug(mutablelink.key.toString())
}

mapResponsible.put(responsible2.toString(), builder.append(writer));
log.debug("first" + mapResponsible.get(responsible2.toString()))
//log.debug(builder.append(writer))
writer.buffer.setLength(0)


if(mapCheck.get(responsible2.toString())){
mapCheck.put(responsible2.toString(), mapCheck.get(responsible2.toString()) + "f")
}else{
mapCheck.put(responsible2.toString(), "f")
}
log.debug(responsible2)


}

} /**************************************** END IF MS REVIEW FINDING ****************************************/

} /**************************************** END MARKUP HTML ****************************************/



}
}
}
}
}




}

log.debug(issue.summary);

} /**************************************** END OF QUERY ****************************************/

def sendEmail(String emailAddr, String subject, String body)
{
SMTPMailServer mailServer = ComponentAccessor.getMailServerManager().getDefaultSMTPMailServer()
if (mailServer) {
Email email = new Email(emailAddr)
email.setSubject(subject)
email.setBody(body)
email.setMimeType("text/html")// this line is necessary to format email body as HTML
mailServer.send(email)
log.debug("Mail sent")
} else {
log.warn("Please make sure that a valid mailServer is configured")
}
}

def emailSubject = "Your open MS Review findings for "

//Send Email to responsibles
for(String i : mapCheck.keySet()){
sendEmail(i, "Testing Subscriptions", mapResponsible.get(i).toString())
}


 

Any tips maybe? :)

Again, thanks a lot for the solution above, it is exactly what I needed.

Best regards,

Stefan 

Ram Kumar Aravindakshan _Adaptavist_
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
August 21, 2021

Hi @kukabgd

Glad to hear the solution worked for you. :)

For your requirement, you could try to modify your code slightly as shown below:-

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.mail.Email
import groovy.xml.MarkupBuilder

def mailServerManager = ComponentAccessor.mailServerManager
def mailServer = mailServerManager.defaultSMTPMailServer
def issueManager = ComponentAccessor.issueManager
def issue = issueManager.getIssueByCurrentKey("MOCK-3")
def subTasks = issue.subTaskObjects

def writer = new StringWriter()
def markup = new MarkupBuilder(writer)
def mailBodyList = [] as ArrayList<String>

def emailSubject = "Sample Mail"

if(subTasks!=null) {

markup.html {
head {
title: "Example table"
}
body() {
table("style":"border: 0.5px solid black;") {
tr {
th ("style":"text-align: left; border: 0.5px solid black;", "class":"header","Counter" )
th ("style":"text-align: left; border: 0.5px solid black;", "class":"header","Parent Issue Key" )
th ("style":"text-align: left; border: 0.5px solid black;", "class":"header","Sub Tasks")
th ("style":"text-align: left; border: 0.5px solid black;", "class":"header","Sub Tasks Assignee")
}
subTasks.eachWithIndex{ Issue i, int index ->
def key = i.key
def parent = i.parentObject
def reporter = i.reporter.name
tr {
td ("style":"text-align: left; border: 0.5px solid black;", "class":"row","${index+1}")
td ("style":"text-align: left; border: 0.5px solid black;", "class":"row","${parent.key}")
td ("style":"text-align: left; border: 0.5px solid black;", "class":"row","${key}")
td ("style":"text-align: left; border: 0.5px solid black;", "class":"row","${reporter}")
}
}
}
}
}
}

mailBodyList.addAll(writer.toString())

mailBodyList.findAll {
if (mailServer) {
def email = new Email("rkumar@adaptavist.com")
email.setSubject(emailSubject)
email.setMimeType("text/html")
email.setBody(it)
def threadClassLoader = Thread.currentThread().contextClassLoader
Thread.currentThread().contextClassLoader = mailServer.class.classLoader
mailServer.send(email)
Thread.currentThread().contextClassLoader = threadClassLoader
}
}

Please note, this sample code is not 100% exact to your environment. Hence, you will need to make the required modifications.

So the modified code, I am not able to display both the Parent issue as well as the sub-tasks, the reporter of the Sub-Task and also a counter to get the row numbers.

Below is a print screen of the output produced:-

scheduler_test.png

I hope this helps to answer your question. :)

Thank you and Kind Regards,

Ram

Suggest an answer

Log in or Sign up to answer