...
| Code Block |
|---|
import com.atlassian.jira.issue.MutableIssue import com.atlassian.jira.workflow.JiraWorkflow import com.atlassian.jira.issue.fields.screen.FieldScreen import com.atlassian.jira.issue.fields.screen.FieldScreenTab import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.fields.CustomField; import com.opensymphony.workflow.loader.ActionDescriptor import com.opensymphony.workflow.loader.ResultDescriptor import com.opensymphony.workflow.loader.ConditionsDescriptor import com.opensymphony.workflow.loader.ConditionDescriptor import com.atlassian.jira.component.spring.ComponentManager; import com.atlassian.jira.config.DefaultStatusManager; def fieldLayoutManager = ComponentAccessor.getFieldLayoutManager() def workflowManager = ComponentAccessor.getWorkflowManager() def fieldScreenManager = ComponentAccessor.getFieldScreenManager() def fieldManager = ComponentAccessor.getFieldManager() def statusMgr = ComponentManager.getInstance().getComponent(DefaultStatusManager.class) MutableIssue issue = Issues.getByKey('DEMO-32') as MutableIssue JiraWorkflow workflow = workflowManager.getWorkflow(issue"Lc Requirement Flow") def out = new StringBuilder() out << "<h2>${workflow.name}</h2>" out << "<table class='aui' id='myTable'>" out << "<tr><th>状态(From -> To)</th><th>动作/转换</th><th>条件</th><th>校验</th><th>后处理</th><th>填写字段</th><th>是否必填</th><th>字段选项</th></tr>" workflow.getAllActions().each { action -> def transitionName = action.getName() def screenId = action.getMetaAttributes()?.get("jira.fieldscreen.id") //排除掉创建页面 if("Create".equals(transitionName)) { return } // 拼 from → to 状态 def statusDesc = workflow.getStepsForTransition(action).collect { step -> def fromStatus = statusMgr.getStatus(step.getMetaAttributes().get("jira.status.id")).getName() def toStatus = statusMgr.getStatus(workflow.getDescriptor().getStep(action.getUnconditionalResult().getStep()).getName(.getUnconditionalResult().getStep()).getMetaAttributes().get("jira.status.id")).getName() log.warn("xx: ${fromStatus} -> ${toStatus}") "${fromStatus} -> ${toStatus}" }.join("<br>") ////////////////////// List<String> conditions = []; ConditionsDescriptor conds = action.getRestriction()?.getConditionsDescriptor() if (conds) { dumpConditions(conds, conditions, 0) log.warn(conditions.join('<BR>')); } List<String> validators = dumpValidator(action.getValidators()) def postfunctions = action.getUnconditionalResult().getPostFunctions().size() /////////////////// if (screenId) { FieldScreen screen = fieldScreenManager.getFieldScreen(screenId as Long) // 收集字段 def fieldRows = [] screen?.getTabs()?.each { FieldScreenTab tab -> tab.getFieldScreenLayoutItems().each { item -> def fieldId = item.getFieldId() def field = fieldManager.getField(fieldId) def layout = fieldLayoutManager.getFieldLayout(issue) def required = layout?.getFieldLayoutItem(fieldId)?.isRequired() ? '必填' : '' def optionsText = "" if (field instanceof CustomField) { optionsText = getOptions(field as CustomField, issue) } fieldRows << [name: field?.name, id: fieldId, required: required, options: optionsText] } } // 🔽 排序:先按字段名,再按 ID fieldRows.sort { a, b -> b.required <=> a.required } if (fieldRows) { def rowspan = fieldRows.size() // 第一行带 rowspan def first = fieldRows[0] out << "<tr><td rowspan='${rowspan}'>${statusDesc}</td>" out << "<td rowspan='${rowspan}'>${transitionName} (${screen?.name})</td>" out << "<td rowspan='${rowspan}'>${conditions.join('<BR>')}</td>" out << "<td rowspan='${rowspan}'>${validators.join('<BR>')}</td>" out << "<td rowspan='${rowspan}'>${postfunctions}</td>" out << "<td>${first.name} (${first.id})</td><td>${first.required}</td><td>${first.options}</td></tr>" // 剩余行 fieldRows.drop(1).each { f -> out << "<tr><td>${f.name} (${f.id})</td><td>${f.required}</td><td>${f.options}</td></tr>" } } else { //界面没有字段,默认输入备注 log.warn("============================${screen.name}") out << "<tr><td>${statusDesc}</td>" out << "<td>${transitionName} (${screen?.name})</td>" out << "<td>${conditions.join('<BR>')}</td>" out << "<td>${validators.join('<BR>')}</td>" out << "<td>${postfunctions}</td>" out << "<td>备注</td><td></td><td></td></tr>" } } else { out << "<tr><td>${statusDesc}</td><td>${transitionName}(无输入项)</td><td>${conditions.join('<BR>')}</td><td>${validators.join('<BR>')}</td><td>${postfunctions}</td><td colspan='3'> <> </td></tr>" } } out << "</table>" // 在表格前加按钮 out.insert(out.indexOf("<h2>"), """ <button class='aui-button' onclick=" var table=document.getElementById('myTable')||document.querySelector('table'); if(!table)return; var html=table.outerHTML; var text=table.innerText; navigator.clipboard.write([ new ClipboardItem({ 'text/html': new Blob([html], {type: 'text/html'}), 'text/plain': new Blob([text], {type: 'text/plain'}) }) ]).then(function(){ var myFlag = AJS.flag({ type: 'success', body: '复制成功', duration: 1000, close: 'auto' }); }).catch(function(err){ console.error(err); alert('复制失败,浏览器可能不支持 ClipboardItem API'); }); "> 复制表格 </button> """ ) return out.toString() // 获取某个字段的全部选项(仅限选择类字段) def getOptions(CustomField cf, MutableIssue issue) { def optionsManager = ComponentAccessor.getOptionsManager() def config = cf.getRelevantConfig(issue) if (!config) { return "" } def options = optionsManager.getOptions(config) if (!options) { return "" } return options.collect { opt -> (opt.disabled ? "[禁用] " : "") + opt.value }.join(" / ") } def dumpConditions(ConditionsDescriptor conds, List<String> rst, int level = 0) { // 生成缩进字符串 def indent = " " * level //这里要解析各种工作流插件的东西,too hard conds.getConditions().each { cond -> if (cond instanceof ConditionsDescriptor) { log.warn("逻辑条件: ${cond.getType()}") // AND / OR dumpConditions(cond, rst, level + 1) } else if (cond instanceof ConditionDescriptor) { def args = cond.getArgs().get("class.name")?.replaceAll("com.atlassian.jira.workflow.condition.", "") // 处理特定条件类型的参数 String conditionDesc = "${indent}${args}" rst.add(conditionDesc) log.warn(conditionDesc) } } } def dumpValidator(List vlist) { List<String> rst = []; if(vlist) { for(int i=0;i < vlist.size(); i++) { def args = vlist.get(i).getArgs().get('class.name')?.replaceAll("com.atlassian.jira.workflow.validator.", "") rst.add(args); } } return rst; } |