当所有链接的故事和任务完成时,有没有办法自动将史诗状态更改为完成

Is there a way to automatically change epic state to done when all the linked stories and tasks are complete

我是 JIRA 和看板的新手。当我创建一个史诗和 link 一些故事和任务时,我期待着。当所有故事和任务 link 完成时,史诗的状态将自动更改(例如完成)。但似乎并非如此。我可以将史诗从 Backlog 移动到 Done 列,即使它的 linked 任务和故事仍然在 backlog 中。有没有办法让 JIRA 防止这种情况发生?

我一直在做类似的事情。我的意图是当状态更改为特定状态时,将另一个问题的所有链接问题分配给特定用户。

我使用以下类型的工作流的后置函数来完成此操作:"Set Field Value to constant or Groovy expression"

在您的情况下,我会执行以下操作:

  • 转到 "Close" 转换,然后单击配置。
  • select postfunctions,并添加我告诉你的类型。
  • 标记表示仅当条件为真时才执行的复选框
  • 设置你的条件。可能类似于 issue.epic=your epic.
  • 然后添加脚本,恢复与史诗相关的所有问题,并检查它们的状态。
  • 创建您的逻辑,以便如果一切正常,您只需更改状态,使用 MutableIssue object。
  • 请记住,此脚本将修改一个字段,我想您不能选择状态作为要设置的字段。如果发生这种情况,请选择摘要,并存储当前值,并使用它来结束您的脚本,并设置摘要值,与之前的值相同。
  • 发布您的工作流程。

不清楚,不好解释,请见谅。如果您还需要其他东西,请告诉我。

PD:如果您只是想在某个特定时刻执行此操作,而不是每个 epics 自动执行此操作,只需添加 Script Runner 插件,然后 运行 您在控制台中的脚本。容易多了。

此致

也许对您有帮助: 我使用系统语言设置为 "Russian" 的 jira(我不擅长 groovy),这就是为什么下面的脚本包含语言依赖项(如果您使用与我的 jira 系统语言不同的语言,您应该编辑代码!至少改变 )

  1. 使用 Scrip Runner 插件
  2. 创建 "Custom listener" 并粘贴代码(代码不是很好,但可以工作):

    import com.atlassian.jira.component.ComponentAccessor;
    import com.atlassian.jira.issue.IssueManager;
    import com.atlassian.jira.issue.Issue;
    import com.atlassian.jira.issue.link.IssueLinkManager;
    import com.atlassian.jira.issue.link.IssueLink;
    import com.atlassian.jira.issue.ModifiedValue;
    import com.atlassian.jira.issue.util.DefaultIssueChangeHolder;
    import com.atlassian.jira.issue.customfields.option.Options;
    import com.atlassian.jira.issue.customfields.option.Option;
    import com.atlassian.jira.issue.fields.config.FieldConfig;
    import com.atlassian.jira.issue.customfields.manager.OptionsManager;
    import com.atlassian.jira.ComponentManager;
    
    ComponentManager componentManager = ComponentManager.getInstance();
    def groupMan = ComponentAccessor.getGroupManager()
    def authCon = ComponentAccessor.getJiraAuthenticationContext() 
    def customFieldManager = ComponentAccessor.getCustomFieldManager()
    def changeHolder = new DefaultIssueChangeHolder();
    IssueManager issueManager = ComponentAccessor.getIssueManager();
    OptionsManager optionsManager = componentManager.getComponentInstanceOfType(OptionsManager.class);
    IssueLinkManager issueLinkManager = ComponentAccessor.getIssueLinkManager()
    
    def curUser = authCon.getUser()
    def issue = event.issue
    
    def epicLinkCf = customFieldManager.getCustomFieldObjects(issue).find {it.name == 'Epic Link'}
    if(!epicLinkCf) {log.warn "No Epic Link field"; return}
    log.warn "Existing Epic Link: ${epicLinkCf.getValue(issue)}"
    
    String epicIssue = epicLinkCf.getValue(issue)
    Issue epic = issueManager.getIssueObject(epicIssue) // epicKey is passed into your script
    
    // Check if Epic link is exist
    if(!epic)
        return true
    
    def newEpicState = "Сделано"
    
    log.warn "Epic: " + epic
    List<IssueLink> allOutIssueLink = issueLinkManager.getOutwardLinks(epic.getId());
    for (Iterator<IssueLink> outIterator = allOutIssueLink.iterator(); outIterator.hasNext();) {
        IssueLink issueLink = (IssueLink) outIterator.next();
        log.warn "child link type: " + issueLink.getIssueLinkType().getName()
        // Check status of all issues from epic
        if (issueLink.getIssueLinkType().getName() == "Epic-Story Link") {
            Issue chIssue = issueLink.getDestinationObject();
            log.warn "child state: " + chIssue.getStatusObject().getName()
            if(chIssue.getStatusObject().getName() == "В процессе") {
                newEpicState = "В процессе"
            } else if (chIssue.getStatusObject().getName() != "Закрыто" && newEpicState != "В процессе") {
                newEpicState = "Сделать"
            }
        }
    }
    
    def epicStatusCf = customFieldManager.getCustomFieldObjects(epic).find {it.name == 'Epic Status'}
    log.warn "Current epic status: " + epicStatusCf.getValue(epic)
    FieldConfig epicStatusFieldConfig = epicStatusCf.getRelevantConfig(epic);
    String oldStatus = epicStatusCf.getValue(epic)
    
    log.warn "New epic status: " + newEpicState
    // Set new status if it necessary
    if (oldStatus != newEpicState) {
        Options epicStatusOptions = optionsManager.getOptions(epicStatusFieldConfig);
        Option epicStatusDoneOption = epicStatusOptions.getOptionForValue(newEpicState, null);
        epicStatusCf.updateValue(null, epic, new ModifiedValue(epic.getCustomFieldValue(epicStatusCf),epicStatusDoneOption),changeHolder)
        log.warn "Epic status is updated!"
    }
    

如果您使用的是 Scriptrunner,那么您最好使用 scriptrunner 代码。请从 scriptrunner 检查此代码:

// Add the next line as a condition to the script listener, so it gets executed only for epic issues, the line must be written uncommented:
// issue.isEpic

// Check if the resolution has been changed
def resolutionChange = changelog.items.find {
    (it as Map).field == 'resolution'
} as Map
logger.info("The resolution change of issue '${issue.key}': ${resolutionChange}.")

if (!resolutionChange) {
    logger.info("The resolution didn't change.")
    return
}

// Compute the 'Epic Status' value to set based on the resolution value
def newEpicStatusValue = (resolutionChange.toString == 'Done') ? 'Done' : 'To Do'

// Get the 'Epic Status' field ID
final epicStatusField = get("/rest/api/2/field")
    .asObject(List)
    .body
    .find {
        (it as Map).name == 'Epic Status'
    } as Map
def epicStatusFieldId = epicStatusField.id

logger.info("Updating Epic Status field (${epicStatusFieldId}) to '${newEpicStatusValue}'.")
// Update the 'Epic Status' field value
put("/rest/api/2/issue/${issue.key}")
    .queryString("overrideScreenSecurity", Boolean.TRUE)
    .header("Content-Type", "application/json")
    .body([
        fields: [
            (epicStatusFieldId): [value: newEpicStatusValue]
        ]
    ])
    .asString()

您可以在您的 post-函数或 jira 中自动化此代码。请在 link.

中找到更多详细信息