我们仍然以这张图作为开头,之前已经讲了,Project创建、问题相关、字段相关、界面相关的内容。接下来就是最重要也是最复杂的工作流相关的内容。
以前的文章中曾经介绍过工作流的思路,主要是2点:
- 子任务驱动主任务状态流转。
- 字段和流程简化以及自动化流转。
工作流概述
工作流服务的主体是问题(Issue),也就是Jira当中的核心载体。工作流中有两个核心概念:转换(Transition)和状态(State)。
我们用最简单的一个工作流来解释一下。
在上图中,TO DO、IN PROGRESS、 DONE表示三个状态,注意三种颜色也代表了其中的含义,蓝色代表未开始,黄色进行中,绿色完成。
当你需要在流程中添加一种新状态时,会需要选择三种颜色之一。
连接两个状态的线,就是转换,意义就是通过这个行为,将问题从状态1变为状态2。
状态是一个结果,是由通过执行转换得到的结果,所以我们可以在转换执行时定义大量的操作,下图就是工作流在选中转换时可操作的内容。
图中我选择了 “转至进行中” 这个转换,右侧出现了五个选择,我们依次解释一下:
- 属性
- 触发器
- 条件
- 验证器
- 后处理功能
属性
看一个例子吧
这里的用法就是标题和提交按钮的国际化的键值
触发器
可以关联到Crucilbe和Fisheye对代码分支和CodeReview自动转换执行。
条件
只有条件被满足,转换才可以被执行
这里包含Jira自带和插件增加的一些条件
验证器
类似表单提交时的验证脚本,可以进行填写有效性的确认
后处理功能
转换成功执行后,自定义一些操作。
我们的自动化的流程推进都是需要后处理功能介入。
给大家看一个子任务的后处理功能触发自动工作流的例子:
工作流类型
自定义工作流需要从我们实际的管理需求出发,每个团队的管理方式方法不同,自然问题类型和工作流也不相同。我在这里提出研发管理实际过程中整理出了四种工作流。
- 主任务工作流
- 子任务工作流
- Bug工作流
- 默认工作流
这样四种工作流,基本能够覆盖到研发管理工作中的相关流程和角色。
主任务
主任务概念包含故事(Story)和任务(Task),故事一般是由产品发起对应平常概念中的需求(主要是功能性需求,当然也可能包含非功能性需求,但是一般都会能够让用户感知到),任务一般是由研发推动的发起的工作(例如代码重构、服务拆分、性能优化等,用户可能无法明显感知的内容)。
主任务就是一个研发团队的核心工作内容的概括,所以工作流是需要包含研发团队内的所有角色,产品、设计、研发、测试、运维等。一般工作任务也是顺序从各个角色流转的,所以可以将工作流划分成这些角色,每个角色都设置待办、进行中、完成这三种状态。其中完成可以设置为下一个环节的待办,这两个状态可以考虑合并为同一个。
子任务
还记得我们的研发管理原则之一就是简化操作,实际的任务执行者不需要了解整体复杂的设计与逻辑,通过简单的操作就能够驱动整体流程前进。这就是要依赖子任务这个概念。
在我们的研发团队中,所有的人员具体的可执行项都只能以一种类型记录,就是子任务。子任务的字段精简,只包含必须的内容。流程简单,只需要包含待办、进行中、完成。由于子任务新建后默认就是待办,所以正常的操作只会包含2个行为,进行中和完成。人员事先用分组进行标记,通过简单的进行中和完成的状态的切换,就能够推动各自角色的主流程状态变化。
Bug
Bug是研发质量管理的重要形式,Bug的提出方一般是QA,接收方大部分是研发,也可能是产品。在Bug的工作流过程中,流程其实还是比较清晰的,就是围绕Bug的解决和验证,在提出、接收方之间流转。我们要优化的点在于,能够实现流程的人员自动转换,无需手动指派。
默认
就像我们写代码,分支循环时都会需要一个默认选择,这个默认选择就只要最简单的待办、进行中、完成即可,甚至只需要待办和完成,大家可以根据实际情况来确认。
工作流设计
工作流的设计中有一个重要的前提就是用户组的划分,用于对角色的区分。
接下来会对上面的四种工作流进行详细的说明,涉及到一些设置或者脚本的地方也会尽量贴出明确的图例或者代码。
主任务
主任务的工作流覆盖到所有角色,所以是最多状态的一个,先来看图。
这个流程中有11个状态,一开始是默认的待办状态(TO DO),接下来大家其实能看出,根据角色分成了几个区块,最右侧是产品,接下来向左依次是UI设计、研发、测试,最终是完成。
这里有几个设计点和大家一起讨论一下:
- TODO状态可以转换到任何一个角色的进行中状态。
由于不同Story或者Task的情况不同,实际参与的角色是有差别的,如果我们流程限制所有的角色流程必须具备则可能不会那么灵活。所以每个角色的进行中状态都是可以直接从TODO转换过来的,但是完成状态只能从进行中转换而来。 - 所有转换的都以统一的格式来命名。
可以看到所有的转换主要分为两种前缀:转至和完成,分别对应着从待办到进行中,和从进行中到完成。这样做的原因,首先命名的时候有统一的模式比较简单不用想很多名字,由于在同一个流程图中,同一个状态可能有多个状态可以到达,为了后续自动化工作流的便利我们这么设计是有帮助的(这句话在稍后的子任务的工作流当中就能很好的解释了)。 - 不同阶段的前置节点不同
产品是最初的阶段,一开始一定是产品进行梳理需求并且整理原型和设计方案。
设计的前置输入一般都是产品,但是也可以独立发起一些需求(例如UI设计改版重构等)
研发的阶段,前置节点可能是产品也可能是UI,因为正常流程是产品设计完原型给到设计,设计做完UI一起交给研发进行开发。当然也有可能产品直接就交付给研发开发无需另外设计。所以设计和产品都可以作为研发的输入。
测试的输入一定是研发,因为有研发进行了系统的变更,才需要测试。
最终完成的节点,大部分情况下都应该是测试确认保证后发布上线的结果,少量的情况测试可能无法验证则研发确认后就直接发布。
后处理功能
主任务的状态很多,但是特殊的配置其实很少,我在这里只有一个特殊的后处理功能设计,场景如下:
我们有研发中心和客户支持中心,客户支持中心可能有一些外部客户的Bug或者建议会转为内部的研发任务。我们会使用“工单关联”将研发中心的Story与支持中心的工单关联,当研发内部的Story执行“转至完成”后,变更关联工单的状态。
这个功能需要安装一个插件JMWE,提供了 Transition linked issues 这样的方法。
这里的配置比较清晰
第一行选择的是要触发关联问题的什么转换(可以通过名称或者ID),下面选择的是链接的类型。
下面Transition screen的部分,我们将“经办人” 修改为 “报告人”,意思就是工单已经发布上线,谁报告的这个问题已经可以做后续的跟进了。
最后会在对应的工单下做一个评论:内部研发流程已经完成并且发布。
主任务总结
主任务本身是管理方法的体现,为了将流程中的各个角色串联起来,并且能够站在较高的视角跟进相关任务推动流程迅速衔接。
所以主任务正常是不会有人去维护的,也就是不会有人主动的管理主任务的状态,大家都只是看。
子任务
如果说主任务是复杂而简单(状态多而复杂,但是背后的逻辑简单),子任务就是简单而复杂。这句话怎么理解,我们先来看子任务的工作流。
看这个图基本上就明白,我完全没有调整过这个最简单的工作流,而且这个工作流只有三个状态,排除掉初始的待办状态,正常只有两个状态可以切换。所以子任务是简单的。
接下来我们看复杂的意思
从待办到处理中这个转换,我们一共有15个后处理功能,除去系统自带,我们也有9个自定义的后处理功能。
同样,我们一起来讨论一下其中的一些设计点:
- 子任务是主任务的组成部分,所以主任务的人员应该就是子任务人员的集合。
我们的设计,在新建或者开始子任务时,都会将报告人和经办人加入到父任务的责任人字段中,表名整个任务的相关人员有哪些,更快速的沟通。
后处理功能
子任务主要的转换是两个,从待办到处理中,以及从处理中到完成。我们的所有后处理功能都在这两个转换上。
待办到处理中
待办到进行中一共9条自定义规则我们一起来看:
日期规则
1的规则是使用了设置字段值这个功能,意思就是要在点击开始的时候,将任务的开始日期置位当前时间。
目的是希望保持子任务的开始时间为实际的时间,不需要操作人手动的调整时间。
解决结果
2的规则也是使用了设置字段值,是将子任务的解决结果设置到父任务。
目的是希望将子任务的结果能够在父任务体现,从而让管理着了解是否存在特殊的解决结果。
责任人
3 4 5的规则也是使用了设置字段值,都是对责任人字段的维护。
目的是希望将子任务或者主任务的责任人字段添加当前涉及到的人员,主任务的责任人字段是为了维护所有参与者快速确认人员范围。子任务则更多是为了明确责任。
主任务流程推进
12-15的规则使用的是Transition parent issue (JMWE add-on),这是插件提供的。是触发主任务流程流转。
目的是通过子任务的流转推动主任务流转。这里会比较复杂,我们来看一下具体实现,下图
这里我们分为4个部分:
- 设置触发主任务流转
- 设置字段值
- 添加评论
- 设置执行条件
设置触发主任务流转:我们要讲到命名的原则在这里的作用了。这里触发主任务流程的前提,还是要求当前主任务的状态是在目标流转的起始点的。也就是说如果现在的工作流是 A→B→C,如果我们期望执行A→B,那当前主任务的状态必须在A才能顺利到达B,否则不会产生效果。我们的命名规则里,把所有转向某个状态的流转都命名为“转至xxx”,这样一个方法就能够触发多种起始状态下的流转了。
设置字段值:这个已经是比较常规的设计了,这个流程中没有需要设置的内容
添加评论:所有的自动化流程执行,都会自动添加一条评论到主任务下,记录由谁推进到什么状态。用于后期的记录跟踪
设置执行条件:这个实际上是最核心的,后处理功能会按照顺序全部执行,应该执行哪个才能够顺利的推进主任务流程流转,只能通过执行条件来判断。这里面主要还是通过当前用户的用户组来判断角色,从而决定推进主任务的什么流转。下面是从产品、设计、研发、测试四个角色的子任务开始脚本:
- 产品经理角色
(currentUser in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-product-pm") && issue.getAsString("任务类型") == "")
这里是判断当前用户是在系统用户组 org-pd-product-pm (产品经理) 中的话,并且任务类型没有特别设置。
用户分组大家好理解,任务类型是什么意思呢?
我们发现因为所有人的工作都要落地成为子任务,有些子任务实际上并没有推进流程状态变化。比如产品经理的需求预研、可行性分析等,实际上还没有推进需求落单,暂时不应该将主任务推进到产品设计进行中这样的状态。这种情况可以通过补全主任务流程状态来弥补,我们是通过另外一种方式,就是设置特殊子任务类型来避免推进流程到错误的状态。所以我们形成了内部约定,一旦子任务的任务类型设置了值,就不会推进流程流转。
- UI设计角色
(currentUser in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-product-ui") && issue.getAsString("任务类型") == "")
- 研发角色
(currentUser in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-coder") && issue.getAsString("任务类型") == "")
- 测试角色
(currentUser in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-qa") && issue.getAsString("任务类型") == "")
处理中到完成
这里的自定义规则有5条:
主任务流程推进
6-10 的规则使用的是Transition parent issue (JMWE add-on),不复述上面的内容了
这里主要只有后处理功能,我们就拆解一下说明
- 产品完成
这部分逻辑首先判断当前操作用户是否是产品角色,如果不是直接忽略。如果是的话,会循环所有的父任务下的子任务,并且判断是否是归属产品角色的任务。如果是产品类型的任务,判定是否已经完成。只有除了当前任务以外,所有其他的产品任务都完成,才将主任务推进到对应角色的完成状态。
if(!(currentUser in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-product-pm"))){
return false;
}
boolean result = true;
for(int i=0; i<issue.parentObject.subTaskObjects.size;i++){
if(issue.parentObject.subTaskObjects[i].assignee in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-product-pm")){
if(issue.parentObject.subTaskObjects[i].key != issue.key && issue.parentObject.subTaskObjects[i].getAsString("状态") != "Done"){
return false;
}
}
}
return result;
- UI设计完成
if(!(currentUser in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-product-ui"))){
return false;
}
boolean result = true;
for(int i=0; i<issue.parentObject.subTaskObjects.size;i++){
if(issue.parentObject.subTaskObjects[i].assignee in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-product-ui")){
if(issue.parentObject.subTaskObjects[i].key != issue.key && issue.parentObject.subTaskObjects[i].getAsString("状态") != "Done"){
return false;
}
}
}
return result;
- 研发完成(有测试)
研发的判断条件会稍微特殊一些,这里会有一个分析研发完成之后的转向有两个分支,是否需要测试。
逻辑实际上是很简单,就是循环所有的子任务,判断是否存在用户组为测试的子任务,如果有就转向“研发完成待测”,如果没有就转向“研发完成无需测试”
if(!(currentUser in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-coder"))){
return false;
}
boolean result = true;
boolean hasQA = false;
for(int i=0; i<issue.parentObject.subTaskObjects.size;i++){
if(issue.parentObject.subTaskObjects[i].assignee in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-coder")){
if(issue.parentObject.subTaskObjects[i].key != issue.key && issue.parentObject.subTaskObjects[i].getAsString("状态") != "Done"){
return false;
}
}
if(!hasQA && (issue.parentObject.subTaskObjects[i].assignee in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-qa"))){
hasQA = true;
}
}
if(!hasQA){
result = false;
}
return result;
- 研发完成(无测试)
if(!(currentUser in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-coder"))){
return false;
}
boolean result = true;
boolean hasQA = false;
for(int i=0; i<issue.parentObject.subTaskObjects.size;i++){
if(issue.parentObject.subTaskObjects[i].assignee in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-coder")){
if(issue.parentObject.subTaskObjects[i].key != issue.key && issue.parentObject.subTaskObjects[i].getAsString("状态") != "Done"){
return false;
}
}
if(!hasQA && (issue.parentObject.subTaskObjects[i].assignee in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-qa"))){
hasQA = true;
}
}
if(hasQA){
result = false;
}
return result;
- 测试完成
if(!(currentUser in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-qa"))){
return false;
}
boolean result = true;
for(int i=0; i<issue.parentObject.subTaskObjects.size;i++){
if(issue.parentObject.subTaskObjects[i].assignee in ComponentAccessor.getGroupManager().getUsersInGroup("org-pd-qa")){
if(issue.parentObject.subTaskObjects[i].key != issue.key && issue.parentObject.subTaskObjects[i].getAsString("状态") != "Done"){
return false;
}
}
}
return result;
子任务总结
子任务的核心,就是简单而复杂。简单的流程才能更好的在团队内推行,复杂的后台逻辑才能满足管理层的管理需求。
Bug
Bug是研发测试过程中的质量缺陷,流程不会复杂,主要是自动化的流转和管控要求。
状态只有四个,和子任务相比,实际上只多了一个状态“已解决”。默认Bug是由测试提出,新建之后状态是“待办”,研发开始处理转换为“进行中”,研发完成后转换为“已解决”,测试会跟进“已解决”的Bug,如果确认解决则转换为“完成”,如果未解决则转换为“进行中”。
后处理功能
关于Bug,最重要的后处理功能实际上在于“进行中”和“已解决”直接来回转换,所以我们来看一下 “解决问题”和“未解决”两个转换的后处理。
解决问题
这里需要关注的主要还是责任人的处理。只有在解决结果是“Done”(其他一般是无法复现、网络环境异常、需求设计如此等)的时候,才将当前操作人作为责任人。这个是为了确认研发的责任归属,只有研发认可并且实际有处理的Bug责任才需要归属于研发。
经办人的会自动分配给报告人,这样避免研发手动指定经办人。
未解决
未解决的转换意义在于说,研发提交给测试的Bug实际上并未修复,需要退回给研发。这里是需要累加“退回次数”这个自定义字段,
<%
int i = issue.get("退回次数") >= 0 ?issue.get("退回次数"):0;
i=i+1;
println(i)
%>
经办人也是需要将当前人员直接指派给之前流转过来的研发人员。使用的是“Assign to last role member (JMWE add-on) ”插件提供。
这个Project Role,是需要我们在项目设置的。
可以在项目设置中这个菜单下进行设置。
Bug总结
Bug的整体逻辑相对来讲简单很多,主要是方便了研发和测试之间的流转,并且尽量保证转换时人员分配的自动化。
默认任务
默认任务这里不再过多说明,正常的“待办-进行中-完成”的流程即可满足。
总结
至此,所有的工作流设计介绍完成,其中涉及到脚本的部分都是使用Groovy语言编写,其中最难的部分还是在于确认如何获取需要的字段在Groovy中的获取方式。
主要还是通过这里的一些帮助工具来进行探索。