Запись от Кристиана Ковача, консультанта Adaptavist.
Добро пожаловать в четвертый пост из серии Gotta Script-em All, в котором мы изучаем возможности Jira с помощью пошагового руководства по созданию бесшовных интеграционных связей между Jira и другими инструментами. . Обязательно ознакомьтесь с предыдущими тремя постами в этой серии, если вы еще этого не сделали.
В части 1 мы рассмотрели, как создать пользовательскую кнопку и конечную точку отдыха в пользовательском интерфейсе Jira. Во части 2 мы возвращаемся к основам с руководством по написанию чистого кода. В третьей части мы продолжим наши поиски улучшения пользовательского интерфейса (UI) с помощью ScriptRunner и углубимся в тонкости манипулирования пользовательскими полями в Jira.
В этом посте (часть 4) теперь, когда мы создали диалоговое окно, мы будем использовать его для отправки данных в XMatters.
Сначала нам нужно создать простой код, который создает структуру нотации объектов Javascript (JSON), которую можно отправить в xMatters в виде сообщения или, с технической точки зрения, полезной нагрузки. Затем мы учим xMatters читать полезную нагрузку JSON и сидеть сложа руки с чувством достижения.
Теперь у нас есть счастливый диалог. Его можно редактировать, но, кроме закрытия диалогового окна, мы мало что можем с его помощью сделать. Если только мы как-то не придумаем, как отправить текстовое поле в xMatters.
План прост:
- Получить информацию из текстовой области при нажатии «Отправить xMatters»
- Отправить информацию в xMatters с помощью RestAPI
Однако выполнить наш план немного сложно.
Соревнование
Проблема в том, что до сих пор мы использовали JavaScript в диалогах. А так как вы можете переместить данные из Javascript обратно в ScriptRunner, вы знаете поговорку: однажды перейдя на JavaScript, вы никогда не вернетесь назад. Когда пользователь нажимает кнопку Отправить xMatters, это полностью JavaScript.
Поэтому нам нужно использовать JavaScript для отправки данных с помощью RestAPI. В Atlassian уже подумали об этом. Это большая безопасность нет-нет. Вы не можете просто пойти и бесцельно отправить данные во вселенную.
Новый план:
- Получить информацию из текстовой области при нажатии «Отправить xMatters»
- Отправка информации в сценарий ретрансляции с помощью RestAPI
- Соберите данные в скрипте ретрансляции и отправьте их в 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.