// ----------------------------------------------------------------------------- // Housekeeping: Inaktive Workflow Schemes (ohne Projektzuordnung) löschen // Jira Cloud - ScriptRunner Console // ----------------------------------------------------------------------------- import groovy.json.JsonOutput // --- Konfiguration ----------------------------------------------------------- // false -> inaktive Workflow Schemes (ohne EXCLUDED_IDS) werden GELÖSCHT // true -> nur Testlauf, es wird NICHT gelöscht final boolean DRY_RUN = true // Workflow-Scheme-IDs, die NIEMALS gelöscht werden sollen // (z.B. Default-System-Schema; ID bitte ggf. anpassen/ergänzen) final Set EXCLUDED_IDS = [10000L] as Set // --- Schritt 1: Alle Workflow Schemes laden (paginiert) --------------------- List allSchemes = [] int startAt = 0 int maxResults = 50 boolean finished = false while (!finished) { def resp = get("/rest/api/3/workflowscheme?startAt=${startAt}&maxResults=${maxResults}") .asObject(Map) if (resp.status != 200) { logger.error("Konnte Workflow Schemes nicht laden (startAt=${startAt}): ${resp.status} - ${resp.body}") return "Fehler beim Laden der Workflow 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 "Workflow 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 Workflow 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: Mappings Workflow Scheme <-> Projekte laden ----------------- // // GET /rest/api/3/workflowscheme/project // liefert PageBean mit values[ { workflowSchemeId, projectId, projectKey, ... } ] List allMappings = [] startAt = 0 finished = false while (!finished) { def resp = get("/rest/api/3/workflowscheme/project?startAt=${startAt}&maxResults=${maxResults}") .asObject(Map) if (resp.status != 200) { logger.error("Konnte Workflow-Scheme-Mappings nicht laden (startAt=${startAt}): ${resp.status} - ${resp.body}") break } def body = resp.body ?: [:] def values = (body.values ?: []) as List allMappings.addAll(values) boolean isLast = (body.isLast == true) int total = (body.total ?: (startAt + values.size())) as int logger.info "Workflow-Scheme-Mappings geladen: ${allMappings.size()} (total ~ ${total}), isLast=${isLast}" if (isLast || values.isEmpty()) { finished = true } else { startAt += maxResults if (startAt >= total) { finished = true } } } // Mappings in schemeUsage eintragen allMappings.each { m -> Long schemeId = (m.workflowSchemeId as Long) String projKey = m.projectKey?.toString() def entry = schemeUsage[schemeId] if (entry) { entry.used = true if (projKey) { entry.projects << projKey } } else { logger.warn "Mapping gefunden für Workflow Scheme ID=${schemeId}, das nicht in allSchemes war. Projekt=${projKey}" } } def projectsWithWorkflowScheme = allMappings.collect { it.projectKey }.findAll { it }.toSet() logger.info "Anzahl Projekte mit Workflow Scheme: ${projectsWithWorkflowScheme.size()}" // --- Schritt 3: Inaktive (unbenutzte & nicht ausgeschlossene) Schemes ------- def inactive = 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 "Ausgeschlossene Workflow-Scheme-IDs : ${EXCLUDED_IDS.join(', ')}" logger.info "Inaktive Workflow Schemes (Kandidaten): ${inactive.size()}" // --- Schritt 4: Optional löschen -------------------------------------------- List deleted = [] List failed = [] if (!DRY_RUN) { inactive.each { entry -> def s = entry.scheme Long id = (s.id as Long) logger.info "Lösche Workflow Scheme ID=${id}, Name=\"${s.name}\" ..." def delResp = delete("/rest/api/3/workflowscheme/${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 << "=== Workflow Schemes Housekeeping ===" lines << "DRY_RUN : ${DRY_RUN}" lines << "Gesamt Workflow Schemes : ${allSchemes.size()}" lines << "Projekte mit Scheme-Mapping : ${projectsWithWorkflowScheme.size()}" lines << "Ausgeschlossene IDs : ${EXCLUDED_IDS.join(', ')}" lines << "Inaktive Kandidaten : ${inactive.size()}" if (!DRY_RUN) { lines << "Gelöscht : ${deleted.size()}" lines << "Fehlgeschlagen : ${failed.size()}" } lines << "" lines << "Inaktive (unbenutzte) Schemes, exkl. EXCLUDED_IDS:" inactive.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, totalWorkflowSchemes : allSchemes.size(), projectsWithMapping : projectsWithWorkflowScheme.size(), excludedIDs : EXCLUDED_IDS, inactiveCandidates : inactive.size(), deleted : deleted.size(), failed : failed.size() ], inactiveWorkflowSchemes: inactive.collect { e -> def s = e.scheme [ id : s.id, name : s.name, description : s.description, projectsUsing: e.projects ] }, deletedWorkflowSchemes: deleted, failedDeletions : failed ] logger.info lines.join("\n") return lines.join("\n") + "\n\nJSON:\n" + JsonOutput.prettyPrint(JsonOutput.toJson(result))