120 lines
4.6 KiB
Groovy
120 lines
4.6 KiB
Groovy
// ScriptRunner Cloud - Scheduled Job
|
||
// 1) Issues per JQL suchen (/rest/api/3/search/jql + nextPageToken)
|
||
// 2) Transition nach "Closed" (per ID ODER Ziel-Status ODER Aktionsname)
|
||
// 3) DueDate leeren
|
||
// 4) Kommentar (ADF) hinzufügen
|
||
|
||
// ===================== Konfiguration =====================
|
||
def JQL = 'project = "Customer Service Desk" AND status = Resolved AND duedate IS NOT EMPTY AND due <= endOfDay()'
|
||
def MAX_PER_PAGE = 50
|
||
|
||
// Wenn du die Transition-ID kennst, HIER setzen (als String). Beispiel: "331" für "Schließen".
|
||
// Wenn null/leer, wird via Name gesucht (erst Ziel-Status, dann Aktionsname).
|
||
final String TRANSITION_ID = "331" // <- bei dir vorhanden; sonst auf null setzen
|
||
final String TARGET_STATUS_NAME = "Closed" // Ziel-Statusname
|
||
final List<String> ACTION_NAME_FALLBACKS = ["Schließen", "Close", "Closed"] // Aktionsnamen-Fallbacks
|
||
|
||
// Kommentartext (als ADF gesendet)
|
||
def commentText = """
|
||
Dieses Ticket wurde automatisch geschlossen, nachdem der Support die Lösung präsentiert hat und wir davon ausgehen, dass die Lösung korrekt ist.
|
||
Sollten weiterhin Fragen bestehen oder erneut Unterstützung benötigt werden, können Sie eine neue Anfrage stellen.
|
||
Ihr pds Support
|
||
""".trim()
|
||
// ========================================================
|
||
|
||
def adfBody = [
|
||
type: "doc",
|
||
version: 1,
|
||
content: [[ type: "paragraph", content: [[ type: "text", text: commentText ]] ]]
|
||
]
|
||
|
||
// --- Helper: Transition-ID bestimmen ---
|
||
def resolveTransitionId = { String issueKey ->
|
||
if (TRANSITION_ID?.trim()) {
|
||
return TRANSITION_ID.trim()
|
||
}
|
||
def resp = get("/rest/api/3/issue/${issueKey}/transitions").asObject(Map)
|
||
assert resp.status == 200 : "Transitions nicht lesbar für ${issueKey}: ${resp.status} - ${resp.body}"
|
||
|
||
def transitions = (resp.body?.transitions as List) ?: []
|
||
|
||
// 1) nach Ziel-Status (to.name)
|
||
def byTargetStatus = transitions.find { it?.to?.name?.equalsIgnoreCase(TARGET_STATUS_NAME) }
|
||
if (byTargetStatus?.id) return byTargetStatus.id as String
|
||
|
||
// 2) nach Aktionsname (name)
|
||
def byActionName = transitions.find { t ->
|
||
ACTION_NAME_FALLBACKS.any { fn -> t?.name?.equalsIgnoreCase(fn) }
|
||
}
|
||
return byActionName?.id as String
|
||
}
|
||
|
||
// --- Helper: Transition ausführen ---
|
||
def doTransition = { String issueKey, String transitionId ->
|
||
def resp = post("/rest/api/3/issue/${issueKey}/transitions")
|
||
.header("Content-Type", "application/json")
|
||
.body([ transition: [ id: transitionId ] ])
|
||
.asString()
|
||
assert resp.status == 204 : "Transition fehlgeschlagen für ${issueKey}: ${resp.status} - ${resp.body}"
|
||
}
|
||
|
||
// --- Helper: DueDate leeren ---
|
||
def clearDueDate = { String issueKey ->
|
||
def resp = put("/rest/api/3/issue/${issueKey}")
|
||
.header("Content-Type", "application/json")
|
||
.body([ fields: [ duedate: null ] ])
|
||
.asString()
|
||
assert resp.status == 204 : "DueDate leeren fehlgeschlagen für ${issueKey}: ${resp.status} - ${resp.body}"
|
||
}
|
||
|
||
// --- Helper: Kommentar hinzufügen (ADF) ---
|
||
def addComment = { String issueKey, Map adf ->
|
||
def resp = post("/rest/api/3/issue/${issueKey}/comment")
|
||
.header("Content-Type", "application/json")
|
||
.body([ body: adf ])
|
||
.asObject(Map)
|
||
assert resp.status == 201 : "Kommentar fehlgeschlagen für ${issueKey}: ${resp.status} - ${resp.body}"
|
||
}
|
||
|
||
// --- Suche mit neuer API und Token-Pagination ---
|
||
def processed = 0
|
||
String nextPageToken = null
|
||
boolean isLast = false
|
||
|
||
while (!isLast) {
|
||
def req = get("/rest/api/3/search/jql")
|
||
.queryString("jql", JQL)
|
||
.queryString("fields", "status,duedate") // key ist immer dabei
|
||
.queryString("maxResults", MAX_PER_PAGE as String)
|
||
if (nextPageToken) req = req.queryString("nextPageToken", nextPageToken)
|
||
|
||
def searchResp = req.asObject(Map)
|
||
assert searchResp.status == 200 : "Search-Fehler: ${searchResp.status} - ${searchResp.body}"
|
||
|
||
def body = searchResp.body as Map
|
||
def issues = (body?.issues as List) ?: []
|
||
isLast = (body?.isLast == true)
|
||
nextPageToken = body?.nextPageToken as String
|
||
|
||
issues.each { Map iss ->
|
||
def key = iss["key"] as String
|
||
try {
|
||
def transitionId = resolveTransitionId(key)
|
||
if (!transitionId) {
|
||
logger.warn("Keine passende Transition für ${key} gefunden (Status='${TARGET_STATUS_NAME}', Fallbacks=${ACTION_NAME_FALLBACKS}) – übersprungen.")
|
||
return
|
||
}
|
||
doTransition(key, transitionId)
|
||
clearDueDate(key)
|
||
addComment(key, adfBody)
|
||
|
||
processed++
|
||
logger.info("OK: ${key} -> Transition=${transitionId}, DueDate geleert, Kommentar gesetzt.")
|
||
} catch (Throwable t) {
|
||
logger.error("Fehler bei ${key}: ${t.message}")
|
||
}
|
||
}
|
||
}
|
||
|
||
logger.info("Fertig. Verarbeitete Tickets: ${processed} (JQL: ${JQL})")
|