/** * Screen Scheme Housekeeping (Cloud) – über Issue Type Screen Scheme Mappings (zuverlässig) * -------------------------------------------------------------------------------------- * Löschen nur, wenn ein Screen Scheme nirgendwo in IssueTypeScreenScheme-Mappings referenziert ist. * Jira verhindert Delete sowieso, wenn es noch referenziert wird. :contentReference[oaicite:2]{index=2} */ def PROJECT_KEYS = ["NIN","NICS","NINPDS","NINPDSARC","CS","CRON"] def PROTECTED_SCHEME_IDS = [ // "1" // Default Screen Scheme ggf. hart schützen ] def PROTECTED_NAME_PATTERNS = [ "default", "system", "jira" ] def DRY_RUN = true def PROJECT_PAGE_SIZE = 50 def SCREEN_SCHEME_PAGE_SIZE = 100 def MAPPING_PAGE_SIZE = 100 logger.info("=== Screen Scheme Housekeeping (via IssueTypeScreenScheme mappings) ===") logger.info("Projects: ${PROJECT_KEYS}") logger.info("Protected scheme IDs: ${PROTECTED_SCHEME_IDS}") logger.info("Protected name patterns: ${PROTECTED_NAME_PATTERNS}") logger.info("DRY_RUN: ${DRY_RUN}") def isNameProtected = { String name -> def n = (name ?: "").toLowerCase() PROTECTED_NAME_PATTERNS.any { p -> n.contains((p ?: "").toLowerCase()) } } def isIdProtected = { String id -> PROTECTED_SCHEME_IDS.any { it?.toString() == id?.toString() } } /** * 1) Project Key -> Project ID */ def projectIds = [:] // key -> id PROJECT_KEYS.each { key -> def resp = get("/rest/api/3/project/${key}").asObject(Map) if (resp.status == 200) { projectIds[key] = resp.body?.id?.toString() logger.info("INFO|PROJECT|${key}|id=${projectIds[key]}") } else { logger.warn("WARN|PROJECT_LOOKUP_FAILED|${key}|status=${resp.status}") } } def validProjectIds = projectIds.values().findAll { it != null }.unique() /** * 2) Aus Projekten die IssueTypeScreenSchemeIds einsammeln * GET /rest/api/3/issuetypescreenscheme/project?projectId=... */ def issueTypeScreenSchemeIds = [] as Set validProjectIds.each { pid -> def resp = get("/rest/api/3/issuetypescreenscheme/project?projectId=${pid}") .asObject(Map) if (resp.status != 200) { logger.warn("WARN|ITSCS_FOR_PROJECT_FAILED|projectId=${pid}|status=${resp.status}") return } (resp.body?.values ?: []).each { row -> def itscsId = row?.issueTypeScreenScheme?.id?.toString() if (itscsId) issueTypeScreenSchemeIds << itscsId } } logger.info("INFO|ISSUETYPE_SCREENSCHEME_IDS|count=${issueTypeScreenSchemeIds.size()}|ids=${issueTypeScreenSchemeIds}") /** * 3) Für jedes IssueTypeScreenSchemeId: Mappings holen und alle screenSchemeIds sammeln * GET /rest/api/3/issuetypescreenscheme/mapping?issueTypeScreenSchemeId=... (paging) */ def referencedScreenSchemeIds = [] as Set issueTypeScreenSchemeIds.each { itscsId -> def startAt = 0 while (true) { def resp = get("/rest/api/3/issuetypescreenscheme/mapping?issueTypeScreenSchemeId=${itscsId}&startAt=${startAt}&maxResults=${MAPPING_PAGE_SIZE}") .asObject(Map) if (resp.status != 200) { logger.warn("WARN|ITSCS_MAPPING_FAILED|itscsId=${itscsId}|status=${resp.status}") break } def values = resp.body?.values ?: [] values.each { m -> def screenSchemeId = m?.screenSchemeId?.toString() if (screenSchemeId) referencedScreenSchemeIds << screenSchemeId } def isLast = resp.body?.isLast if (isLast == true || values.isEmpty()) break startAt += MAPPING_PAGE_SIZE } } logger.info("INFO|REFERENCED_SCREENSCHEME_IDS|count=${referencedScreenSchemeIds.size()}") /** * 4) Alle Screen Schemes holen, Kandidaten bestimmen */ def allScreenSchemes = [] def startAt = 0 while (true) { def resp = get("/rest/api/3/screenscheme?startAt=${startAt}&maxResults=${SCREEN_SCHEME_PAGE_SIZE}") .asObject(Map) if (resp.status != 200) { logger.error("ERROR|SCREENSCHEME_LIST_FAILED|status=${resp.status}|body=${resp.body}") break } def values = resp.body?.values ?: [] allScreenSchemes.addAll(values) def isLast = resp.body?.isLast if (isLast == true || values.isEmpty()) break startAt += SCREEN_SCHEME_PAGE_SIZE } logger.info("INFO|TOTAL_SCREEN_SCHEMES|${allScreenSchemes.size()}") def keptReferenced = 0 def keptProtected = 0 def candidates = [] // [id,name,reason] allScreenSchemes.each { ss -> def id = ss?.id?.toString() def name = ss?.name?.toString() def nameProtected = isNameProtected(name) def idProtected = isIdProtected(id) if (nameProtected || idProtected) { keptProtected++ def why = [] if (idProtected) why << "ID_PROTECTED" if (nameProtected) why << "NAME_PROTECTED" logger.info("KEEP|screenSchemeId=${id}|name=${name}|reason=${why}") return } if (referencedScreenSchemeIds.contains(id)) { keptReferenced++ logger.info("KEEP|screenSchemeId=${id}|name=${name}|reason=REFERENCED_BY_ISSUETYPE_SCREENSCHEME") return } logger.info("DEL?|screenSchemeId=${id}|name=${name}|reason=NOT_REFERENCED_ANYWHERE") candidates << [id, name, "NOT_REFERENCED_ANYWHERE"] if (!DRY_RUN) { def delResp = delete("/rest/api/3/screenscheme/${id}").asString() if (delResp.status == 204) { logger.info("DEL|OK|screenSchemeId=${id}|name=${name}") } else { logger.error("DEL|FAIL|screenSchemeId=${id}|name=${name}|status=${delResp.status}|body=${delResp.body}") } } } logger.info("=== SUMMARY ===") logger.info("Total screen schemes: ${allScreenSchemes.size()}") logger.info("Kept (referenced): ${keptReferenced}") logger.info("Kept (protected by rules): ${keptProtected}") logger.info("Delete candidates: ${candidates.size()}") candidates.each { c -> logger.info("CANDIDATE|screenSchemeId=${c[0]}|name=${c[1]}|reason=${c[2]}") } logger.info("=== DONE ===")