# Value Stream Analytics development guide > 原文:[https://docs.gitlab.com/ee/development/value_stream_analytics.html](https://docs.gitlab.com/ee/development/value_stream_analytics.html) * [Stage](#stage) * [Events](#events) * [Implementing an `Event` class](#implementing-an-event-class) * [Validating start and end events](#validating-start-and-end-events) * [Parent](#parent) * [How parent relationship it work](#how-parent-relationship-it-work) * [Default stages](#default-stages) * [Data Collector](#data-collector) * [Database query](#database-query) * [High-level overview](#high-level-overview) * [Testing](#testing) # Value Stream Analytics development guide[](#value-stream-analytics-development-guide "Permalink") 值流分析计算域对象上记录的两个任意事件之间的时间,并提供有关持续时间的汇总统计信息. 有关如何在 GitLab 中配置 Value Stream Analytics 的信息,请参阅我们的[分析文档](../user/analytics/value_stream_analytics.html) . ## Stage[](#stage "Permalink") 在开发过程中,会发生一些事件,这些事件会移动问题并在不同的进展阶段合并请求,直到认为它们完成为止. 这些阶段可以用`Stage`模型表示. 示例阶段: * Name: Development * 开始事件:问题已创建 * 结束事件:在提交中首先提到的问题 * Parent: `Group: gitlab-org` ### Events[](#events "Permalink") 事件是价值流分析功能的最小构建块. 一个阶段包括两个事件: * Start * End 这些事件在持续时间计算中起关键作用. Formula: `duration = end_event_time - start_event_time` 为了使持续时间计算更加灵活,每个`Event`都实现为一个单独的类. 他们负责定义将在计算查询中使用的时间戳表达式. #### Implementing an `Event` class[](#implementing-an-event-class "Permalink") 有一些方法需要实现, `StageEvent`基类非常详细地描述了它们. 最重要的是: * `object_type` * `timestamp_projection` `object_type`方法定义将查询哪个域对象以进行计算. 当前允许两种模型: * `Issue` * `MergeRequest` 对于持续时间计算,将使用`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[](#validating-start-and-end-events "Permalink") 某些开始/结束事件对彼此不"兼容". 例如: * 从"已创建问题"到"已合并请求":事件类在不同的域模型上定义, `object_type`方法不同. * "问题已关闭"到"问题已创建":必须先创建问题,然后才能将其关闭. * 从"问题已关闭"到"问题已关闭":持续时间始终为 0. `StageEvents`模块描述了允许的`start_event`和`end_event`配对( `PAIRING_RULES`常数). 如果添加了新事件,则需要在该模块中注册它. 要添加新事件: 1. 在`ENUM_MAPPING`添加一个具有唯一编号的条目,该条目将在`Stage`模型中用作`enum` . 2. 在`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[](#parent "Permalink") 团队和组织可能会定义自己的软件构建方式,因此阶段可能完全不同. 对于每个阶段,都需要定义一个父对象. 目前支持的父母: * `Project` * `Group` #### How parent relationship it work[](#how-parent-relationship-it-work "Permalink") 1. 用户导航到价值流分析页面. 2. 用户选择一个组. 3. 后端将加载选定组的已定义阶段. 4. 对阶段的添加和修改将仅保留在所选组中. ### Default stages[](#default-stages "Permalink") 价值流分析的[原始实施](https://gitlab.com/gitlab-org/gitlab/-/issues/847)定义了 7 个阶段. 每个父母都可以使用这些阶段,但是无法更改这些阶段. 为了提高效率并减少创建的记录数,默认阶段被表示为内存中对象(不持久). 当用户首次创建自定义阶段时,所有阶段都将保留. 此行为在价值流分析服务对象中实现. 这样做的原因是我们希望稍后添加隐藏和订购阶段的功能. ## Data Collector[](#data-collector "Permalink") `DataCollector`是从数据库查询数据的中心点. 该类始终在单个阶段上运行,并且由以下组件组成: * `BaseQueryBuilder`: * 负责编写初始查询. * 处理特定于`Stage`配置:事件及其查询自定义. * 来自用户界面的参数:日期范围. * `Median` :使用`BaseQueryBuilder`的查询计算一个阶段的中位数持续时间. * `RecordsFetcher` :使用来自`BaseQueryBuilder`的查询和特定的`Finder`类加载阶段的相关记录,以应用可见性规则. * `DataForDurationChart` :加载散点图的带有完成时间(结束事件时间戳)的计算的持续时间. 对于新的计算或查询,可将其实现为`DataCollector`类中的新方法调用. ## Database query[](#database-query "Permalink") 数据库查询的结构: ``` 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[](#high-level-overview "Permalink") * Rails 控制器( `Analytics::CycleAnalytics`模块):值流分析通过 JSON 端点公开其数据,该端点在`analytics`工作区中实现. 配置阶段还实现 JSON 端点(CRUD). * 服务( `Analytics::CycleAnalytics`模块):所有与`Stage`相关的操作都将委派给相应的服务对象. * 模型( `Analytics::CycleAnalytics`模块):模型用于持久化`Stage`对象`ProjectStage`和`GroupStage` . * 要素类( `Gitlab::Analytics::CycleAnalytics`模块): * 负责撰写查询并定义特定于功能的业务逻辑. * `DataCollector` , `Event` , `StageEvents`等. ## Testing[](#testing "Permalink") 由于我们有很多事件和可能的配对,因此无法测试每个配对. 规则是至少要有一个使用`Event`类的测试用例. 使用新`Event`为阶段编写测试用例可能会遇到挑战,因为必须为两个事件都创建数据. 为了使此过程更简单,必须在`data_collector_spec.rb`中实现每个测试用例,在该`data_collector_spec.rb`中,通过`DataCollector`对该阶段进行测试. 每个测试用例都将变成多个测试,涵盖以下情况: * 不同的父母: `Group`或`Project` * 不同的计算方式: `Median` , `RecordsFetcher`或`DataForDurationChart`