Using Script Runner: How to automate a Script that changes the Summary Field of an Issue everytime

dD
Contributor
September 24, 2021

Hello,

 

I am trying to get comfortable with ScriptRunner and therefore I want to automate a Script that changes the Summary of issues of an certain issuetype everytime the issue is created or updated. I already have written a Automation Rule with Jira Automation Plugin which works. Now I want the same result using ScriptRunner.

The following figure shows the Rule which I want to "copy" with ScriptRunner.


figure 1.GIF


To get there I used a Behaviour and added the following Script:


import com.atlassian.jira.ComponentAccessor
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.Issue


def field2ChangeValue = getFieldByName('summary')

def StudiengangF =
ComponentAccessor.getCustomFieldManager().getCustomFieldObject("Studiengang")

def StudiengangValue =
underlyingIssue?.getCustomFieldValue(StudiengangF)

def ModulkennungF =
ComponentAccessor.getCustomFieldManager().getCustomFieldObject("Modulkennung")

def ModulkennungValue =
underlyingIssue?.getCustomFieldValue(ModulkennungF)

field2ChangeValue.setFormValue("${StudiengangValue}-${ModulkennungValue}")


It seems like the script itself is fine but I dont have any idea how to automate this. To literally say Jira: Do this Scirpt everytime an issue of the issuetype XY is created or updated.

Do I have to set an initialiser for that and what would it be?

 

1 answer

1 accepted

2 votes
Answer accepted
Hana Kučerová
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
September 24, 2021

Hi @dD ,

Behaviours won't work in your case. You need to create a new custom listener with events Issue Created and Issue Updated. You can find some inspiration for your script here.

dD
Contributor
September 24, 2021

Thanks @Hana Kučerová for your support. 

I found some ispiration and tried my best but it did not worked out. Could you please help me with my code?

import com.atlassian.jira.ComponentAccessor
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder

// Define which issuetypes
def issue = event.issue
issue.projectObject.key == 'SUD'
issue.issueType.name == 'SUD Modul Basic'

def customFieldManager = ComponentAccessor.customFieldManager

def StudiengangF =
ComponentAccessor.getCustomFieldManager().getCustomFieldObject("Studiengang")

def StudiengangValue = getCustomFieldValue(StudiengangF)

def ModulkennungF =
ComponentAccessor.getCustomFieldManager().getCustomFieldObject("Modulkennung")

def ModulkennungValue = getCustomFieldValue(ModulkennungF)

// the name of the custom field to update
final customFieldName = 'Summary'

// the new value of the field
final newValue = '${StudiengangValue}-${ModulkennungValue}'

def customField = customFieldManager.getCustomFieldObjects(issue).findByName(customFieldName)
assert customField : "Could not find custom field with name $customFieldName"

customField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(customField), newValue), new DefaultIssueChangeHolder())

Best

Daniel

Hana Kučerová
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
September 24, 2021

Hi @dD ,

please try something like this.

Appropriate ids for you custom fields needs to be set

  • CUSTOM_FIELD_STUDIENGANG_ID
  • CUSTOM_FIELD_MODULKENNUNG_ID

Project needs to be set in the configuration of listener.

import com.atlassian.jira.bc.issue.IssueService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.IssueInputParameters
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.user.ApplicationUser

Long CUSTOM_FIELD_STUDIENGANG_ID = 12345
Long CUSTOM_FIELD_MODULKENNUNG_ID = 23456

Issue issue = event.getIssue()
CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager()
IssueService issueService = ComponentAccessor.issueService
ApplicationUser loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
IssueInputParameters issueInputParameters = issueService.newIssueInputParameters()

CustomField studiengangCustomField = customFieldManager.getCustomFieldObject(CUSTOM_FIELD_STUDIENGANG_ID)
CustomField modulkennungCustomField = customFieldManager.getCustomFieldObject(CUSTOM_FIELD_MODULKENNUNG_ID)
String studiengang = issue.getCustomFieldValue(studiengangCustomField) as String
String modulkennung = issue.getCustomFieldValue(modulkennungCustomField) as String

