Запись от Кристиана Ковача, консультанта Adaptavist.

Добро пожаловать в четвертый пост из серии Gotta Script-em All, в котором мы изучаем возможности Jira с помощью пошагового руководства по созданию бесшовных интеграционных связей между Jira и другими инструментами. . Обязательно ознакомьтесь с предыдущими тремя постами в этой серии, если вы еще этого не сделали.

В части 1 мы рассмотрели, как создать пользовательскую кнопку и конечную точку отдыха в пользовательском интерфейсе Jira. Во части 2 мы возвращаемся к основам с руководством по написанию чистого кода. В третьей части мы продолжим наши поиски улучшения пользовательского интерфейса (UI) с помощью ScriptRunner и углубимся в тонкости манипулирования пользовательскими полями в Jira.

В этом посте (часть 4) теперь, когда мы создали диалоговое окно, мы будем использовать его для отправки данных в XMatters.

Сначала нам нужно создать простой код, который создает структуру нотации объектов Javascript (JSON), которую можно отправить в xMatters в виде сообщения или, с технической точки зрения, полезной нагрузки. Затем мы учим xMatters читать полезную нагрузку JSON и сидеть сложа руки с чувством достижения.

Теперь у нас есть счастливый диалог. Его можно редактировать, но, кроме закрытия диалогового окна, мы мало что можем с его помощью сделать. Если только мы как-то не придумаем, как отправить текстовое поле в xMatters.

План прост:

  1. Получить информацию из текстовой области при нажатии «Отправить xMatters»
  2. Отправить информацию в xMatters с помощью RestAPI

Однако выполнить наш план немного сложно.

Соревнование

Проблема в том, что до сих пор мы использовали JavaScript в диалогах. А так как вы можете переместить данные из Javascript обратно в ScriptRunner, вы знаете поговорку: однажды перейдя на JavaScript, вы никогда не вернетесь назад. Когда пользователь нажимает кнопку Отправить xMatters, это полностью JavaScript.

Поэтому нам нужно использовать JavaScript для отправки данных с помощью RestAPI. В Atlassian уже подумали об этом. Это большая безопасность нет-нет. Вы не можете просто пойти и бесцельно отправить данные во вселенную.

Новый план:

  1. Получить информацию из текстовой области при нажатии «Отправить xMatters»
  2. Отправка информации в сценарий ретрансляции с помощью RestAPI
  3. Соберите данные в скрипте ретрансляции и отправьте их в xMatters.

Это просто еще один дополнительный шаг. Как трудно это может быть?

Что такое JSON?

JSON и FREDY впервые появились в голливудском блокбастере… хм, подождите. Мои мысли только что ушли куда-то еще… Итак, вернемся к делу. JSON — это текстовый формат (сокращение от JavaScript Object Notation). Точно так же, как эссе имеет определенную структуру, JSON имеет свою собственную.

Однако он стандартизирован во всей отрасли, и вы можете использовать его во многих местах. В основном это относится к тому, как части программного обеспечения взаимодействуют через RestAPI.

В нашем случае полезная нагрузка выглядит так:

payload = {
            "subject" : "Urgent Message",
            "body" : ""
        }

Это довольно просто. Вы можете назвать переменную (например, «subject») и содержащееся в ней значение (например, «срочное сообщение»).

Когда вы отправляете его по правильному URL-адресу, другая часть программного обеспечения, которая прослушивает входящие данные, попытается «расшифровать» их. Этот процесс называется парсингом. Это причудливое название понимания данных в предопределенной форме.

В нашем случае xMatters будет ожидать subject и body в качестве входящих объектов и примет любые данные, представленные после двоеточия.

Сложная часть

Сначала давайте посмотрим на JavaScript из предыдущей главы, теперь дополненный вызовом RestAPI.

function submit() {
var payload = {
"subject" : "Urgent Message",
"body" : ""
};
payload.body = document.getElementById("sr-dialog-textarea").value;
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "https://MYJIRAINSTANCE.COW/rest/scriptrunner/latest/custom/xMattersRelay", false);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.send(JSON.stringify(payload));
AJS.dialog2("#sr-dialog").hide();
}

Итак, мы создали полезную нагрузку с такими свойствами, как subject и body, и план состоит в том, чтобы взять все данные из текстовой области и поместить их в 'body, затем отправьте все это в наш сценарий ретрансляции, который отправит его в xMatters.

