Интеграция Jira с внешними системами — частая задача в корпоративных средах. Нужно синхронизировать данные с CRM, отправлять уведомления в Slack, интегрироваться с CI/CD, обмениваться данными с другими системами. ScriptRunner позволяет создавать интеграции через REST API, вебхуки и другие механизмы без разработки плагинов. В этой статье разберу практические примеры интеграций.
Типы интеграций
Основные типы интеграций через ScriptRunner:
- Исходящие запросы — Jira отправляет данные во внешние системы
- Входящие запросы — внешние системы отправляют данные в Jira через REST API
- Двусторонняя синхронизация — обмен данными в обе стороны
- Вебхуки — отправка уведомлений о событиях в Jira
Отправка данных во внешние системы
Пример отправки данных о задаче в внешнюю систему при её создании:
import groovy.json.JsonBuilder
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.net.URI
def event = event
def issue = event.issue
def eventTypeId = event.eventTypeId
if (eventTypeId != com.atlassian.jira.event.type.EventType.ISSUE_CREATED_ID) {
return
}
def externalApiUrl = "https://external-api.company.com/issues"
def payload = [
jiraKey: issue.key,
summary: issue.summary,
description: issue.description,
status: issue.status.name,
assignee: issue.assignee?.username,
reporter: issue.reporter?.username,
created: issue.created.toString()
]
def json = new JsonBuilder(payload).toPrettyString()
def client = HttpClient.newHttpClient()
def request = HttpRequest.newBuilder()
.uri(URI.create(externalApiUrl))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer YOUR_API_TOKEN")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build()
try {
def response = client.send(request, HttpResponse.BodyHandlers.ofString())
if (response.statusCode() == 200 || response.statusCode() == 201) {
log.warn("Successfully sent issue ${issue.key} to external system")
} else {
log.error("Failed to send issue ${issue.key}: ${response.statusCode()}")
}
} catch (Exception e) {
log.error("Error sending issue ${issue.key}: ${e.message}")
}
Интеграция с Slack
Отправка уведомлений в Slack при изменении статуса задачи:
import groovy.json.JsonBuilder
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.net.URI
def event = event
def issue = event.issue
def slackWebhookUrl = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
def message = [
text: "Задача ${issue.key} изменила статус",
blocks: [
[
type: "section",
text: [
type: "mrkdwn",
text: "*<${getBaseUrl()}/browse/${issue.key}|${issue.key}>* - ${issue.summary}\nСтатус: *${issue.status.name}*"
]
]
]
]
def json = new JsonBuilder(message).toPrettyString()
def client = HttpClient.newHttpClient()
def request = HttpRequest.newBuilder()
.uri(URI.create(slackWebhookUrl))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build()
try {
client.send(request, HttpResponse.BodyHandlers.ofString())
} catch (Exception e) {
log.error("Failed to send Slack notification: ${e.message}")
}
Интеграция с CI/CD
Обновление статуса задачи в Jira при деплое из CI/CD системы:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.MutableIssue
import groovy.json.JsonSlurper
// Данные приходят через REST API эндпоинт
def requestBody = httpMethod.getRequestBodyAsString()
def json = new JsonSlurper().parseText(requestBody)
def issueKey = json.issueKey
def deployStatus = json.status // "success" или "failure"
def issueManager = ComponentAccessor.getIssueManager()
def issue = issueManager.getIssueObject(issueKey)
if (!issue) {
return Response.status(404).entity([error: "Issue not found"]).build()
}
def mutableIssue = issueManager.getIssueObject(issue.id) as MutableIssue
// Обновляем кастомное поле "Deploy Status"
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def deployStatusField = customFieldManager.getCustomFieldObjectByName("Deploy Status")
mutableIssue.setCustomFieldValue(deployStatusField, deployStatus)
def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
issueManager.updateIssue(currentUser, mutableIssue,
com.atlassian.jira.event.type.EventDispatchOption.ISSUE_UPDATED, false)
return Response.ok([success: true]).build()
Синхронизация с базой данных
Пример синхронизации данных задачи с внешней базой данных:
import groovy.sql.Sql
import com.atlassian.jira.component.ComponentAccessor
def issue = event.issue
// Подключение к базе данных (настройте под вашу БД)
def dbUrl = "jdbc:postgresql://db.company.com:5432/external_db"
def dbUser = "jira_sync"
def dbPassword = "password"
def sql = Sql.newInstance(dbUrl, dbUser, dbPassword, "org.postgresql.Driver")
try {
// Проверяем, существует ли запись
def existing = sql.firstRow(
"SELECT id FROM jira_sync WHERE jira_key = ?",
[issue.key]
)
if (existing) {
// Обновляем существующую запись
sql.executeUpdate(
"""UPDATE jira_sync
SET summary = ?, status = ?, updated_at = NOW()
WHERE jira_key = ?""",
[issue.summary, issue.status.name, issue.key]
)
} else {
// Создаём новую запись
sql.executeInsert(
"""INSERT INTO jira_sync (jira_key, summary, status, created_at, updated_at)
VALUES (?, ?, ?, NOW(), NOW())""",
[issue.key, issue.summary, issue.status.name]
)
}
} catch (Exception e) {
log.error("Failed to sync to database: ${e.message}")
} finally {
sql.close()
}
Получение данных из внешних систем
Пример получения данных из внешнего API и обновления задачи:
import groovy.json.JsonSlurper
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.net.URI
def issue = event.issue
def externalApiUrl = "https://external-api.company.com/data/${issue.key}"
def client = HttpClient.newHttpClient()
def request = HttpRequest.newBuilder()
.uri(URI.create(externalApiUrl))
.header("Authorization", "Bearer YOUR_API_TOKEN")
.GET()
.build()
try {
def response = client.send(request, HttpResponse.BodyHandlers.ofString())
if (response.statusCode() == 200) {
def json = new JsonSlurper().parseText(response.body())
// Обновляем кастомное поле на основе данных из внешней системы
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def externalDataField = customFieldManager.getCustomFieldObjectByName("External Data")
def mutableIssue = ComponentAccessor.getIssueManager().getIssueObject(issue.id) as MutableIssue
mutableIssue.setCustomFieldValue(externalDataField, json.value)
def currentUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
ComponentAccessor.getIssueManager().updateIssue(currentUser, mutableIssue,
com.atlassian.jira.event.type.EventDispatchOption.DO_NOT_DISPATCH, false)
}
} catch (Exception e) {
log.error("Failed to fetch external data: ${e.message}")
}
Обработка ошибок и повторные попытки
При интеграциях важно обрабатывать ошибки и реализовывать механизм повторных попыток:
def maxRetries = 3
def retryDelay = 1000 // миллисекунды
def attemptRequest(url, payload) {
for (int i = 0; i < maxRetries; i++) {
try {
def response = sendRequest(url, payload)
if (response.statusCode() >= 200 && response.statusCode() < 300) {
return response
}
} catch (Exception e) {
log.warn("Attempt ${i + 1} failed: ${e.message}")
if (i < maxRetries - 1) {
Thread.sleep(retryDelay * (i + 1)) // Экспоненциальная задержка
}
}
}
throw new Exception("Failed after ${maxRetries} attempts")
}
Выводы
ScriptRunner позволяет создавать мощные интеграции Jira с внешними системами без разработки плагинов. Используйте REST API для обмена данными, обрабатывайте ошибки, реализуйте повторные попытки для надёжности.
Всегда проверяйте аутентификацию при работе с внешними API, логируйте все операции для отладки, тестируйте интеграции на тестовых средах перед применением на production.
Если нужна помощь с созданием интеграций — свяжитесь со мной.