issueInputParameters.setSummary(studiengang + "-" + modulkennung)

IssueService.UpdateValidationResult updateValidationResult = issueService.validateUpdate(loggedInUser, issue.getId(), issueInputParameters)
assert updateValidationResult.isValid() : updateValidationResult.errorCollection

IssueService.IssueResult issueResult = issueService.update(loggedInUser, updateValidationResult)
assert issueResult.isValid() : issueResult.errorCollection
Like dD likes this
dD
Contributor
September 24, 2021

Best thanks to you it really worked !! :) I really appreciate your help!

Can I  also ask you to briefly explain me some coding fragments (I would like to really understand it?

E.g. what does "long" do and why is it neccesairy to relate to a field by the field_ids and not by the name?

I am also wondering why sometimes words need to be written twice like " IssueService issueService =" ?

How exactly does the "IssueInputParameters"-function work?

and lastly what happens in that codeblock?

IssueService.UpdateValidationResult updateValidationResult = issueService.validateUpdate(loggedInUser, issue.getId(), issueInputParameters)
assert updateValidationResult.isValid() : updateValidationResult.errorCollection

IssueService.IssueResult issueResult = issueService.update(loggedInUser, updateValidationResult)
assert issueResult.isValid() : issueResult.errorCollection
Hana Kučerová
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
September 24, 2021

E.g. what does "long" do and why is it neccesairy to relate to a field by the field_ids and not by the name?

It is not necessary, but for me it is a good practise to use ids instead of names, as you can have more custom fields with the same name, but id is an unique identifier of the custom field.

 

I am also wondering why sometimes words need to be written twice like " IssueService issueService =" ?

You can also do 

def issueService = ComponentAccessor.issueService

I just prefer to define exact variable types, so that all sort of suggestion and check functions work in my IDE.

 

How exactly does the "IssueInputParameters"-function work?

and lastly what happens in that codeblock?

Through IssueInputParameters you prepare all sort of changes and then try to update the issue (validateUpdate) under the account of actually logged in user. If everything is ok and there are no errors, then really update the issue (update).

Like dD likes this
dD
Contributor
September 24, 2021

Thank you so much @Hana Kučerová , I really learned a lot from you :) 

Like Hana Kučerová likes this
dD
Contributor
September 24, 2021

Regarding your last answer: 

Through IssueInputParameters you prepare all sort of changes and then try to update the issue (validateUpdate) under the account of actually logged in user. If everything is ok and there are no errors, then really update the issue (update).

Is it necessairly to update the issue under the account of actually logged in user? Or is it possible to avoid this and change the summary without touching an user account just alone by the system?

Hana Kučerová
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
September 24, 2021

@dD it is not necessary to use logged in user (which in this context means - the user, who created / updated the issue), it can be any user (e.g. some technical account), who has the appropriate permissions (to see the issue and update it). If the user doesn't have appropriate permissions, the proces of validation fails and update is not performed.

Like dD likes this
dD
Contributor
September 24, 2021

Alright that makes sense.

 

As I went on further testing I noted that this Script is still not working perfectly. We overlooked that by now every new issue within that project is adressed. So when I create a new Issue from a different issuetype, automatically the summary gets changed. But this automation rule should only adress one specific issuetype within the project, how could i make this clear? 

Issue issue.issueType.name == 'SUD Modul Basic'
Issue issue = event.getIssue() 

I tried this but it doesnt work 

Hana Kučerová
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
September 24, 2021

You can add the check on the top of the script, something like:


String ISSUE_TYPE_ID = "12345"

Issue issue = event.getIssue()
if
(issue.getIssueTypeId() != ISSUE_TYPE_ID) {
return
}

 12345 needs to be replaced with your allowed issue type id

Like dD likes this
dD
Contributor
September 26, 2021

Yes !! Nice !! This works :)

If I would want to adress to more than one Issuetype, would I create a list like

