价值流分析开发指南
有关如何在 GitLab 中配置价值流分析(VSA)的信息,请参阅我们的分析文档。
价值流分析是如何工作的?
价值流分析计算两个时间戳列或时间戳表达式之间的持续时间,并对数据运行各种聚合。
例如:
- 合并请求创建时间与合并请求合并时间之间的持续时间。
- 问题创建时间与问题关闭时间之间的持续时间。
此持续时间通过多种方式展示:
- 聚合:中位数、平均值
- 列表:列出单个合并请求和问题记录的持续时间
除了持续时间外,我们还展示了各阶段内的记录数量。
功能可用性
- 群组级别(需授权):需要 Ultimate 或 Premium 订阅。此版本功能最全面。
- 项目级别(需授权):我们持续为项目级别的 VSA 添加功能,使其与群组级别的 VSA 保持一致。
- 项目级别(FOSS):保持原样。
| 功能 | 群组级别(需授权) | 项目级别(需授权) | 项目级别(FOSS) |
|---|---|---|---|
| 创建自定义价值流 | 是 | 否,只有一个价值流(默认),包含默认阶段 | 否,只有一个价值流(默认),包含默认阶段 |
| 创建自定义阶段 | 是 | 否 | 否 |
| 过滤(作者、标签、里程碑等) | 是 | 是 | 是 |
| 阶段时间图表 | 是 | 否 | 否 |
| 总时间图表 | 是 | 否 | 否 |
| 按类型任务图表 | 是 | 否 | 否 |
| DORA 指标 | 是 | 是 | 否 |
| 周期时间和前置时间摘要(生命周期指标) | 是 | 是 | 否 |
| 新问题、提交和部署(生命周期指标) | 是,不包括提交 | 是 | 是 |
| 使用聚合后端 | 是 | 否 | 否 |
| 日期过滤器行为 | 过滤在日期范围内完成的项目 | 按创建日期过滤项目 | 按创建日期过滤项目 |
| 授权 | 至少报告者 | 至少报告者 | 可公开访问 |
VSA 核心领域对象
阶段
阶段表示一个事件对(开始和结束事件),并带有额外的元数据,如阶段的名称。阶段在用户在后端定义的配对规则内是可配置的。
示例阶段:代码审查
- 开始事件标识符:合并请求创建时间。
- 开始事件列:使用
merge_requests.created_at时间戳列。 - 结束事件标识符:合并请求合并时间。
- 结束事件列:使用
merge_request_metrics.merged_at时间戳列。 - 阶段事件哈希 ID:开始和结束事件标识符对的计算哈希值。
- 如果两个阶段具有相同的开始和结束事件配置,则它们的阶段事件哈希 ID 相同。
- 阶段事件哈希 ID 后来用于在分区数据库表中存储聚合数据。
历史上,价值流分析定义了六个阶段,无论订阅如何,这些阶段始终可供最终用户使用。
价值流
价值流是阶段的容器对象。每个群组可以有多个价值流,专注于 DevOps 生命周期的不同方面。
事件
事件是价值流分析功能的最小构建块。一个阶段由两个事件组成:
- 开始事件
- 结束事件
这些事件在持续时间计算中起着关键作用。
公式:持续时间 = 结束事件时间 - 开始事件时间
为了使持续时间计算更加灵活,每个 Event 都被实现为一个单独的类。它们负责定义在计算查询中使用的时间戳表达式。
实现 Event 类
您必须实现 StageEvent 基类中描述的几个方法。最重要的方法是:
object_typetimestamp_projection
object_type 方法定义查询计算使用哪个领域对象。目前允许两个模型:
IssueMergeRequest
对于持续时间计算,使用 timestamp_projection 方法。
def timestamp_projection
# 您的时间戳表达式放在这里
end
# 事件将在持续时间计算中使用问题创建时间
def timestamp_projection
Issue.arel_table[:created_at]
end也可以使用更复杂的表达式(例如使用 COALESCE)。查看现有的事件类以获取示例。
在某些情况下,仅定义 timestamp_projection 方法是不够的。计算查询应该知道哪个表包含时间戳表达式。每个 Event 类负责修改计算查询以使 timestamp_projection 工作。这通常意味着连接一个额外的表。
连接 issue_metrics 表并使用 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)
# 在这种情况下,query 属性将基于 Issue 模型:`Issue.where(...)`
query.joins(:metrics)
end验证开始和结束事件
一些开始/结束事件对彼此不"兼容"。例如:
- “问题创建"到"合并请求创建”:事件类在不同的领域模型上定义,
object_type方法不同。 - “问题关闭"到"问题创建”:问题必须先创建才能关闭。
- “问题关闭"到"问题关闭”:持续时间始终为 0。
StageEvents 模块描述了允许的 start_event 和 end_event 配对(PAIRING_RULES 常量)。如果添加新事件,需要在此模块中注册它。
添加新事件的步骤:
- 在
ENUM_MAPPING中添加一个具有唯一数字的条目,该数字在Stage模型中用作enum。 - 在
PAIRING_RULES哈希中定义哪些事件与此事件兼容。
支持的开始/结束事件配对:
graph LR; IssueCreated --> IssueClosed; IssueCreated --> IssueFirstAddedToBoard; IssueCreated --> IssueFirstAssociatedWithMilestone; IssueCreated --> IssueFirstMentionedInCommit; IssueCreated --> IssueLastEdited; IssueCreated --> IssueLabelAdded; IssueCreated --> IssueLabelRemoved; IssueCreated --> IssueFirstAssignedAt; MergeRequestCreated --> MergeRequestMerged; MergeRequestCreated --> MergeRequestClosed; MergeRequestCreated --> MergeRequestFirstDeployedToProduction; MergeRequestCreated --> MergeRequestLastBuildStarted; MergeRequestCreated --> MergeRequestLastBuildFinished; MergeRequestCreated --> MergeRequestLastEdited; MergeRequestCreated --> MergeRequestLabelAdded; MergeRequestCreated --> MergeRequestLabelRemoved; MergeRequestCreated --> MergeRequestFirstAssignedAt; MergeRequestFirstAssignedAt --> MergeRequestClosed; MergeRequestFirstAssignedAt --> MergeRequestLastBuildStarted; MergeRequestFirstAssignedAt --> MergeRequestLastEdited; MergeRequestFirstAssignedAt --> MergeRequestMerged; MergeRequestFirstAssignedAt --> MergeRequestLabelAdded; MergeRequestFirstAssignedAt --> 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; IssueLabelAdded --> IssueFirstAssignedAt; IssueLabelRemoved --> IssueClosed; IssueLabelRemoved --> IssueFirstAssignedAt; IssueFirstAddedToBoard --> IssueClosed; IssueFirstAddedToBoard --> IssueFirstAssociatedWithMilestone; IssueFirstAddedToBoard --> IssueFirstMentionedInCommit; IssueFirstAddedToBoard --> IssueLastEdited; IssueFirstAddedToBoard --> IssueLabelAdded; IssueFirstAddedToBoard --> IssueLabelRemoved; IssueFirstAddedToBoard --> IssueFirstAssignedAt; IssueFirstAssignedAt --> IssueClosed; IssueFirstAssignedAt --> IssueFirstAddedToBoard; IssueFirstAssignedAt --> IssueFirstAssociatedWithMilestone; IssueFirstAssignedAt --> IssueFirstMentionedInCommit; IssueFirstAssignedAt --> IssueLastEdited; IssueFirstAssignedAt --> IssueLabelAdded; IssueFirstAssignedAt --> IssueLabelRemoved; IssueFirstAssociatedWithMilestone --> IssueClosed; IssueFirstAssociatedWithMilestone --> IssueFirstAddedToBoard; IssueFirstAssociatedWithMilestone --> IssueFirstMentionedInCommit; IssueFirstAssociatedWithMilestone --> IssueLastEdited; IssueFirstAssociatedWithMilestone --> IssueLabelAdded; IssueFirstAssociatedWithMilestone --> IssueLabelRemoved; IssueFirstAssociatedWithMilestone --> IssueFirstAssignedAt; IssueFirstMentionedInCommit --> IssueClosed; IssueFirstMentionedInCommit --> IssueFirstAssociatedWithMilestone; IssueFirstMentionedInCommit --> IssueFirstAddedToBoard; IssueFirstMentionedInCommit --> IssueLastEdited; IssueFirstMentionedInCommit --> IssueLabelAdded; IssueFirstMentionedInCommit --> IssueLabelRemoved; 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; MergeRequestLabelAdded --> MergeRequestMerged; MergeRequestLabelAdded --> MergeRequestFirstAssignedAt; MergeRequestLabelRemoved --> MergeRequestLabelAdded; MergeRequestLabelRemoved --> MergeRequestLabelRemoved; MergeRequestLabelRemoved --> MergeRequestFirstAssignedAt;
默认阶段
价值流分析的原始实现定义了 7 个阶段。这些阶段始终可用于每个父级,但是无法更改这些阶段。
为了提高效率并减少创建的记录数量,默认阶段表示为内存中的对象(不持久化)。当用户首次创建自定义阶段时,所有阶段都会被持久化。此行为在价值流分析服务对象中实现。
这样做的目的是我们希望稍后能够隐藏和排序阶段。
数据收集器
DataCollector 是从数据库查询数据的中心点。该类始终在单个阶段上运行,并由以下组件组成:
BaseQueryBuilder:- 负责构建初始查询。
- 处理
Stage特定配置:事件及其查询自定义。 - 来自 UI 的参数:日期范围。
Median:使用来自BaseQueryBuilder的查询计算阶段的中位数持续时间。RecordsFetcher:使用来自BaseQueryBuilder的查询和特定的Finder类加载相关记录,以应用可见性规则。DataForDurationChart:为散点图图表加载计算出的持续时间和完成时间(结束事件时间戳)。
对于新的计算或查询,在 DataCollector 类中实现它作为新的方法调用。
为了支持聚合的价值流分析后端,这些类在 Aggregated 命名空间内重新实现。
数据库查询后端
VSA 支持两个后端:聚合和"实时"。实时查询后端可以被视为遗留功能,将在某个时间点逐步淘汰。
- “实时”:使用标准的
IssuableFinders。 - 聚合:从预聚合的数据库表中查询数据。
高级概述
- Rails Controller(
Analytics::CycleAnalytics模块):价值流分析通过 JSON 端点公开其数据,在analytics工作区内实现。配置阶段也实现了 JSON 端点(CRUD)。 - Services(
Analytics::CycleAnalytics模块):所有Stage相关操作都委托给相应的服务对象。 - Models(
Analytics::CycleAnalytics模块):模型用于持久化Stage对象。 - Feature 类(
Gitlab::Analytics::CycleAnalytics模块):- 负责构建查询并定义功能特定的业务逻辑。
DataCollector、Event、StageEvents等。
前端
项目 VSA 对所有用户可用,并且:
- 基于层级包含关键指标和 DORA 指标的混合。
- 使用默认阶段集合。
群组 VSA 仅对授权用户可用,并扩展项目 VSA 以包括:
- 一个概览阶段。
- 创建自定义价值流的能力。
群组和项目级别的 VSA 前端都使用 Vue 和 Vuex 构建,并遵循相似的模式:
index.js文件提取任何 URL 查询参数,创建 Vue 应用和 Vuex 存储,并调度initializeVuex 操作。base.vue文件用于渲染每个页面的主要组件、指标、过滤器、图表和阶段表。
群组 VSA Vuex 存储利用 Vuex 模块来分离用于渲染图表的一些状态和逻辑。
共享组件
部分 UI 在项目 VSA 和群组 VSA 之间共享,如阶段表和路径。这些共享组件位于项目 VSA 目录 app/assets/javascripts/cycle_analytics/components 中,并在需要时在群组级别 VSA 中包含。
群组级别功能的所有前端代码都位于 ee/app/assets/javascripts/analytics/cycle_analytics/components。
测试
由于我们有很多事件和可能的配对,测试每个配对是不可能的。规则是至少有一个测试案例使用 Event 类。
使用新的 Event 为阶段编写测试案例可能具有挑战性,因为必须为两个事件创建数据。为了简化这一点,每个测试案例必须在 data_collector_spec.rb 中实现,其中通过 DataCollector 测试阶段。每个测试案例会变成多个测试,涵盖以下情况:
- 不同的父级:
Group或Project - 不同的计算:
Median、RecordsFetcher或DataForDurationChart
VSA 前端在两个不同级别(集成、单元)上进行了广泛测试:
- 使用 Capybara 和 RSpec 通过真实后端进行端到端集成测试。
- 使用预生成的数据固定装置进行 Jest 前端测试。
开发设置和测试
可以通过 GDK 运行价值流分析。默认情况下,您将能够查看该功能的项目级别(FOSS)版本。
如果您的 GDK 正在运行,您可以运行种子脚本来生成一些数据:
SEED_CYCLE_ANALYTICS=true SEED_VSA=true FILTER=cycle_analytics rake db:seed_fu数据生成器脚本创建一个新的群组和一个新的项目,其中包含问题和合并请求数据(请参阅脚本的输出)。要查看该功能的群组级别版本,您需要为您的 GDK 实例请求许可证。
在此步骤之后,您可以访问群组级别价值流分析页面,您可以在其中创建价值流和阶段。数据聚合可能会有延迟,因此在阶段创建后您可能不会立即看到数据。为了加快此过程,您可以在 rails 控制台(rails c)中运行以下命令:
Analytics::CycleAnalytics::ReaggregationWorker.new.perform种子数据
价值流分析
有关如何为价值流分析播种数据的说明,请参阅开发种子文件。