Однако есть одна проблема, которую нам придется решить. JSON плохо работает с многострочными строками, и, поскольку мы хотим поместить нашу текстовую область в строку (элемент в JSON), это вызовет проблему. Я знаю, что кажусь оракулом, зная будущее, но поскольку я уже прошел через это, поверьте мне. #юстимпутешествия

К счастью, здесь на помощь приходит Google.

var textarea = $('textarea').val();
var linebreak = textarea.split('\n');
var length = linebreak.length;
var data = [];
for ( var i = 0 ; i<length ; i++){
data.push({ 'line': i , 'content': linebreak[i] });
console.log(data);
}

Теперь нужно просто вставить часть или весь этот код Javascript в уже существующий код.

Однако создание большего количества JSON здесь не является целью. Давайте просто удалим символы «\n» и вместо этого создадим одну длинную строку:

function submit() {
var payload = {
"subject" : "Urgent Message",
"body" : ""
};
var textarea = document.getElementById("sr-dialog-textarea").value;
var linebreak = textarea.split('\\n');
var length = linebreak.length;
var data = [];
for ( var i = 0 ; i<length ; i++){
data = data + " " + linebreak[i];
}
payload.body = data;
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "https://tesc-dev501.adaptavist.cloud/rest/scriptrunner/latest/custom/xMattersRelay", false);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.send(JSON.stringify(payload));
alert(xhttp.responseText);
AJS.dialog2("#sr-dialog").hide();
}

Настройка xMatters

План состоит в том, чтобы создать план связи в xMatters, настроить сообщение, создать конечную точку для отдыха и поместить URL-адрес (определенный xMatters) в сценарий ретрансляции.

Затем настройте простое сообщение с темой и телом. Эти два фрагмента данных будут отправлены диалоговым окном предварительного просмотра xMatters в Jira.

Настройте интеграцию с базовой аутентификацией (имя пользователя/пароль), выберите простую форму сообщения, которую вы создали на предыдущем шаге, а затем скопируйте URL-адрес. Вам понадобится этот URL-адрес, чтобы вставить его в код реле xMatters.

Полный сценарий диалога xMatters

Всегда не забывайте заменять идентификаторы настраиваемых полей, так как скрипт выйдет из строя, потому что он не сможет обрабатывать несуществующие настраиваемые поля.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
@BaseScript CustomEndpointDelegate delegate
import groovy.json.JsonSlurper
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.ContentType
import static groovyx.net.http.Method.*
import com.atlassian.jira.issue.fields.rest.json.beans.PriorityJsonBean
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript
import org.codehaus.jackson.map.ObjectMapper
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
//------------------------------------------------------------------
// This script can be applied as a rest-end-point that shows the dialog
// when a user clicks on the 'send xmatters' button
// Currently known button placement for web fragments:
// - jira.issue.tools
//------------------------------------------------------------------
// Please change the following so the script would suit your needs:
List<String> CustomFieldIDs = [
"customfield_10105",
"customfield_10106"
]
//------------------------------------------------------------------
static trimIssueKey(String key) {
key = key.replaceAll(/^\[|]$/, '')
return key
}
static getJiraBaseUrl() {
def baseUrl = ComponentAccessor.getApplicationProperties().getString("jira.baseurl")
return baseUrl
}
static getCustomFieldData(String key, String customFieldID) {
Issue issue = ComponentAccessor.issueManager.getIssueByCurrentKey(key)
return issue.getCustomFieldValue(ComponentAccessor.getCustomFieldManager()
.getCustomFieldObject(customFieldID))
}
static getTextArea(String issueKey, List<String> CustomFieldIDs) {
return """
This is the actual message that will be sent to xMatters with all the variables we gather from custom fields.
It can be edited here before sent.
Custom Field A: ${getCustomFieldData(issueKey, CustomFieldIDs[0])}
Another Custom Field: ${getCustomFieldData(issueKey, CustomFieldIDs[1])}
Thank you for your attention.
Cheers,
The A Team"""
}
static getDialogText(String issueKey, List<String> CustomFieldIDs) {
return """
<script>
function submit() {
var payload = {
"subject" : "Urgent Message",
"body" : "moo"
};
var textarea = document.getElementById("sr-dialog-textarea").value;
var linebreak = textarea.split('\\n');
var length = linebreak.length;
var data = [];
for ( var i = 0 ; i<length ; i++) {
data = data + " " + linebreak[i];
}
payload.body = data;
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "${getJiraBaseUrl()}/rest/scriptrunner/latest/custom/xMattersRelay", false);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.send(JSON.stringify(payload));
alert(xhttp.responseText);
AJS.dialog2("#sr-dialog").hide();
}
var el = document.getElementById("submit");
if (el.addEventListener)
el.addEventListener("click", submit, false);
else if (el.attachEvent)
el.attachEvent('onclick', submit);
</script>
<section role="dialog" id="sr-dialog"
class="aui-layer aui-dialog2 aui-dialog2-medium" aria-hidden="true" data-aui-remove-on-hide="true">
<header class="aui-dialog2-header">
<h2 class="aui-dialog2-header-main">xMatters Message</h2>
<a class="aui-dialog2-header-close">
<span class="aui-icon aui-icon-small aui-iconfont-close-dialog">Close</span>
</a>
</header>
<div class="aui-dialog2-content">
<p>Header of the dialog, some text about warnings and whatever....</p>
<textarea id="sr-dialog-textarea" rows="15" cols="75">
${getTextArea(issueKey, CustomFieldIDs)}
</textarea>
</div>
<footer class="aui-dialog2-footer">
<div class="aui-dialog2-footer-actions">
<button class="aui-button" id="submit">Send xMatters</button>
<button id="dialog-close-button" class="aui-button aui-button-link">Close</button>
</div>
<div class="aui-dialog2-footer-hint">This is a footer message</div>
</footer>
</section>
"""
}
showXMattersDialog() {
MultivaluedMap queryParams ->
String issueKey = queryParams.get("issueKey")
issueKey = trimIssueKey(issueKey)
String dialog = getDialogText(issueKey, CustomFieldIDs)
Response.ok().type(MediaType.TEXT_HTML).entity(dialog).build()
}

