← Назад к статьям

Планировщик задач в ScriptRunner: автоматизация по расписанию

Многие задачи в Jira требуют периодического выполнения: архивация старых задач, обновление статусов, отправка отчётов, синхронизация данных. ScriptRunner предоставляет планировщик задач (Scheduled Jobs), который позволяет выполнять скрипты по расписанию. В этой статье разберу практические примеры использования планировщика для автоматизации рутинных операций.

Зачем нужны запланированные задачи

Планировщик задач полезен для:

  • Периодической архивации старых данных
  • Автоматического обновления статусов задач
  • Генерации и отправки отчётов
  • Синхронизации данных с внешними системами
  • Очистки временных данных
  • Проверки и уведомлений о проблемах

Создание запланированной задачи

Создайте задачу через: Administration → ScriptRunner → Scheduled Jobs → Add job.

Пример задачи, которая выполняет проверку просроченных задач каждый день:

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 customFieldManager = ComponentAccessor.getCustomFieldManager()
def userManager = ComponentAccessor.getUserManager()
def user = userManager.getUserByKey("admin")

def dueDateField = customFieldManager.getCustomFieldObjectByName("Due Date")
def now = new Date()

// Находим просроченные задачи
def jql = "resolution = Unresolved AND dueDate < now()"
def query = searchService.parseQuery(user, jql)
def results = searchService.search(user, query.query, PagerFilter.getUnlimitedFilter())

def overdueCount = results.total
log.warn("Found ${overdueCount} overdue issues")

// Можно отправить уведомление или выполнить другие действия
results.results.each { issue ->
    log.warn("Overdue issue: ${issue.key} - ${issue.summary}")
}

Настройка расписания

ScriptRunner использует cron-синтаксис для настройки расписания. Примеры:

  • 0 0 * * * ? — каждый день в полночь
  • 0 0 9 * * ? — каждый день в 9:00
  • 0 0 0 * * MON — каждый понедельник в полночь
  • 0 0/30 * * * ? — каждые 30 минут
  • 0 0 0 1 * ? — первого числа каждого месяца

Архивация старых задач

Пример задачи для архивации закрытых задач старше года:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.web.bean.PagerFilter
import java.util.Calendar

def searchService = ComponentAccessor.getComponent(SearchService)
def userManager = ComponentAccessor.getUserManager()
def user = userManager.getUserByKey("admin")

// Дата год назад
def calendar = Calendar.getInstance()
calendar.add(Calendar.YEAR, -1)
def oneYearAgo = calendar.getTime()

def jql = "resolution != Unresolved AND resolved < '${new java.text.SimpleDateFormat("yyyy-MM-dd").format(oneYearAgo)}'"
def query = searchService.parseQuery(user, jql)
def results = searchService.search(user, query.query, PagerFilter.getUnlimitedFilter())

log.warn("Found ${results.total} issues to archive")

// Здесь можно переместить задачи в архивный проект или пометить их
results.results.each { issue ->
    log.warn("Archiving: ${issue.key}")
    // Логика архивации
}

Автоматическое обновление статусов

Автоматически переводим задачи в статус "Done" при отсутствии активности:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.workflow.WorkflowManager
import java.util.Calendar

def searchService = ComponentAccessor.getComponent(SearchService)
def issueManager = ComponentAccessor.getIssueManager()
def workflowManager = ComponentAccessor.getWorkflowManager()
def userManager = ComponentAccessor.getUserManager()
def user = userManager.getUserByKey("admin")

// Задачи, не обновлявшиеся более 90 дней
def calendar = Calendar.getInstance()
calendar.add(Calendar.DAY_OF_MONTH, -90)
def cutoffDate = calendar.getTime()

def jql = "status = 'In Progress' AND updated < '${new java.text.SimpleDateFormat("yyyy-MM-dd").format(cutoffDate)}'"
def query = searchService.parseQuery(user, jql)
def results = searchService.search(user, query.query, PagerFilter.getUnlimitedFilter())

results.results.each { issue ->
    try {
        def mutableIssue = issueManager.getIssueObject(issue.id) as MutableIssue
        def workflow = workflowManager.getWorkflow(mutableIssue)
        def action = workflow.getLinkedStatus(mutableIssue.status.id)?.getActions().find { 
            it.getName() == "Close Issue"
        }
        
        if (action) {
            workflowManager.transitionIssue(user, mutableIssue, action.getId(), null)
            log.warn("Auto-closed: ${issue.key}")
        }
    } catch (Exception e) {
        log.error("Failed to close ${issue.key}: ${e.message}")
    }
}

Генерация и отправка отчётов

Генерируем еженедельный отчёт и отправляем его по email:

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 projectKey = "PROJ"
def jql = "project = ${projectKey} AND created >= -7d"
def query = searchService.parseQuery(user, jql)
def results = searchService.search(user, query.query, PagerFilter.getUnlimitedFilter())

def report = new StringBuilder()
report.append("Weekly Report for Project ${projectKey}\n")
report.append("=====================================\n\n")
report.append("New issues this week: ${results.total}\n\n")

// Статистика по статусам
def statuses = ["Open", "In Progress", "Done"]
statuses.each { status ->
    def statusJql = "project = ${projectKey} AND status = '${status}'"
    def statusQuery = searchService.parseQuery(user, statusJql)
    def statusResults = searchService.search(user, statusQuery.query, PagerFilter.getUnlimitedFilter())
    report.append("${status}: ${statusResults.total}\n")
}

log.warn(report.toString())

// Здесь можно отправить отчёт по email через MailService или внешний API

Обработка ошибок в запланированных задачах

Важно правильно обрабатывать ошибки в запланированных задачах, чтобы они не падали и не блокировали выполнение:

def issues = getIssuesToProcess()

issues.each { issue ->
    try {
        processIssue(issue)
    } catch (Exception e) {
        log.error("Error processing ${issue.key}: ${e.message}", e)
        // Продолжаем обработку остальных задач
    }
}

Мониторинг запланированных задач

Регулярно проверяйте логи выполнения запланированных задач. Настройте алерты на ошибки, чтобы знать, когда задачи не выполняются или завершаются с ошибками.

Выводы

Планировщик задач ScriptRunner — мощный инструмент для автоматизации периодических операций. Используйте его для архивации, обновления статусов, генерации отчётов и других рутинных задач.

Всегда обрабатывайте ошибки, логируйте результаты выполнения, тестируйте задачи перед добавлением в production. Настройте мониторинг для отслеживания выполнения задач.

Если нужна помощь с настройкой запланированных задач — свяжитесь со мной.