180 lines
5.1 KiB
Groovy
180 lines
5.1 KiB
Groovy
/**
|
||
* Field Configuration Scheme Housekeeping (Cloud) – Report + optional Delete
|
||
* -------------------------------------------------------------------------
|
||
* Delete rule:
|
||
* - Scheme darf nur gelöscht werden, wenn es mit KEINEM Projekt verknüpft ist.
|
||
* - Extra-Schutz über PROTECTED_SCHEME_IDS und PROTECTED_NAME_PATTERNS.
|
||
*/
|
||
|
||
def PROTECTED_SCHEME_IDS = [
|
||
// "10000"
|
||
]
|
||
|
||
def PROTECTED_NAME_PATTERNS = [
|
||
"default",
|
||
"system"
|
||
]
|
||
|
||
def DRY_RUN = true // erst auf false setzen, wenn Report passt
|
||
def PROJECT_PAGE_SIZE = 50 // paging für project/search
|
||
def SCHEME_PAGE_SIZE = 100 // paging für fieldconfigurationscheme
|
||
|
||
logger.info("=== Field Configuration Scheme Housekeeping ===")
|
||
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()
|
||
return PROTECTED_NAME_PATTERNS.any { p -> n.contains((p ?: "").toLowerCase()) }
|
||
}
|
||
def isIdProtected = { String id ->
|
||
return PROTECTED_SCHEME_IDS.any { it?.toString() == id?.toString() }
|
||
}
|
||
|
||
/**
|
||
* 1) Alle Projekte holen (id + key)
|
||
*/
|
||
def projects = []
|
||
def startAt = 0
|
||
while (true) {
|
||
def resp = get("/rest/api/3/project/search?startAt=${startAt}&maxResults=${PROJECT_PAGE_SIZE}")
|
||
.asObject(Map)
|
||
|
||
if (resp.status != 200) {
|
||
logger.error("ERROR|PROJECT_SEARCH_FAILED|status=${resp.status}|body=${resp.body}")
|
||
break
|
||
}
|
||
|
||
def values = resp.body?.values ?: []
|
||
projects.addAll(values)
|
||
|
||
def isLast = resp.body?.isLast
|
||
if (isLast == true || values.isEmpty()) break
|
||
|
||
startAt += PROJECT_PAGE_SIZE
|
||
}
|
||
|
||
logger.info("INFO|TOTAL_PROJECTS|${projects.size()}")
|
||
|
||
def projectIdToKey = [:]
|
||
projects.each { p ->
|
||
def pid = p?.id?.toString()
|
||
def pkey = p?.key?.toString()
|
||
if (pid && pkey) projectIdToKey[pid] = pkey
|
||
}
|
||
|
||
/**
|
||
* 2) Mapping: fieldConfigurationSchemeId -> [projectIds]
|
||
* (Endpoint braucht projectId, daher über alle Projekte iterieren)
|
||
*
|
||
* REST: GET /rest/api/3/fieldconfigurationscheme/project?projectId={projectId}
|
||
*/
|
||
def schemeToProjectIds = [:].withDefault { [] }
|
||
|
||
projectIdToKey.keySet().each { pid ->
|
||
def resp = get("/rest/api/3/fieldconfigurationscheme/project?projectId=${pid}")
|
||
.asObject(Map)
|
||
|
||
if (resp.status != 200) {
|
||
logger.warn("WARN|SCHEME_LOOKUP_FAILED|projectId=${pid}|status=${resp.status}")
|
||
return
|
||
}
|
||
|
||
def values = resp.body?.values ?: []
|
||
values.each { row ->
|
||
def schemeId = row?.fieldConfigurationScheme?.id?.toString()
|
||
def pids = (row?.projectIds ?: []).collect { it?.toString() }.findAll { it != null }
|
||
|
||
if (schemeId) {
|
||
schemeToProjectIds[schemeId] = (schemeToProjectIds[schemeId] + pids).unique()
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 3) Alle Field Configuration Schemes holen
|
||
*
|
||
* REST: GET /rest/api/3/fieldconfigurationscheme
|
||
*/
|
||
def schemes = []
|
||
startAt = 0
|
||
while (true) {
|
||
def resp = get("/rest/api/3/fieldconfigurationscheme?startAt=${startAt}&maxResults=${SCHEME_PAGE_SIZE}")
|
||
.asObject(Map)
|
||
|
||
if (resp.status != 200) {
|
||
logger.error("ERROR|SCHEME_LIST_FAILED|status=${resp.status}|body=${resp.body}")
|
||
break
|
||
}
|
||
|
||
def values = resp.body?.values ?: []
|
||
schemes.addAll(values)
|
||
|
||
def isLast = resp.body?.isLast
|
||
if (isLast == true || values.isEmpty()) break
|
||
|
||
startAt += SCHEME_PAGE_SIZE
|
||
}
|
||
|
||
logger.info("INFO|TOTAL_SCHEMES|${schemes.size()}")
|
||
|
||
/**
|
||
* 4) Auswertung + optional Delete
|
||
*/
|
||
def keptAssociated = 0
|
||
def keptProtected = 0
|
||
def candidates = [] // [id,name,reason]
|
||
|
||
schemes.each { s ->
|
||
def schemeId = s?.id?.toString()
|
||
def schemeName = s?.name?.toString()
|
||
|
||
def assocProjectIds = schemeToProjectIds[schemeId] ?: []
|
||
def assocProjectKeys = assocProjectIds.collect { projectIdToKey[it] }.findAll { it != null }.unique()
|
||
|
||
def nameProtected = isNameProtected(schemeName)
|
||
def idProtected = isIdProtected(schemeId)
|
||
|
||
if (idProtected || nameProtected) {
|
||
keptProtected++
|
||
def why = []
|
||
if (idProtected) why << "ID_PROTECTED"
|
||
if (nameProtected) why << "NAME_PROTECTED"
|
||
logger.info("KEEP|schemeId=${schemeId}|name=${schemeName}|assocProjects=${assocProjectKeys}|reason=${why}")
|
||
return
|
||
}
|
||
|
||
if (!assocProjectIds.isEmpty()) {
|
||
keptAssociated++
|
||
logger.info("KEEP|schemeId=${schemeId}|name=${schemeName}|assocProjects=${assocProjectKeys}|reason=ASSOCIATED_TO_PROJECTS")
|
||
return
|
||
}
|
||
|
||
logger.info("DEL?|schemeId=${schemeId}|name=${schemeName}|assocProjects=[]|reason=UNASSOCIATED")
|
||
candidates << [schemeId, schemeName, "UNASSOCIATED"]
|
||
|
||
if (!DRY_RUN) {
|
||
def delResp = delete("/rest/api/3/fieldconfigurationscheme/${schemeId}")
|
||
.asString()
|
||
|
||
if (delResp.status == 204) {
|
||
logger.info("DEL|OK|schemeId=${schemeId}|name=${schemeName}")
|
||
} else {
|
||
logger.error("DEL|FAIL|schemeId=${schemeId}|name=${schemeName}|status=${delResp.status}|body=${delResp.body}")
|
||
}
|
||
}
|
||
}
|
||
|
||
logger.info("=== SUMMARY ===")
|
||
logger.info("Total schemes: ${schemes.size()}")
|
||
logger.info("Kept (associated to any projects): ${keptAssociated}")
|
||
logger.info("Kept (protected by rules): ${keptProtected}")
|
||
logger.info("Delete candidates: ${candidates.size()}")
|
||
|
||
candidates.each { c ->
|
||
logger.info("CANDIDATE|schemeId=${c[0]}|name=${c[1]}|reason=${c[2]}")
|
||
}
|
||
|
||
logger.info("=== DONE ===")
|