Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in
Deleted user
0 / 0 points
Next:
badges earned

Your Points Tracker
Challenges
Leaderboard
  • Global
  • Feed

Badge for your thoughts?

You're enrolled in our new beta rewards program. Join our group to get the inside scoop and share your feedback.

Join group
Recognition
Give the gift of kudos
You have 0 kudos available to give
Who do you want to recognize?
Why do you want to recognize them?
Kudos
Great job appreciating your peers!
Check back soon to give more kudos.

Past Kudos Given
No kudos given
You haven't given any kudos yet. Share the love above and you'll see it here.

It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

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

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.

1 answer

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

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))
}

 

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

Suggest an answer

Log in or Sign up to answer
TAGS

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you