Value Stream Analytics development guide
原文:https://docs.gitlab.com/ee/development/value_stream_analytics.html
Value Stream Analytics development guide
值流分析计算域对象上记录的两个任意事件之间的时间,并提供有关持续时间的汇总统计信息.
有关如何在 GitLab 中配置 Value Stream Analytics 的信息,请参阅我们的分析文档 .
Stage
在开发过程中,会发生一些事件,这些事件会移动问题并在不同的进展阶段合并请求,直到认为它们完成为止. 这些阶段可以用Stage模型表示.
示例阶段:
- Name: Development
 - 开始事件:问题已创建
 - 结束事件:在提交中首先提到的问题
 - Parent: 
Group: gitlab-org 
Events
事件是价值流分析功能的最小构建块. 一个阶段包括两个事件:
- Start
 - End
 
这些事件在持续时间计算中起关键作用.
Formula: duration = end_event_time - start_event_time
为了使持续时间计算更加灵活,每个Event都实现为一个单独的类. 他们负责定义将在计算查询中使用的时间戳表达式.
Implementing an Event class
有一些方法需要实现, StageEvent基类非常详细地描述了它们. 最重要的是:
object_typetimestamp_projection
object_type方法定义将查询哪个域对象以进行计算. 当前允许两种模型:
IssueMergeRequest
对于持续时间计算,将使用timestamp_projection方法.
def timestamp_projection
  # your timestamp expression comes here
end
# event will use the issue creation time in the duration calculation
def timestamp_projection
  Issue.arel_table[:created_at]
end 
注意:也可以使用更复杂的表达式(例如,使用COALESCE ). 查看现有事件类作为示例.
在某些情况下,仅定义timestamp_projection方法是不够的. 计算查询应该知道哪个表包含时间戳表达式. 每个Event类负责修改计算查询,以使timestamp_projection起作用. 这通常意味着加入一个附加表.
连接issue_metrics表并使用first_mentioned_in_commit_at列作为时间戳表达式的first_mentioned_in_commit_at :
def object_type
  Issue
end
def timestamp_projection
  IssueMetrics.arel_table[:first_mentioned_in_commit_at]
end
def apply_query_customization(query)
  # in this case the query attribute will be based on the Issue model: `Issue.where(...)`
  query.joins(:metrics)
end 
Validating start and end events
某些开始/结束事件对彼此不"兼容". 例如:
- 从"已创建问题"到"已合并请求":事件类在不同的域模型上定义, 
object_type方法不同. - "问题已关闭"到"问题已创建":必须先创建问题,然后才能将其关闭.
 - 从"问题已关闭"到"问题已关闭":持续时间始终为 0.
 
StageEvents模块描述了允许的start_event和end_event配对( PAIRING_RULES常数). 如果添加了新事件,则需要在该模块中注册它. 要添加新事件:
- 在
ENUM_MAPPING添加一个具有唯一编号的条目,该条目将在Stage模型中用作enum. - 在
PAIRING_RULES哈希中定义哪些事件与该事件兼容. 
支持的开始/结束事件配对:
图 LR; IssueCreated-> IssueClosed; IssueCreated-> IssueFirstAddedToBoard; IssueCreated-> IssueFirstAssociatedWithMilestone; IssueCreated-> IssueFirstMentionedInCommit; IssueCreated-> IssueLastEdited; IssueCreated-> IssueLabelAdded; IssueCreated-> IssueLabelRemoved; MergeRequestCreated-> MergeRequestMerged; MergeRequestCreated-> MergeRequestClosed; MergeRequestCreated-> MergeRequestFirstDeployedToProduction; MergeRequestCreated-> MergeRequestLastBuildStarted; MergeRequestCreated-> MergeRequestLastBuildFinished; MergeRequestCreated-> MergeRequestLastEdited; MergeRequestCreated-> MergeRequestLabelAdded; MergeRequestCreated-> MergeRequestLabelRemoved; MergeRequestLastBuildStarted-> MergeRequestLastBuildFinished; MergeRequestLastBuildStarted-> MergeRequestClosed; MergeRequestLastBuildStarted-> MergeRequestFirstDeployedToProduction; MergeRequestLastBuildStarted-> MergeRequestLastEdited; MergeRequestLastBuildStarted-> MergeRequestMerged; MergeRequestLastBuildStarted-> MergeRequestLabelAdded; MergeRequestLastBuildStarted-> MergeRequestLabelRemoved; MergeRequestMerged-> MergeRequestFirstDeployedToProduction; MergeRequestMerged-> MergeRequestClosed; MergeRequestMerged-> MergeRequestFirstDeployedToProduction; MergeRequestMerged-> MergeRequestLastEdited; MergeRequestMerged-> MergeRequestLabelAdded; MergeRequestMerged-> MergeRequestLabelRemoved; IssueLabelAdded-> IssueLabelAdded; IssueLabelAdded-> IssueLabelRemoved; IssueLabelAdded-> IssueClosed; IssueLabelRemoved-> IssueClosed; IssueFirstAddedToBoard-> IssueClosed; IssueFirstAddedToBoard-> IssueFirstAssociatedWithMilestone; IssueFirstAddedToBoard-> IssueFirstMentionedInCommit; IssueFirstAddedToBoard-> IssueLastEdited; IssueFirstAddedToBoard-> IssueLabelAdded; IssueFirstAddedToBoard-> IssueLabelRemoved; IssueFirstAssociatedWithMilestone-> IssueClosed; IssueFirstAssociatedWithMilestone-> IssueFirstAddedToBoard; IssueFirstAssociatedWithMilestone-> IssueFirstMentionedInCommit; IssueFirstAssociatedWithMilestone-> IssueLastEdited; IssueFirstAssociatedWithMilestone-> IssueLabelAdded; IssueFirstAssociatedWithMilestone-> IssueLabelRemoved; IssueFirstMentionedInCommit-> IssueClosed; IssueFirstMentionedInCommit-> IssueFirstAssociatedWithMilestone; IssueFirstMentionedInCommit-> IssueFirstAddedToBoard; IssueFirstMentionedInCommit-> IssueLastEdited; IssueFirstMentionedInCommit-> IssueLabelAdded; IssueFirstMentionedInCommit->已删除 IssueLabel; IssueClosed-> IssueLastEdited; IssueClosed-> IssueLabelAdded; IssueClosed-> IssueLabelRemoved; MergeRequestClosed-> MergeRequestFirstDeployedToProduction; MergeRequestClosed-> MergeRequestLastEdited; MergeRequestClosed-> MergeRequestLabelAdded; MergeRequestClosed-> MergeRequestLabelRemoved; MergeRequestFirstDeployedToProduction-> MergeRequestLastEdited; MergeRequestFirstDeployedToProduction-> MergeRequestLabelAdded; MergeRequestFirstDeployedToProduction-> MergeRequestLabelRemoved; MergeRequestLastBuildFinished-> MergeRequestClosed; MergeRequestLastBuildFinished-> MergeRequestFirstDeployedToProduction; MergeRequestLastBuildFinished-> MergeRequestLastEdited; MergeRequestLastBuildFinished-> MergeRequestMerged; MergeRequestLastBuildFinished-> MergeRequestLabelAdded; MergeRequestLastBuildFinished-> MergeRequestLabelRemoved; MergeRequestLabelAdded-> MergeRequestLabelAdded; MergeRequestLabelAdded-> MergeRequestLabelRemoved; MergeRequestLabelRemoved-> MergeRequestLabelAdded; MergeRequestLabelRemoved-> MergeRequestLabelRemoved;
Parent
团队和组织可能会定义自己的软件构建方式,因此阶段可能完全不同. 对于每个阶段,都需要定义一个父对象.
目前支持的父母:
ProjectGroup
How parent relationship it work
- 用户导航到价值流分析页面.
 - 用户选择一个组.
 - 后端将加载选定组的已定义阶段.
 - 对阶段的添加和修改将仅保留在所选组中.
 
