// ----------------------------------------------------------------------------- // Housekeeping: Unbenutzte Notification Schemes finden (projektbasiert) // Jira Cloud - ScriptRunner Console // ----------------------------------------------------------------------------- import groovy.json.JsonOutput // --- Konfiguration ----------------------------------------------------------- // true -> nur Testlauf, es wird NICHT gelöscht // false -> unbenutzte Notification Schemes (ohne EXCLUDED_IDS) würden gelöscht // (Löschlogik ist unten schon vorbereitet, aber per Default aus) final boolean DRY_RUN = true // Notification-Scheme-IDs, die auf keinen Fall gelöscht werden sollen // (z.B. Standardschemata / System-Schemata) final Set EXCLUDED_IDS = [10000L] as Set // --- Schritt 1: Alle Notification Schemes laden (paginierte API) ------------ List allSchemes = [] int startAt = 0 int maxResults = 50 boolean finished = false while (!finished) { def resp = get("/rest/api/3/notificationscheme?startAt=${startAt}&maxResults=${maxResults}") .asObject(Map) if (resp.status != 200) { logger.error("Konnte Notification Schemes nicht laden (startAt=${startAt}): ${resp.status} - ${resp.body}") return "Fehler beim Laden der Notification Schemes. Siehe Log." } def body = resp.body ?: [:] def values = (body.values ?: []) as List allSchemes.addAll(values) boolean isLast = (body.isLast == true) int total = (body.total ?: (startAt + values.size())) as int logger.info "Notification Schemes geladen: ${allSchemes.size()} (total ~ ${total}), isLast=${isLast}" if (isLast || values.isEmpty()) { finished = true } else { startAt += maxResults if (startAt >= total) { finished = true } } } logger.info "Anzahl Notification Schemes insgesamt: ${allSchemes.size()}" // Map: schemeId -> [scheme: , used: boolean, projects: Set] def schemeUsage = allSchemes.collectEntries { scheme -> Long id = (scheme.id as Long) [ (id): [ scheme : scheme, used : false, projects: [] as Set ] ] } // --- Schritt 2: Alle Projekte laden und pro Projekt das Notification Scheme holen --- int totalProjects = 0 startAt = 0 finished = false while (!finished) { def projResp = get("/rest/api/3/project/search?startAt=${startAt}&maxResults=${maxResults}") .asObject(Map) if (projResp.status != 200) { logger.error("Konnte Projekte nicht laden (startAt=${startAt}): ${projResp.status} - ${projResp.body}") break } def body = projResp.body ?: [:] def projects = (body.values ?: []) as List totalProjects += projects.size() logger.info "Verarbeite Projekte ${startAt} bis ${startAt + projects.size() - 1} ..." projects.each { proj -> String projectKey = proj.key String projectId = proj.id?.toString() // Für jedes Projekt das zugeordnete Notification Scheme holen def notifResp = get("/rest/api/3/project/${projectId}/notificationscheme") .asObject(Map) if (notifResp.status == 200) { def schemeId = notifResp.body?.id if (schemeId) { Long idLong = (schemeId as Long) def entry = schemeUsage[idLong] if (entry) { entry.used = true entry.projects << projectKey } else { // Projekt nutzt ein Scheme, das nicht in der Liste war (sollte selten sein) logger.warn "Projekt ${projectKey} nutzt Notification Scheme ID=${schemeId}, das nicht in der globalen Liste war." } } } else if (notifResp.status == 404) { // z.B. Team-managed-Projekte, die kein klassisches Notification Scheme haben logger.debug "Projekt ${projectKey} hat kein klassisches Notification Scheme (404)." } else { logger.warn "Konnte Notification Scheme für Projekt ${projectKey} nicht laden: ${notifResp.status} - ${notifResp.body}" } } int total = (body.total ?: totalProjects) as int startAt += maxResults if (startAt >= total) { finished = true } } // --- Schritt 3: Unbenutzte (und nicht ausgeschlossene) Schemes bestimmen ----- def unused = schemeUsage.values() .findAll { entry -> Long id = (entry.scheme.id as Long) !entry.used && !EXCLUDED_IDS.contains(id) } .sort { it.scheme.name?.toString()?.toLowerCase() } logger.info "Projekte insgesamt : ${totalProjects}" logger.info "Ausgeschlossene Notification-IDs : ${EXCLUDED_IDS.join(', ')}" logger.info "Unbenutzte Notification Schemes : ${unused.size()}" // --- Schritt 4: Optional löschen (aktuell noch durch DRY_RUN geschützt) ----- List deleted = [] List failed = [] if (!DRY_RUN) { unused.each { entry -> def s = entry.scheme Long id = (s.id as Long) logger.info "Lösche Notification Scheme ID=${id}, Name=\"${s.name}\" ..." def delResp = delete("/rest/api/3/notificationscheme/${id}") .asString() if (delResp.status in [200, 204]) { logger.info "Erfolgreich gelöscht: ID=${id}, Name=\"${s.name}\"" deleted << [ id : id, name : s.name, description: s.description ] } else { logger.warn "Löschen fehlgeschlagen für ID=${id}, Name=\"${s.name}\": Status=${delResp.status}, Body=${delResp.body}" failed << [ id : id, name : s.name, status : delResp.status, body : delResp.body ] } } } else { logger.info "DRY_RUN = true -> Es wird NICHT gelöscht, nur Kandidaten ermittelt." } // --- Schritt 5: Zusammenfassung --------------------------------------------- def lines = [] lines << "=== Notification Schemes Housekeeping (projektbasiert) ===" lines << "DRY_RUN : ${DRY_RUN}" lines << "Gesamt Notification Schemes : ${allSchemes.size()}" lines << "Gesamt Projekte : ${totalProjects}" lines << "Ausgeschlossene IDs : ${EXCLUDED_IDS.join(', ')}" lines << "Kandidaten (unused) : ${unused.size()}" if (!DRY_RUN) { lines << "Gelöscht : ${deleted.size()}" lines << "Fehlgeschlagen : ${failed.size()}" } lines << "" lines << "Kandidaten (unbenutzte Schemes, exkl. EXCLUDED_IDS):" unused.each { entry -> def s = entry.scheme lines << String.format( "- ID=%s | Name=\"%s\" | Beschreibung=\"%s\" | Projekte=%s", s.id, s.name ?: "", (s.description ?: "").replaceAll('\\s+', ' ').trim(), entry.projects ?: [] ) } def result = [ summary : [ dryRun : DRY_RUN, totalNotificationSchemes: allSchemes.size(), totalProjects : totalProjects, excludedIDs : EXCLUDED_IDS, candidateUnused : unused.size(), deleted : deleted.size(), failed : failed.size() ], candidateUnusedSchemes: unused.collect { e -> def s = e.scheme [ id : s.id, name : s.name, description : s.description, projectsUsing: e.projects ] }, deletedNotificationSchemes: deleted, failedDeletions : failed ] logger.info lines.join("\n") return lines.join("\n") + "\n\nJSON:\n" + JsonOutput.prettyPrint(JsonOutput.toJson(result))