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

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 2 Current »

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

def fieldLayoutManager = ComponentAccessor.getFieldLayoutManager()
def workflowManager = ComponentAccessor.getWorkflowManager()
def fieldScreenManager = ComponentAccessor.getFieldScreenManager()
def fieldManager = ComponentAccessor.getFieldManager()


MutableIssue issue = Issues.getByKey('DEMO-32') as MutableIssue
JiraWorkflow workflow = workflowManager.getWorkflow(issue)

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 = step.getName()
        def toStatus = workflow.getDescriptor().getStep(action.getUnconditionalResult().getStep()).getName()
        "${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