import groovy.json.JsonOutput // ------------------ Konfig ------------------ boolean DRY_RUN = true int PAGE_SIZE = 50 Set EXCLUDE_BY_NAME = [] as Set // optional Namen schützen // ------------------ Logging ----------------- void logInfo(String m){ try{ logger.info(m) }catch(e){ println m } } void logWarn(String m){ try{ logger.warn(m) }catch(e){ println "WARN: " + m } } void logErr (String m){ try{ logger.error(m)}catch(e){ println "ERR: " + m } } // ------------------ HTTP Helpers ----------- Map getAsMap(String path, Map q=[:]) { def req = get(path) q.each{ k,v -> req = req.queryString(k, v) } def r = req.asObject(Map) if (r.status != 200) throw new RuntimeException("GET " + path + " failed: HTTP " + r.status + " :: " + r.body) (r.body ?: [:]) as Map } List pagedGetValues(String path, int pageSize=50) { int startAt = 0; List all = [] while (true) { Map body = getAsMap(path, [startAt:startAt, maxResults:pageSize]) List vals = (body.values ?: []) as List all.addAll(vals as List) int total = (body.total ?: (startAt + vals.size())) as int int nextStart = startAt + ((body.maxResults ?: vals.size()) as int) if (vals.isEmpty() || nextStart >= total) break startAt = nextStart } all } // ------------------ Fetchers ---------------- List fetchITSS(int pageSize){ logInfo("Lade Issue Type Screen Schemes…") def list = pagedGetValues("/rest/api/3/issuetypescreenscheme", pageSize) logInfo("ITSS gefunden: " + list.size()) list } List fetchProjects(int pageSize){ logInfo("Lade Projekte…") def list = pagedGetValues("/rest/api/3/project/search", pageSize) logInfo("Projekte gefunden: " + list.size()) list } Long fetchITSSForProject(Long projectId){ // Liefert die ITSS-ID, die einem Projekt zugewiesen ist def m = getAsMap("/rest/api/3/project/${projectId}/issuetypescreenscheme") def id = m.get("issueTypeScreenSchemeId") return (id == null ? null : Long.valueOf(id.toString())) } // ------------------ Delete ------------------ boolean deleteITSS(long id, String name){ def resp = delete("/rest/api/3/issuetypescreenscheme/${id}").asString() if (resp.status in [200,204]) { logInfo("Gelöscht: [${id}] ${name}"); return true } logWarn("Nicht gelöscht [${id}] ${name} :: HTTP ${resp.status} :: ${resp.body}") false } // ------------------ Main -------------------- void runITSSCleanup(boolean dryRun, int pageSize, Set excludeByName){ def itss = fetchITSS(pageSize) if (itss.isEmpty()){ logInfo("Keine ITSS vorhanden – nichts zu tun."); return } Map itssById = [:] itss.each{ Map x -> if (x.id!=null) itssById[Long.valueOf(x.id.toString())] = x } def projects = fetchProjects(pageSize) Set referenced = new LinkedHashSet<>() projects.each{ Map p -> def pid = p.get("id"); if (pid==null) return try { Long ref = fetchITSSForProject(Long.valueOf(pid.toString())) if (ref!=null) referenced << ref } catch (Exception ex) { logWarn("ITSS-Mapping für Projekt ${p.key ?: pid} nicht lesbar: " + ex.message) } } logInfo("Referenzierte ITSS gesamt: " + referenced.size()) List candidates = [] itssById.each{ Long id, Map row -> String name = (row.name ?: "") as String if (!referenced.contains(id) && !excludeByName.contains(name)){ candidates << [id:id, name:name, description:(row.description ?: "")] } } candidates.sort{ a,b -> a.name <=> b.name } if (candidates.isEmpty()){ logInfo("Keine ungenutzten ITSS gefunden. ✅"); return } logWarn("Ungenutzte ITSS (${candidates.size()}):") candidates.each{ c -> logWarn(" - [${c.id}] ${c.name}") } if (dryRun){ logInfo("Dry-Run aktiv → nichts gelöscht.") logInfo("JSON:\n" + JsonOutput.prettyPrint(JsonOutput.toJson(candidates))) return } int deleted=0, skipped=0 candidates.each{ c -> try { if (deleteITSS((c.id as Long), c.name.toString())) deleted++ else skipped++ } catch (Exception ex){ skipped++; logErr("Fehler beim Löschen [${c.id}] ${c.name} :: " + ex.message) } } logWarn("Fertig. Ergebnis: deleted=${deleted}, skipped=${skipped}") } // ---- Start ---- runITSSCleanup(DRY_RUN, PAGE_SIZE, EXCLUDE_BY_NAME)