Массовые операции с задачами — одна из самых частых задач в администрировании Jira. Нужно обновить поля у сотен задач, переназначить исполнителей, изменить приоритеты. Делать это вручную через интерфейс неэффективно, а стандартные инструменты Jira ограничены. ScriptRunner позволяет выполнять массовые операции эффективно и безопасно. В этой статье разберу практические подходы к массовым операциям.
Поиск задач для массовых операций
Первый шаг — найти задачи, с которыми нужно работать. Используйте JQL для этого:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.web.bean.PagerFilter
def searchService = ComponentAccessor.getComponent(SearchService)
def userManager = ComponentAccessor.getUserManager()
def user = userManager.getUserByKey("admin")
def jql = "project = PROJ AND status = 'In Progress'"
def query = searchService.parseQuery(user, jql)
def results = searchService.search(user, query.query, PagerFilter.getUnlimitedFilter())
def issues = results.results
Теперь у вас есть список задач для обработки.
Массовое обновление полей
Обновляем поле у всех найденных задач:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.MutableIssue
def issueManager = ComponentAccessor.getIssueManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def userManager = ComponentAccessor.getUserManager()
def currentUser = userManager.getUserByKey("admin")
def field = customFieldManager.getCustomFieldObjectByName("My Field")
def newValue = "New Value"
def updated = 0
def errors = 0
issues.each { issue ->
try {
def mutableIssue = issueManager.getIssueObject(issue.id) as MutableIssue
mutableIssue.setCustomFieldValue(field, newValue)
issueManager.updateIssue(currentUser, mutableIssue,
com.atlassian.jira.event.type.EventDispatchOption.DO_NOT_DISPATCH, false)
updated++
} catch (Exception e) {
log.error("Failed to update ${issue.key}: ${e.message}")
errors++
}
}
log.warn("Updated: ${updated}, Errors: ${errors}")
Обработка больших объёмов данных
Для больших объёмов данных обрабатывайте задачи пакетами, чтобы не перегружать систему:
def batchSize = 100
def total = issues.size()
def processed = 0
issues.collate(batchSize).eachWithIndex { batch, batchIndex ->
log.warn("Processing batch ${batchIndex + 1}, tasks ${processed + 1}-${processed + batch.size()}")
batch.each { issue ->
try {
// Обновление задачи
updateIssue(issue)
processed++
} catch (Exception e) {
log.error("Failed to process ${issue.key}: ${e.message}")
}
}
// Небольшая пауза между пакетами
Thread.sleep(1000)
}
log.warn("Processed ${processed} of ${total} issues")
Массовое переназначение
Переназначаем задачи на нового исполнителя:
def newAssigneeKey = "john.doe"
def newAssignee = userManager.getUserByKey(newAssigneeKey)
if (!newAssignee) {
log.error("User ${newAssigneeKey} not found")
return
}
issues.each { issue ->
try {
def mutableIssue = issueManager.getIssueObject(issue.id) as MutableIssue
mutableIssue.setAssignee(newAssignee)
issueManager.updateIssue(currentUser, mutableIssue,
com.atlassian.jira.event.type.EventDispatchOption.ISSUE_UPDATED, false)
} catch (Exception e) {
log.error("Failed to assign ${issue.key}: ${e.message}")
}
}
Условное обновление
Обновляем задачи на основе условий:
def priorityField = customFieldManager.getCustomFieldObjectByName("Priority")
issues.each { issue ->
try {
def mutableIssue = issueManager.getIssueObject(issue.id) as MutableIssue
// Обновляем только задачи определённого типа
if (issue.issueType.name == "Bug") {
mutableIssue.setPriority(ComponentAccessor.getConstantsManager().getPriorityObject("High"))
}
issueManager.updateIssue(currentUser, mutableIssue,
com.atlassian.jira.event.type.EventDispatchOption.ISSUE_UPDATED, false)
} catch (Exception e) {
log.error("Failed to update ${issue.key}: ${e.message}")
}
}
Массовое создание задач
Создаём задачи из списка данных:
import com.atlassian.jira.issue.IssueFactory
def issueFactory = ComponentAccessor.getIssueFactory()
def projectManager = ComponentAccessor.getProjectManager()
def project = projectManager.getProjectObjByKey("PROJ")
def tasksData = [
[summary: "Task 1", description: "Description 1"],
[summary: "Task 2", description: "Description 2"],
// ... больше задач
]
tasksData.each { taskData ->
try {
def issue = issueFactory.getIssue()
issue.setProjectObject(project)
issue.setIssueTypeId("10001") // Story
issue.setSummary(taskData.summary)
issue.setDescription(taskData.description)
issue.setReporter(currentUser)
def createdIssue = issueManager.createIssueObject(currentUser, issue)
log.warn("Created: ${createdIssue.key}")
} catch (Exception e) {
log.error("Failed to create task: ${e.message}")
}
}
Безопасность массовых операций
При массовых операциях важно соблюдать безопасность:
- Тестируйте на малых объёмах — сначала проверьте на 5-10 задачах
- Делайте бэкап — перед массовыми операциями создайте бэкап
- Логируйте действия — записывайте все изменения для возможности отката
- Используйте транзакции — где возможно, группируйте изменения
Оптимизация производительности
Для больших объёмов данных оптимизируйте операции:
- Обрабатывайте пакетами — не обрабатывайте все задачи сразу
- Используйте DO_NOT_DISPATCH — если не нужны события, отключайте их
- Избегайте лишних запросов — кэшируйте объекты, которые используются многократно
- Делайте паузы — между пакетами делайте небольшие паузы
Выводы
ScriptRunner позволяет эффективно выполнять массовые операции с задачами в Jira. Используйте JQL для поиска задач, обрабатывайте пакетами для больших объёмов, всегда тестируйте на малых объёмах перед применением на production.
Если нужна помощь с массовыми операциями — свяжитесь со мной.