Page tree
Skip to end of metadata
Go to start of metadata

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;
}    
  • No labels