Default stages
价值流分析的原始实施定义了 7 个阶段. 每个父母都可以使用这些阶段,但是无法更改这些阶段. 为了提高效率并减少创建的记录数,默认阶段被表示为内存中对象(不持久). 当用户首次创建自定义阶段时,所有阶段都将保留. 此行为在价值流分析服务对象中实现. 这样做的原因是我们希望稍后添加隐藏和订购阶段的功能.
Data Collector
DataCollector是从数据库查询数据的中心点. 该类始终在单个阶段上运行,并且由以下组件组成:
BaseQueryBuilder:- 负责编写初始查询.
 - 处理特定于
Stage配置:事件及其查询自定义. - 来自用户界面的参数:日期范围.
 
Median:使用BaseQueryBuilder的查询计算一个阶段的中位数持续时间.RecordsFetcher:使用来自BaseQueryBuilder的查询和特定的Finder类加载阶段的相关记录,以应用可见性规则.DataForDurationChart:加载散点图的带有完成时间(结束事件时间戳)的计算的持续时间.
对于新的计算或查询,可将其实现为DataCollector类中的新方法调用.
Database query
数据库查询的结构:
SELECT (customized by: Median or RecordsFetcher or DataForDurationChart)
FROM OBJECT_TYPE (Issue or MergeRequest)
INNER JOIN (several JOIN statements, depending on the events)
WHERE
  (Filter by the PARENT model, example: filter Issues from Project A)
  (Date range filter based on the OBJECT_TYPE.created_at)
  (Check if the START_EVENT is earlier than END_EVENT, preventing negative duration) 
Median的SELECT语句的结构:
SELECT (calculate median from START_EVENT_TIME-END_EVENT_TIME) 
用于DataForDurationChart的SELECT语句的DataForDurationChart :
SELECT (START_EVENT_TIME-END_EVENT_TIME) as duration, END_EVENT.timestamp 
High-level overview
- Rails 控制器( 
Analytics::CycleAnalytics模块):值流分析通过 JSON 端点公开其数据,该端点在analytics工作区中实现. 配置阶段还实现 JSON 端点(CRUD). - 服务( 
Analytics::CycleAnalytics模块):所有与Stage相关的操作都将委派给相应的服务对象. - 模型( 
Analytics::CycleAnalytics模块):模型用于持久化Stage对象ProjectStage和GroupStage. - 要素类( 
Gitlab::Analytics::CycleAnalytics模块):- 负责撰写查询并定义特定于功能的业务逻辑.
 DataCollector,Event,StageEvents等.
 
Testing
由于我们有很多事件和可能的配对,因此无法测试每个配对. 规则是至少要有一个使用Event类的测试用例.
使用新Event为阶段编写测试用例可能会遇到挑战,因为必须为两个事件都创建数据. 为了使此过程更简单,必须在data_collector_spec.rb中实现每个测试用例,在该data_collector_spec.rb中,通过DataCollector对该阶段进行测试. 每个测试用例都将变成多个测试,涵盖以下情况:
- 不同的父母: 
Group或Project - 不同的计算方式: 
Median,RecordsFetcher或DataForDurationChart