Полный скрипт ретрансляции xMatters

Вы также должны заменить URL-адрес xMatters, а также имя пользователя и пароль. Без этого конечная точка отдыха xMatters не примет вашу полезную нагрузку.

import groovy.transform.Field
import com.onresolve.scriptrunner.runner.util.UserMessageUtil
import groovy.json.JsonSlurper
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.ContentType
import static groovyx.net.http.Method.*
@BaseScript CustomEndpointDelegate delegate
import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
//------------------------------------------------------------------
// Don't forget to change the variables
//------------------------------------------------------------------
@Field String xMattersURL =
"https://adaptavist-dev.xmatters.com/api/integration/1/functions/3eb43db8-e0a4-40ff-b1c8-452e89dd4691/triggers"
@Field String xMattersUser = "[email protected]"
@Field String xMattersPassword = "myfancypassword"
//------------------------------------------------------------------
String issueKey = null
xMattersRelay(httpMethod: "POST") {
MultivaluedMap queryParams,
def payload ->
def jsonSlurper = new JsonSlurper()
def content = jsonSlurper.parseText(payload)
issueKey = content.key
def http = new HTTPBuilder(xMattersURL)
http.request(POST) {
requestContentType = ContentType.JSON
headers.'Authorization' =
"Basic ${"${xMattersUser}:${xMattersPassword}".bytes.encodeBase64().toString()}"
body = """
{
"properties": {
"Subject" : "${content.subject}",
"Body": "${content.body}"
},
"recipients": [
"kkovacs|Work Email"
]
}
"""
response.success = { resp, JSON ->
UserMessageUtil.success("xMatters sent. Weeeeee...")
return JSON
}
response.failure = { resp ->
UserMessageUtil.error("${resp.status}")
return "Request failed with status ${resp.status}"
}
}
Response.ok(payload).build()
}

Еще одна важная вещь, о которой следует помнить, это то, что для аутентификации xMatters используется ваш адрес электронной почты, а не ваше имя пользователя (указано в вашем профиле).

Присоединяйтесь к нам снова для части 5, где теперь, когда мы создали сценарий для передачи данных в xMatters, изменили наш сценарий диалога и настроили xMatters для получения данных, нам нужно преобразовать наш простой скрипт для отправки более сложных данных в XMatters.

Для этого нам нужно глубже погрузиться в мир кода и выяснить, что заставляет RestAPI работать. Хотя правильно сказать, что наше решение уже «достаточно хорошо», мы, тем не менее, стремимся к совершенству.

Об авторе:

Кристиан (Крис) Ковач — консультант Адаптавист, с опытом системного администрирования, обеспечения качества (QA) (ручные и автоматические тесты), ScriptRunner и настройка продукта Atlassian. Он обладает знаниями и опытом работы с различными языками программирования, включая JAVA, HTML, CSS, Pascal и C#.

Первоначально опубликовано на www.adaptavist.com.