1、必填:工作流的必填会受到插件的影响,这里只是取了字段方案里的必填信息。
2、权限:Condition这里没有提取,因为也是存在插件原因范围比较难确定。
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("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()).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;
}