String ISSUE_TYPE_ID = ["12345","234562]

or would I need to define a Variable for each issuetype? like 

String ISSUE_TYPE_ID_1
String ISSUE_TYPE_ID_2
String ISSUE_TYPE_ID_3

or which is the smartest way? :D 

Hana Kučerová
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
September 28, 2021

@dD 

I would go for list of strings, something like

List<String> ISSUE_TYPE_IDS = ["12345", "23456"]

Issue issue = event.getIssue()
if (!ISSUE_TYPE_IDS.contains(issue.getIssueTypeId())) {
return
}
Like dD likes this
dD
Contributor
September 28, 2021

This is really helpful! Thank you @Hana Kučerová  :)

 

Also, is it right that "event.getIssue()" only defines the issues for listeners? What would I need to write if I would like to run a scheduled job? Because then I retrieve an error, if I use event.getIssue().

Hana Kučerová
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
September 28, 2021

@dD 

Listeners are called, whenever events occur in Jira.  Scheduled Jobs are a mechanism for running code at specific times, so they are not connected to any events in Jira and "event.getIssue()" cannot work.

You need to obtain the issues differently, for example search for them using JQL or get them directly using their key.

dD
Contributor
September 28, 2021

// Get the Issue via JQL Search
final jqlSearch = "issuetype = 'SUD Modul' and 'Sommersemester aktiv?' = aktiv"

// Step1: some components
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def searchService = ComponentAccessor.getComponentOfType(SearchService)

// Step2: Parse the query
def parseResult = searchService.parseQuery(user, jqlSearch)
if (!parseResult.valid) {
log.error('Invalid query')
return null}

try {
// Step3: Perform the query to get the issues
def results = searchService.search(user, parseResult.query, PagerFilter.unlimitedFilter)
def issues = results.results
issues.each {
log.info(it.key)}

def assignee = issue.getAsignee()

// Defining the subject and body of the email
def emailSubject = "Verantwortlichkeit im Modul ${issue.modulname}"
def emailBody = "Sehr geehrte/r ${assignee.displayName},\n derzeit sind Sie als Lehrkraft im Modul {{issue.modulname}} eingetragen. \n Wenn Sie diese Modul im kommenden Sommersemester (SoSe ${now.format("yyyy")}) nicht unterrichten können, dann melden Sie dies bitte umgehend ihrer Studiengangsassistenz.\n Ansonsten tun Sie nichts und ihre Modulverantwortlichkeit bleibt bestehen.\n\n(Dies ist eine automatisch generierte Mail)"
def emailAddr = issue.getAssignee().getEmailAdress

String sendEmail(String emailAddr, String subject, String body) {
def logger = Logger.getLogger(getClass())
logger.setLevel(Level.DEBUG)

// Stop emails being sent if the outgoing mail server gets disabled (useful if you start a script sending emails and need to stop it)
def mailSettings = ComponentAccessor.getComponent(MailSettings)
if (mailSettings?.send()?.disabled) {
return 'Your outgoing mail server has been disabled'
}
def mailServer = ComponentAccessor.mailServerManager.defaultSMTPMailServer
if (!mailServer) {
logger.debug('Your mail server Object is Null, make sure to set the SMTP Mail Server Settings Correctly on your Server')
return 'Failed to Send Mail. No SMTP Mail Server Defined'
}
def email = new Email(emailAddr)
email.setMimeType('text/html')
email.setSubject(subject)
email.setBody(body)
try {
// This is needed to avoid the exception about IMAPProvider
ContextClassLoaderSwitchingUtil.runInContext(SMTPMailServer.classLoader) {
mailServer.send(email)
}
logger.debug('Mail sent')
'Success'
} catch (MailException e) {
logger.debug("Send mail failed with error: ${e.message}")
'Failed to Send Mail, Check Logs for error'
}
}
sendEmail(emailAddr, emailSubject, emailBody)

 @Hana Kučerová  I tried this code, but it is wrong. What do I have to change?

Suggest an answer

Log in or Sign up to answer