Help us learn about your current experience with the documentation. Take the survey.

价值流分析开发指南

有关如何在 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_type
  • timestamp_projection

object_type 方法定义查询计算使用哪个领域对象。目前允许两个模型:

  • Issue
  • MergeRequest

对于持续时间计算,使用 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_eventend_event 配对(PAIRING_RULES 常量)。如果添加新事件,需要在此模块中注册它。 添加新事件的步骤:

  1. ENUM_MAPPING 中添加一个具有唯一数字的条目,该数字在 Stage 模型中用作 enum
  2. 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 模块):
    • 负责构建查询并定义功能特定的业务逻辑。
    • DataCollectorEventStageEvents 等。

前端

项目 VSA 对所有用户可用,并且:

  • 基于层级包含关键指标和 DORA 指标的混合。
  • 使用默认阶段集合。

群组 VSA 仅对授权用户可用,并扩展项目 VSA 以包括:

  • 一个概览阶段
  • 创建自定义价值流的能力。

群组和项目级别的 VSA 前端都使用 Vue 和 Vuex 构建,并遵循相似的模式:

  • index.js 文件提取任何 URL 查询参数,创建 Vue 应用和 Vuex 存储,并调度 initialize Vuex 操作。
  • 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 测试阶段。每个测试案例会变成多个测试,涵盖以下情况:

  • 不同的父级:GroupProject
  • 不同的计算:MedianRecordsFetcherDataForDurationChart

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

种子数据

价值流分析

有关如何为价值流分析播种数据的说明,请参阅开发种子文件