Commit a1cf08b7 authored by Robert Hunt's avatar Robert Hunt Committed by Michael Kozono

Rename cycle analytics to value stream analytics

This is only to rename the user perceivable (façades)
elements of cycle analytics to value stream analytics.
This includes:
- Renaming content
- Renaming error messages
- Renaming routes
- Renaming docs

BE/FE codebase specific changes need to be done as a separate MR
parent 2daf2ee3
...@@ -27,7 +27,7 @@ export default { ...@@ -27,7 +27,7 @@ export default {
<template> <template>
<div class="landing content-block"> <div class="landing content-block">
<button <button
:aria-label="__('Dismiss Cycle Analytics introduction box')" :aria-label="__('Dismiss Value Stream Analytics introduction box')"
class="js-ca-dismiss-button dismiss-button" class="js-ca-dismiss-button dismiss-button"
type="button" type="button"
@click="dismissOverviewDialog" @click="dismissOverviewDialog"
...@@ -36,10 +36,10 @@ export default { ...@@ -36,10 +36,10 @@ export default {
</button> </button>
<div class="svg-container" v-html="iconCycleAnalyticsSplash"></div> <div class="svg-container" v-html="iconCycleAnalyticsSplash"></div>
<div class="inner-content"> <div class="inner-content">
<h4>{{ __('Introducing Cycle Analytics') }}</h4> <h4>{{ __('Introducing Value Stream Analytics') }}</h4>
<p> <p>
{{ {{
__(`Cycle Analytics gives an overview __(`Value Stream Analytics gives an overview
of how much time it takes to go from idea to production in your project.`) of how much time it takes to go from idea to production in your project.`)
}} }}
</p> </p>
......
...@@ -71,7 +71,7 @@ export default () => { ...@@ -71,7 +71,7 @@ export default () => {
}, },
created() { created() {
// Conditional check placed here to prevent this method from being called on the // Conditional check placed here to prevent this method from being called on the
// new Cycle Analytics page (i.e. the new page will be initialized blank and only // new Value Stream Analytics page (i.e. the new page will be initialized blank and only
// after a group is selected the cycle analyitcs data will be fetched). Once the // after a group is selected the cycle analyitcs data will be fetched). Once the
// old (current) page has been removed this entire created method as well as the // old (current) page has been removed this entire created method as well as the
// variable itself can be completely removed. // variable itself can be completely removed.
...@@ -81,7 +81,7 @@ export default () => { ...@@ -81,7 +81,7 @@ export default () => {
methods: { methods: {
handleError() { handleError() {
this.store.setErrorState(true); this.store.setErrorState(true);
return new Flash(__('There was an error while fetching cycle analytics data.')); return new Flash(__('There was an error while fetching value stream analytics data.'));
}, },
initDropdown() { initDropdown() {
const $dropdown = $('.js-ca-dropdown'); const $dropdown = $('.js-ca-dropdown');
......
...@@ -579,7 +579,7 @@ $calendar-border-color: rgba(#000, 0.1); ...@@ -579,7 +579,7 @@ $calendar-border-color: rgba(#000, 0.1);
$calendar-user-contrib-text: #959494; $calendar-user-contrib-text: #959494;
/* /*
* Cycle Analytics * Value Stream Analytics
*/ */
$cycle-analytics-box-padding: 30px; $cycle-analytics-box-padding: 30px;
$cycle-analytics-box-text-color: #8c8c8c; $cycle-analytics-box-text-color: #8c8c8c;
......
...@@ -35,7 +35,7 @@ module AnalyticsNavbarHelper ...@@ -35,7 +35,7 @@ module AnalyticsNavbarHelper
return unless project_nav_tab?(:cycle_analytics) return unless project_nav_tab?(:cycle_analytics)
navbar_sub_item( navbar_sub_item(
title: _('Cycle Analytics'), title: _('Value Stream Analytics'),
path: 'cycle_analytics#show', path: 'cycle_analytics#show',
link: project_cycle_analytics_path(project), link: project_cycle_analytics_path(project),
link_to_options: { class: 'shortcuts-project-cycle-analytics' } link_to_options: { class: 'shortcuts-project-cycle-analytics' }
......
...@@ -42,8 +42,8 @@ ...@@ -42,8 +42,8 @@
- unless should_display_analytics_pages_in_sidebar - unless should_display_analytics_pages_in_sidebar
- if can?(current_user, :read_cycle_analytics, @project) - if can?(current_user, :read_cycle_analytics, @project)
= nav_link(path: 'cycle_analytics#show') do = nav_link(path: 'cycle_analytics#show') do
= link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do = link_to project_cycle_analytics_path(@project), title: _('Value Stream Analytics'), class: 'shortcuts-project-cycle-analytics' do
%span= _('Cycle Analytics') %span= _('Value Stream Analytics')
= render_if_exists 'layouts/nav/project_insights_link' = render_if_exists 'layouts/nav/project_insights_link'
......
...@@ -4,12 +4,12 @@ ...@@ -4,12 +4,12 @@
.col-md-10.offset-md-1 .col-md-10.offset-md-1
.row.overview-details .row.overview-details
.col-md-6.overview-text .col-md-6.overview-text
%h4 Introducing Cycle Analytics %h4 Introducing Value Stream Analytics
%p %p
Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project. Value Stream Analytics (VSA) gives an overview of how much time it takes to go from idea to production in your project.
To set up CA, you must first define a production environment by setting up your CI and then deploy to production. To set up VSA, you must first define a production environment by setting up your CI and then deploy to production.
%p %p
%a.btn{ href: help_page_path('user/analytics/cycle_analytics.md'), target: '_blank' } Read more %a.btn{ href: help_page_path('user/analytics/value_stream_analytics.md'), target: '_blank' } Read more
.col-md-6.overview-image .col-md-6.overview-image
%span.overview-icon %span.overview-icon
= custom_icon ('icon_cycle_analytics_overview') = custom_icon ('icon_cycle_analytics_overview')
- page_title "Cycle Analytics" - page_title "Value Stream Analytics"
#cycle-analytics{ "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } } #cycle-analytics{ "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } }
- if @cycle_analytics_no_data - if @cycle_analytics_no_data
%banner{ "v-if" => "!isOverviewDialogDismissed", %banner{ "v-if" => "!isOverviewDialogDismissed",
"documentation-link": help_page_path('user/analytics/cycle_analytics.md'), "documentation-link": help_page_path('user/analytics/value_stream_analytics.md'),
"v-on:dismiss-overview-dialog" => "dismissOverviewDialog()" } "v-on:dismiss-overview-dialog" => "dismissOverviewDialog()" }
= icon("spinner spin", "v-show" => "isLoading") = icon("spinner spin", "v-show" => "isLoading")
.wrapper{ "v-show" => "!isLoading && !hasError" } .wrapper{ "v-show" => "!isLoading && !hasError" }
......
---
title: Rename cycle analytics interfaces to value stream analytics
merge_request: 23427
author:
type: changed
...@@ -195,9 +195,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -195,9 +195,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
end end
resource :cycle_analytics, only: [:show] resource :cycle_analytics, only: :show, path: 'value_stream_analytics'
scope module: :cycle_analytics, as: 'cycle_analytics', path: 'value_stream_analytics' do
namespace :cycle_analytics do
scope :events, controller: 'events' do scope :events, controller: 'events' do
get :issue get :issue
get :plan get :plan
...@@ -208,6 +207,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -208,6 +207,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :production get :production
end end
end end
get '/cycle_analytics', to: redirect('%{namespace_id}/%{project_id}/-/value_stream_analytics')
concerns :clusterable concerns :clusterable
......
...@@ -109,7 +109,7 @@ class Gitlab::Seeder::CycleAnalytics ...@@ -109,7 +109,7 @@ class Gitlab::Seeder::CycleAnalytics
def create_issues def create_issues
Array.new(@issue_count) do Array.new(@issue_count) do
issue_params = { issue_params = {
title: "Cycle Analytics: #{FFaker::Lorem.sentence(6)}", title: "Value Stream Analytics: #{FFaker::Lorem.sentence(6)}",
description: FFaker::Lorem.sentence, description: FFaker::Lorem.sentence,
state: 'opened', state: 'opened',
assignees: [@project.team.users.sample] assignees: [@project.team.users.sample]
...@@ -166,7 +166,7 @@ class Gitlab::Seeder::CycleAnalytics ...@@ -166,7 +166,7 @@ class Gitlab::Seeder::CycleAnalytics
Timecop.travel 12.hours.from_now Timecop.travel 12.hours.from_now
opts = { opts = {
title: 'Cycle Analytics merge_request', title: 'Value Stream Analytics merge_request',
description: "Fixes #{issue.to_reference}", description: "Fixes #{issue.to_reference}",
source_branch: branch, source_branch: branch,
target_branch: 'master' target_branch: 'master'
......
...@@ -88,7 +88,7 @@ The following documentation relates to the DevOps **Manage** stage: ...@@ -88,7 +88,7 @@ The following documentation relates to the DevOps **Manage** stage:
| Manage Topics | Description | | Manage Topics | Description |
|:--------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |:--------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Authentication and<br/>Authorization](administration/auth/README.md) **(CORE ONLY)** | Supported authentication and authorization providers. | | [Authentication and<br/>Authorization](administration/auth/README.md) **(CORE ONLY)** | Supported authentication and authorization providers. |
| [GitLab Cycle Analytics](user/project/cycle_analytics.md) | Measure the time it takes to go from an [idea to production](https://about.gitlab.com/blog/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have. | | [GitLab Value Stream Analytics](user/project/cycle_analytics.md) | Measure the time it takes to go from an [idea to production](https://about.gitlab.com/blog/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have. |
| [Instance Statistics](user/instance_statistics/index.md) | Discover statistics on how many GitLab features you use and user activity. | | [Instance Statistics](user/instance_statistics/index.md) | Discover statistics on how many GitLab features you use and user activity. |
<div align="right"> <div align="right">
......
...@@ -220,7 +220,7 @@ deployment will be recorded as a new environment named `production`. ...@@ -220,7 +220,7 @@ deployment will be recorded as a new environment named `production`.
NOTE: **Note:** NOTE: **Note:**
If your environment's name is `production` (all lowercase), If your environment's name is `production` (all lowercase),
it will get recorded in [Cycle Analytics](../user/project/cycle_analytics.md). it will get recorded in [Value Stream Analytics](../user/project/cycle_analytics.md).
### Configuring dynamic environments ### Configuring dynamic environments
......
...@@ -80,7 +80,7 @@ Complementary reads: ...@@ -80,7 +80,7 @@ Complementary reads:
- [File uploads](uploads.md) - [File uploads](uploads.md)
- [Auto DevOps development guide](auto_devops.md) - [Auto DevOps development guide](auto_devops.md)
- [Mass Inserting Models](mass_insert.md) - [Mass Inserting Models](mass_insert.md)
- [Cycle Analytics development guide](cycle_analytics.md) - [Value Stream Analytics development guide](value_stream_analytics.md)
- [Issue types vs first-class types](issue_types.md) - [Issue types vs first-class types](issue_types.md)
- [Application limits](application_limits.md) - [Application limits](application_limits.md)
- [Redis guidelines](redis.md) - [Redis guidelines](redis.md)
......
# Cycle Analytics development guide ---
redirect_to: 'value_stream_analytics.md'
---
Cycle analytics calculates the time between two arbitrary events recorded on domain objects and provides aggregated statistics about the duration. This document was moved to [another location](value_stream_analytics.md)
## Stage
During development, events occur that move issues and merge requests through different stages of progress until they are considered finished. These stages can be expressed with the `Stage` model.
Example stage:
- Name: Development
- Start event: Issue created
- End event: Issue first mentioned in commit
- Parent: `Group: gitlab-org`
### Events
Events are the smallest building blocks of the cycle analytics feature. A stage consists of two events:
- Start
- End
These events play a key role in the duration calculation.
Formula: `duration = end_event_time - start_event_time`
To make the duration calculation flexible, each `Event` is implemented as a separate class. They're responsible for defining a timestamp expression that will be used in the calculation query.
#### Implementing an `Event` class
There are a few methods that are required to be implemented, the `StageEvent` base class describes them in great detail. The most important ones are:
- `object_type`
- `timestamp_projection`
The `object_type` method defines which domain object will be queried for the calculation. Currently two models are allowed:
- `Issue`
- `MergeRequest`
For the duration calculation the `timestamp_projection` method will be used.
```ruby
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
```
NOTE: **Note:**
More complex expressions are also possible (e.g. using `COALESCE`). Look at the existing event classes for examples.
In some cases, defining the `timestamp_projection` method is not enough. The calculation query should know which table contains the timestamp expression. Each `Event` class is responsible for making modifications to the calculation query to make the `timestamp_projection` work. This usually means joining an additional table.
Example for joining the `issue_metrics` table and using the `first_mentioned_in_commit_at` column as the timestamp expression:
```ruby
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
Some start/end event pairs are not "compatible" with each other. For example:
- "Issue created" to "Merge Request created": The event classes are defined on different domain models, the `object_type` method is different.
- "Issue closed" to "Issue created": Issue must be created first before it can be closed.
- "Issue closed" to "Issue closed": Duration is always 0.
The `StageEvents` module describes the allowed `start_event` and `end_event` pairings (`PAIRING_RULES` constant). If a new event is added, it needs to be registered in this module.
​To add a new event:​
1. Add an entry in `ENUM_MAPPING` with a unique number, it'll be used in the `Stage` model as `enum`.
1. Define which events are compatible with the event in the `PAIRING_RULES` hash.
Supported start/end event pairings:
```mermaid
graph 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 --> 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;
MergeRequestLabelRemoved --> MergeRequestLabelAdded;
MergeRequestLabelRemoved --> MergeRequestLabelRemoved;
```
### Parent
Teams and organizations might define their own way of building software, thus stages can be completely different. For each stage, a parent object needs to be defined.
Currently supported parents:
- `Project`
- `Group`
#### How parent relationship it work
1. User navigates to the cycle analytics page.
1. User selects a group.
1. Backend loads the defined stages for the selected group.
1. Additions and modifications to the stages will be persisted within the selected group only.
### Default stages
The [original implementation](https://gitlab.com/gitlab-org/gitlab/issues/847) of cycle analytics defined 7 stages. These stages are always available for each parent, however altering these stages is not possible.
To make things efficient and reduce the number of records created, the default stages are expressed as in-memory objects (not persisted). When the user creates a custom stage for the first time, all the stages will be persisted. This behaviour is implemented in the cycle analytics service objects.
The reason for this was that we'd like to add the abilities to hide and order stages later on.
## Data Collector
`DataCollector` is the central point where the data will be queried from the database. The class always operates on a single stage and consists of the following components:
- `BaseQueryBuilder`:
- Responsible for composing the initial query.
- Deals with `Stage` specific configuration: events and their query customizations.
- Parameters coming from the UI: date ranges.
- `Median`: Calculates the median duration for a stage using the query from `BaseQueryBuilder`.
- `RecordsFetcher`: Loads relevant records for a stage using the query from `BaseQueryBuilder` and specific `Finder` classes to apply visibility rules.
- `DataForDurationChart`: Loads calculated durations with the finish time (end event timestamp) for the scatterplot chart.
For a new calculation or a query, implement it as a new method call in the `DataCollector` class.
## Database query
Structure of the database query:
```sql
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)
```
Structure of the `SELECT` statement for `Median`:
```sql
SELECT (calculate median from START_EVENT_TIME-END_EVENT_TIME)
```
Structure of the `SELECT` statement for `DataForDurationChart`:
```sql
SELECT (START_EVENT_TIME-END_EVENT_TIME) as duration, END_EVENT.timestamp
```
## High-level overview
- Rails Controller (`Analytics::CycleAnalytics` module): Cycle analytics exposes its data via JSON endpoints, implemented within the `analytics` workspace. Configuring the stages are also implements JSON endpoints (CRUD).
- Services (`Analytics::CycleAnalytics` module): All `Stage` related actions will be delegated to respective service objects.
- Models (`Analytics::CycleAnalytics` module): Models are used to persist the `Stage` objects `ProjectStage` and `GroupStage`.
- Feature classes (`Gitlab::Analytics::CycleAnalytics` module):
- Responsible for composing queries and define feature specific busines logic.
- `DataCollector`, `Event`, `StageEvents`, etc.
## Testing
Since we have a lots of events and possible pairings, testing each pairing is not possible. The rule is to have at least one test case using an `Event` class.
Writing a test case for a stage using a new `Event` can be challenging since data must be created for both events. To make this a bit simpler, each test case must be implemented in the `data_collector_spec.rb` where the stage is tested through the `DataCollector`. Each test case will be turned into multiple tests, covering the following cases:
- Different parents: `Group` or `Project`
- Different calculations: `Median`, `RecordsFetcher` or `DataForDurationChart`
...@@ -559,5 +559,5 @@ Let's suppose you want to add translations for a new language, let's say French. ...@@ -559,5 +559,5 @@ Let's suppose you want to add translations for a new language, let's say French.
```shell ```shell
git add locale/fr/ app/assets/javascripts/locale/fr/ git add locale/fr/ app/assets/javascripts/locale/fr/
git commit -m "Add French translations for Cycle Analytics page" git commit -m "Add French translations for Value Stream Analytics page"
``` ```
# Value Stream Analytics development guide
Value stream analytics calculates the time between two arbitrary events recorded on domain objects and provides aggregated statistics about the duration.
For information on how to configure Value Stream Analytics in GitLab, see our [analytics documentation](../user/analytics/value_stream_analytics.md).
## Stage
During development, events occur that move issues and merge requests through different stages of progress until they are considered finished. These stages can be expressed with the `Stage` model.
Example stage:
- Name: Development
- Start event: Issue created
- End event: Issue first mentioned in commit
- Parent: `Group: gitlab-org`
### Events
Events are the smallest building blocks of the value stream analytics feature. A stage consists of two events:
- Start
- End
These events play a key role in the duration calculation.
Formula: `duration = end_event_time - start_event_time`
To make the duration calculation flexible, each `Event` is implemented as a separate class. They're responsible for defining a timestamp expression that will be used in the calculation query.
#### Implementing an `Event` class
There are a few methods that are required to be implemented, the `StageEvent` base class describes them in great detail. The most important ones are:
- `object_type`
- `timestamp_projection`
The `object_type` method defines which domain object will be queried for the calculation. Currently two models are allowed:
- `Issue`
- `MergeRequest`
For the duration calculation the `timestamp_projection` method will be used.
```ruby
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
```
NOTE: **Note:**
More complex expressions are also possible (e.g. using `COALESCE`). Look at the existing event classes for examples.
In some cases, defining the `timestamp_projection` method is not enough. The calculation query should know which table contains the timestamp expression. Each `Event` class is responsible for making modifications to the calculation query to make the `timestamp_projection` work. This usually means joining an additional table.
Example for joining the `issue_metrics` table and using the `first_mentioned_in_commit_at` column as the timestamp expression:
```ruby
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
Some start/end event pairs are not "compatible" with each other. For example:
- "Issue created" to "Merge Request created": The event classes are defined on different domain models, the `object_type` method is different.
- "Issue closed" to "Issue created": Issue must be created first before it can be closed.
- "Issue closed" to "Issue closed": Duration is always 0.
The `StageEvents` module describes the allowed `start_event` and `end_event` pairings (`PAIRING_RULES` constant). If a new event is added, it needs to be registered in this module.
​To add a new event:​
1. Add an entry in `ENUM_MAPPING` with a unique number, it'll be used in the `Stage` model as `enum`.
1. Define which events are compatible with the event in the `PAIRING_RULES` hash.
Supported start/end event pairings:
```mermaid
graph 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 --> 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;
MergeRequestLabelRemoved --> MergeRequestLabelAdded;
MergeRequestLabelRemoved --> MergeRequestLabelRemoved;
```
### Parent
Teams and organizations might define their own way of building software, thus stages can be completely different. For each stage, a parent object needs to be defined.
Currently supported parents:
- `Project`
- `Group`
#### How parent relationship it work
1. User navigates to the value stream analytics page.
1. User selects a group.
1. Backend loads the defined stages for the selected group.
1. Additions and modifications to the stages will be persisted within the selected group only.
### Default stages
The [original implementation](https://gitlab.com/gitlab-org/gitlab/issues/847) of value stream analytics defined 7 stages. These stages are always available for each parent, however altering these stages is not possible.
To make things efficient and reduce the number of records created, the default stages are expressed as in-memory objects (not persisted). When the user creates a custom stage for the first time, all the stages will be persisted. This behaviour is implemented in the value stream analytics service objects.
The reason for this was that we'd like to add the abilities to hide and order stages later on.
## Data Collector
`DataCollector` is the central point where the data will be queried from the database. The class always operates on a single stage and consists of the following components:
- `BaseQueryBuilder`:
- Responsible for composing the initial query.
- Deals with `Stage` specific configuration: events and their query customizations.
- Parameters coming from the UI: date ranges.
- `Median`: Calculates the median duration for a stage using the query from `BaseQueryBuilder`.
- `RecordsFetcher`: Loads relevant records for a stage using the query from `BaseQueryBuilder` and specific `Finder` classes to apply visibility rules.
- `DataForDurationChart`: Loads calculated durations with the finish time (end event timestamp) for the scatterplot chart.
For a new calculation or a query, implement it as a new method call in the `DataCollector` class.
## Database query
Structure of the database query:
```sql
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)
```
Structure of the `SELECT` statement for `Median`:
```sql
SELECT (calculate median from START_EVENT_TIME-END_EVENT_TIME)
```
Structure of the `SELECT` statement for `DataForDurationChart`:
```sql
SELECT (START_EVENT_TIME-END_EVENT_TIME) as duration, END_EVENT.timestamp
```
## High-level overview
- Rails Controller (`Analytics::CycleAnalytics` module): Value stream analytics exposes its data via JSON endpoints, implemented within the `analytics` workspace. Configuring the stages are also implements JSON endpoints (CRUD).
- Services (`Analytics::CycleAnalytics` module): All `Stage` related actions will be delegated to respective service objects.
- Models (`Analytics::CycleAnalytics` module): Models are used to persist the `Stage` objects `ProjectStage` and `GroupStage`.
- Feature classes (`Gitlab::Analytics::CycleAnalytics` module):
- Responsible for composing queries and define feature specific busines logic.
- `DataCollector`, `Event`, `StageEvents`, etc.
## Testing
Since we have a lots of events and possible pairings, testing each pairing is not possible. The rule is to have at least one test case using an `Event` class.
Writing a test case for a stage using a new `Event` can be challenging since data must be created for both events. To make this a bit simpler, each test case must be implemented in the `data_collector_spec.rb` where the stage is tested through the `DataCollector`. Each test case will be turned into multiple tests, covering the following cases:
- Different parents: `Group` or `Project`
- Different calculations: `Median`, `RecordsFetcher` or `DataForDurationChart`
...@@ -36,7 +36,7 @@ identify improvements that might substantially accelerate your development cycle ...@@ -36,7 +36,7 @@ identify improvements that might substantially accelerate your development cycle
Code Review Analytics can be used when: Code Review Analytics can be used when:
- Your team agrees that code review is moving too slow. - Your team agrees that code review is moving too slow.
- The [Cycle Analytics feature](cycle_analytics.md) shows that reviews are your team's most time-consuming step. - The [Value Stream Analytics feature](value_stream_analytics.md) shows that reviews are your team's most time-consuming step.
You can use Code Review Analytics to see the types of work that are currently moving the slowest, and analyze the patterns You can use Code Review Analytics to see the types of work that are currently moving the slowest, and analyze the patterns
and trends between them. For example: and trends between them. For example:
......
# Cycle Analytics ---
redirect_to: '../analytics/value_stream_analytics.md'
---
> - Introduced prior to GitLab 12.3 at the project level. This document was moved to [another location](../analytics/value_stream_analytics.md)
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12077) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.3 at the group level.
Cycle Analytics measures the time spent to go from an
[idea to production](https://about.gitlab.com/blog/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab)
(also known as cycle time) for each of your projects. Cycle Analytics displays the median time
spent in each stage defined in the process.
NOTE: **Note:**
Use the `cycle_analytics` feature flag to enable at the group level.
Cycle Analytics is useful in order to quickly determine the velocity of a given
project. It points to bottlenecks in the development process, enabling management
to uncover, triage, and identify the root cause of slowdowns in the software development life cycle.
Cycle Analytics is tightly coupled with the [GitLab flow](../../topics/gitlab_flow.md) and
calculates a separate median for each stage.
## Overview
Cycle Analytics is available:
- From GitLab 12.3, at the group level in the analytics workspace (top navigation bar) at
**Analytics > Cycle Analytics**. **(PREMIUM)**
In the future, multiple groups will be selectable which will effectively make this an
instance-level feature.
- At the project level via **Project > Cycle Analytics**.
There are seven stages that are tracked as part of the Cycle Analytics calculations.
- **Issue** (Tracker)
- Time to schedule an issue (by milestone or by adding it to an issue board)
- **Plan** (Board)
- Time to first commit
- **Code** (IDE)
- Time to create a merge request
- **Test** (CI)
- Time it takes GitLab CI/CD to test your code
- **Review** (Merge Request/MR)
- Time spent on code review
- **Staging** (Continuous Deployment)
- Time between merging and deploying to production
- **Total** (Total)
- Total lifecycle time. That is, the velocity of the project or team. [Previously known](https://gitlab.com/gitlab-org/gitlab/issues/38317) as **Production**.
## Date ranges
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/13216) in GitLab 12.4.
GitLab provides the ability to filter analytics based on a date range. To filter results:
1. Select a group.
1. Optionally select a project.
1. Select a date range using the available date pickers.
## How the data is measured
Cycle Analytics records cycle time and data based on the project issues with the
exception of the staging and total stages, where only data deployed to
production are measured.
Specifically, if your CI is not set up and you have not defined a `production`
or `production/*` [environment](../../ci/yaml/README.md#environment), then you will not have any
data for this stage.
Each stage of Cycle Analytics is further described in the table below.
| **Stage** | **Description** |
| --------- | --------------- |
| Issue | Measures the median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whatever comes first. The label will be tracked only if it already has an [Issue Board list](../project/issue_board.md#creating-a-new-list) created for it. |
| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the branch. The very first commit of the branch is the one that triggers the separation between **Plan** and **Code**, and at least one of the commits in the branch needs to contain the related issue number (e.g., `#42`). If none of the commits in the branch mention the related issue number, it is not considered to the measurement time of the stage. |
| Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request (MR) related to that commit. The key to keep the process tracked is to include the [issue closing pattern](../project/issues/managing_issues.md#closing-issues-automatically) to the description of the merge request (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request). If the issue closing pattern is not present in the merge request description, the MR is not considered to the measurement time of the stage. |
| Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. |
| Review | Measures the median time taken to review the merge request that has closing issue pattern, between its creation and until it's merged. |
| Staging | Measures the median time between merging the merge request with closing issue pattern until the very first deployment to production. It's tracked by the environment set to `production` or matching `production/*` (case-sensitive, `Production` won't work) in your GitLab CI configuration. If there isn't a production environment, this is not tracked. |
| Total | The sum of all time (medians) taken to run the entire process, from issue creation to deploying the code to production. [Previously known](https://gitlab.com/gitlab-org/gitlab/issues/38317) as **Production**. |
How this works, behind the scenes:
1. Issues and merge requests are grouped together in pairs, such that for each
`<issue, merge request>` pair, the merge request has the [issue closing pattern](../project/issues/managing_issues.md#closing-issues-automatically)
for the corresponding issue. All other issues and merge requests are **not**
considered.
1. Then the `<issue, merge request>` pairs are filtered out by last XX days (specified
by the UI - default is 90 days). So it prohibits these pairs from being considered.
1. For the remaining `<issue, merge request>` pairs, we check the information that
we need for the stages, like issue creation date, merge request merge time,
etc.
To sum up, anything that doesn't follow [GitLab flow](../../workflow/gitlab_flow.md) will not be tracked and the
Cycle Analytics dashboard will not present any data for:
- Merge requests that do not close an issue.
- Issues not labeled with a label present in the Issue Board or for issues not assigned a milestone.
- Staging and production stages, if the project has no `production` or `production/*`
environment.
## Example workflow
Below is a simple fictional workflow of a single cycle that happens in a
single day passing through all seven stages. Note that if a stage does not have
a start and a stop mark, it is not measured and hence not calculated in the median
time. It is assumed that milestones are created and CI for testing and setting
environments is configured.
1. Issue is created at 09:00 (start of **Issue** stage).
1. Issue is added to a milestone at 11:00 (stop of **Issue** stage / start of
**Plan** stage).
1. Start working on the issue, create a branch locally and make one commit at
12:00.
1. Make a second commit to the branch which mentions the issue number at 12.30
(stop of **Plan** stage / start of **Code** stage).
1. Push branch and create a merge request that contains the [issue closing pattern](../project/issues/managing_issues.md#closing-issues-automatically)
in its description at 14:00 (stop of **Code** stage / start of **Test** and
**Review** stages).
1. The CI starts running your scripts defined in [`.gitlab-ci.yml`](../../ci/yaml/README.md) and
takes 5min (stop of **Test** stage).
1. Review merge request, ensure that everything is OK and merge the merge
request at 19:00. (stop of **Review** stage / start of **Staging** stage).
1. Now that the merge request is merged, a deployment to the `production`
environment starts and finishes at 19:30 (stop of **Staging** stage).
1. The cycle completes and the sum of the median times of the previous stages
is recorded to the **Total** stage. That is the time between creating an
issue and deploying its relevant merge request to production.
From the above example you can conclude the time it took each stage to complete
as long as their total time:
- **Issue**: 2h (11:00 - 09:00)
- **Plan**: 1h (12:00 - 11:00)
- **Code**: 2h (14:00 - 12:00)
- **Test**: 5min
- **Review**: 5h (19:00 - 14:00)
- **Staging**: 30min (19:30 - 19:00)
- **Total**: Since this stage measures the sum of median time of all
previous stages, we cannot calculate it if we don't know the status of the
stages before. In case this is the very first cycle that is run in the project,
then the **Total** time is 10h 30min (19:30 - 09:00)
A few notes:
- In the above example we demonstrated that it doesn't matter if your first
commit doesn't mention the issue number, you can do this later in any commit
of the branch you are working on.
- You can see that the **Test** stage is not calculated to the overall time of
the cycle since it is included in the **Review** process (every MR should be
tested).
- The example above was just **one cycle** of the seven stages. Add multiple
cycles, calculate their median time and the result is what the dashboard of
Cycle Analytics is showing.
## Days to completion chart
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21631) in GitLab 12.6.
This chart visually depicts the total number of days it takes for cycles to be completed.
This chart uses the global page filters for displaying data based on the selected
group, projects, and timeframe. In addition, specific stages can be selected
from within the chart itself.
### Chart median line
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/36675) in GitLab 12.7.
The median line on the chart displays data that is offset by the number of days selected.
For example, if 30 days worth of data has been selected (for example, 2019-12-16 to 2020-01-15) the
median line will represent the previous 30 days worth of data (2019-11-16 to 2019-12-16)
as a metric to compare against.
### Disabling chart
This chart is enabled by default. If you have a self-managed instance, an
administrator can open a Rails console and disable it with the following command:
```ruby
Feature.disable(:cycle_analytics_scatterplot_enabled)
```
### Disabling chart median line
This chart's median line is enabled by default. If you have a self-managed instance, an
administrator can open a Rails console and disable it with the following command:
```ruby
Feature.disable(:cycle_analytics_scatterplot_median_enabled)
```
## Permissions
The current permissions on the Project Cycle Analytics dashboard are:
- Public projects - anyone can access.
- Internal projects - any authenticated user can access.
- Private projects - any member Guest and above can access.
You can [read more about permissions](../../ci/yaml/README.md) in general.
NOTE: **Note:**
As of GitLab 12.3, the project-level page is deprecated. You should access
project-level Cycle Analytics from **Analytics > Cycle Analytics** in the top
navigation bar. We will ensure that the same project-level functionality is available
to CE users in the new analytics space.
For Cycle Analytics functionality introduced in GitLab 12.3 and later:
- Users must have Reporter access or above.
- Features are available only on
[Premium or Silver tiers](https://about.gitlab.com/pricing/) and above.
## More resources
Learn more about Cycle Analytics in the following resources:
- [Cycle Analytics feature page](https://about.gitlab.com/product/cycle-analytics/).
- [Cycle Analytics feature preview](https://about.gitlab.com/blog/2016/09/16/feature-preview-introducing-cycle-analytics/).
- [Cycle Analytics feature highlight](https://about.gitlab.com/blog/2016/09/21/cycle-analytics-feature-highlight/).
...@@ -16,13 +16,13 @@ Once enabled, click on **Analytics** from the top navigation bar. ...@@ -16,13 +16,13 @@ Once enabled, click on **Analytics** from the top navigation bar.
From the centralized analytics workspace, the following analytics are available: From the centralized analytics workspace, the following analytics are available:
- [Code Review Analytics](code_review_analytics.md). **(STARTER)** - [Code Review Analytics](code_review_analytics.md). **(STARTER)**
- [Cycle Analytics](cycle_analytics.md), enabled with the `cycle_analytics` - [Value Stream Analytics](value_stream_analytics.md), enabled with the `cycle_analytics`
[feature flag](../../development/feature_flags/development.md#enabling-a-feature-flag-in-development). **(PREMIUM)** [feature flag](../../development/feature_flags/development.md#enabling-a-feature-flag-in-development). **(PREMIUM)**
- [Productivity Analytics](productivity_analytics.md), enabled with the `productivity_analytics` - [Productivity Analytics](productivity_analytics.md), enabled with the `productivity_analytics`
[feature flag](../../development/feature_flags/development.md#enabling-a-feature-flag-in-development). **(PREMIUM)** [feature flag](../../development/feature_flags/development.md#enabling-a-feature-flag-in-development). **(PREMIUM)**
NOTE: **Note:** NOTE: **Note:**
Project-level Cycle Analytics are still available at a project's **Project > Cycle Analytics**. Project-level Value Stream Analytics are still available at a project's **Project > Value Stream Analytics**.
## Other analytics tools ## Other analytics tools
......
...@@ -7,7 +7,7 @@ Track development velocity with Productivity Analytics. ...@@ -7,7 +7,7 @@ Track development velocity with Productivity Analytics.
For many companies, the development cycle is a blackbox and getting an estimate of how For many companies, the development cycle is a blackbox and getting an estimate of how
long, on average, it takes to deliver features is an enormous endeavor. long, on average, it takes to deliver features is an enormous endeavor.
While [Cycle Analytics](../project/cycle_analytics.md) focuses on the entire While [Value Stream Analytics](../project/cycle_analytics.md) focuses on the entire
Software Development Life Cycle (SDLC) process, Productivity Analytics provides a way for Engineering Management to drill down in a systematic way to uncover patterns and causes for success or failure at an individual, project or group level. Software Development Life Cycle (SDLC) process, Productivity Analytics provides a way for Engineering Management to drill down in a systematic way to uncover patterns and causes for success or failure at an individual, project or group level.
Productivity can slow down for many reasons ranging from degrading code base to quickly growing teams. In order to investigate, department or team leaders can start by visualizing the time it takes for merge requests to be merged. Productivity can slow down for many reasons ranging from degrading code base to quickly growing teams. In order to investigate, department or team leaders can start by visualizing the time it takes for merge requests to be merged.
......
# Value Stream Analytics
> - Introduced as Cycle Analytics prior to GitLab 12.3 at the project level.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12077) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.3 at the group level.
> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23427) from Cycle Analytics to Value Stream Analytics in GitLab 12.8.
Value Stream Analytics measures the time spent to go from an
[idea to production](https://about.gitlab.com/blog/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab)
(also known as cycle time) for each of your projects. Value Stream Analytics displays the median time
spent in each stage defined in the process.
For information on how to contribute to the development of Value Stream Analytics, see our [contributor documentation](../../development/value_stream_analytics.md).
NOTE: **Note:**
Use the `cycle_analytics` feature flag to enable at the group level.
Value Stream Analytics is useful in order to quickly determine the velocity of a given
project. It points to bottlenecks in the development process, enabling management
to uncover, triage, and identify the root cause of slowdowns in the software development life cycle.
Value Stream Analytics is tightly coupled with the [GitLab flow](../../topics/gitlab_flow.md) and
calculates a separate median for each stage.
## Overview
Value Stream Analytics is available:
- From GitLab 12.3, at the group level in the analytics workspace (top navigation bar) at
**Analytics > Value Stream Analytics**. **(PREMIUM)**
In the future, multiple groups will be selectable which will effectively make this an
instance-level feature.
- At the project level via **Project > Value Stream Analytics**.
There are seven stages that are tracked as part of the Value Stream Analytics calculations.
- **Issue** (Tracker)
- Time to schedule an issue (by milestone or by adding it to an issue board)
- **Plan** (Board)
- Time to first commit
- **Code** (IDE)
- Time to create a merge request
- **Test** (CI)
- Time it takes GitLab CI/CD to test your code
- **Review** (Merge Request/MR)
- Time spent on code review
- **Staging** (Continuous Deployment)
- Time between merging and deploying to production
- **Total** (Total)
- Total lifecycle time. That is, the velocity of the project or team. [Previously known](https://gitlab.com/gitlab-org/gitlab/issues/38317) as **Production**.
## Date ranges
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/13216) in GitLab 12.4.
GitLab provides the ability to filter analytics based on a date range. To filter results:
1. Select a group.
1. Optionally select a project.
1. Select a date range using the available date pickers.
## How the data is measured
Value Stream Analytics records cycle time and data based on the project issues with the
exception of the staging and total stages, where only data deployed to
production are measured.
Specifically, if your CI is not set up and you have not defined a `production`
or `production/*` [environment](../../ci/yaml/README.md#environment), then you will not have any
data for this stage.
Each stage of Value Stream Analytics is further described in the table below.
| **Stage** | **Description** |
| --------- | --------------- |
| Issue | Measures the median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whatever comes first. The label will be tracked only if it already has an [Issue Board list](../project/issue_board.md#creating-a-new-list) created for it. |
| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the branch. The very first commit of the branch is the one that triggers the separation between **Plan** and **Code**, and at least one of the commits in the branch needs to contain the related issue number (e.g., `#42`). If none of the commits in the branch mention the related issue number, it is not considered to the measurement time of the stage. |
| Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request (MR) related to that commit. The key to keep the process tracked is to include the [issue closing pattern](../project/issues/managing_issues.md#closing-issues-automatically) to the description of the merge request (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request). If the issue closing pattern is not present in the merge request description, the MR is not considered to the measurement time of the stage. |
| Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. |
| Review | Measures the median time taken to review the merge request that has closing issue pattern, between its creation and until it's merged. |
| Staging | Measures the median time between merging the merge request with closing issue pattern until the very first deployment to production. It's tracked by the environment set to `production` or matching `production/*` (case-sensitive, `Production` won't work) in your GitLab CI configuration. If there isn't a production environment, this is not tracked. |
| Total | The sum of all time (medians) taken to run the entire process, from issue creation to deploying the code to production. [Previously known](https://gitlab.com/gitlab-org/gitlab/issues/38317) as **Production**. |
How this works, behind the scenes:
1. Issues and merge requests are grouped together in pairs, such that for each
`<issue, merge request>` pair, the merge request has the [issue closing pattern](../project/issues/managing_issues.md#closing-issues-automatically)
for the corresponding issue. All other issues and merge requests are **not**
considered.
1. Then the `<issue, merge request>` pairs are filtered out by last XX days (specified
by the UI - default is 90 days). So it prohibits these pairs from being considered.
1. For the remaining `<issue, merge request>` pairs, we check the information that
we need for the stages, like issue creation date, merge request merge time,
etc.
To sum up, anything that doesn't follow [GitLab flow](../../workflow/gitlab_flow.md) will not be tracked and the
Value Stream Analytics dashboard will not present any data for:
- Merge requests that do not close an issue.
- Issues not labeled with a label present in the Issue Board or for issues not assigned a milestone.
- Staging and production stages, if the project has no `production` or `production/*`
environment.
## Example workflow
Below is a simple fictional workflow of a single cycle that happens in a
single day passing through all seven stages. Note that if a stage does not have
a start and a stop mark, it is not measured and hence not calculated in the median
time. It is assumed that milestones are created and CI for testing and setting
environments is configured.
1. Issue is created at 09:00 (start of **Issue** stage).
1. Issue is added to a milestone at 11:00 (stop of **Issue** stage / start of
**Plan** stage).
1. Start working on the issue, create a branch locally and make one commit at
12:00.
1. Make a second commit to the branch which mentions the issue number at 12.30
(stop of **Plan** stage / start of **Code** stage).
1. Push branch and create a merge request that contains the [issue closing pattern](../project/issues/managing_issues.md#closing-issues-automatically)
in its description at 14:00 (stop of **Code** stage / start of **Test** and
**Review** stages).
1. The CI starts running your scripts defined in [`.gitlab-ci.yml`](../../ci/yaml/README.md) and
takes 5min (stop of **Test** stage).
1. Review merge request, ensure that everything is OK and merge the merge
request at 19:00. (stop of **Review** stage / start of **Staging** stage).
1. Now that the merge request is merged, a deployment to the `production`
environment starts and finishes at 19:30 (stop of **Staging** stage).
1. The cycle completes and the sum of the median times of the previous stages
is recorded to the **Total** stage. That is the time between creating an
issue and deploying its relevant merge request to production.
From the above example you can conclude the time it took each stage to complete
as long as their total time:
- **Issue**: 2h (11:00 - 09:00)
- **Plan**: 1h (12:00 - 11:00)
- **Code**: 2h (14:00 - 12:00)
- **Test**: 5min
- **Review**: 5h (19:00 - 14:00)
- **Staging**: 30min (19:30 - 19:00)
- **Total**: Since this stage measures the sum of median time of all
previous stages, we cannot calculate it if we don't know the status of the
stages before. In case this is the very first cycle that is run in the project,
then the **Total** time is 10h 30min (19:30 - 09:00)
A few notes:
- In the above example we demonstrated that it doesn't matter if your first
commit doesn't mention the issue number, you can do this later in any commit
of the branch you are working on.
- You can see that the **Test** stage is not calculated to the overall time of
the cycle since it is included in the **Review** process (every MR should be
tested).
- The example above was just **one cycle** of the seven stages. Add multiple
cycles, calculate their median time and the result is what the dashboard of
Value Stream Analytics is showing.
## Days to completion chart
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21631) in GitLab 12.6.
This chart visually depicts the total number of days it takes for cycles to be completed.
This chart uses the global page filters for displaying data based on the selected
group, projects, and timeframe. In addition, specific stages can be selected
from within the chart itself.
### Chart median line
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/36675) in GitLab 12.7.
The median line on the chart displays data that is offset by the number of days selected.
For example, if 30 days worth of data has been selected (for example, 2019-12-16 to 2020-01-15) the
median line will represent the previous 30 days worth of data (2019-11-16 to 2019-12-16)
as a metric to compare against.
### Disabling chart
This chart is enabled by default. If you have a self-managed instance, an
administrator can open a Rails console and disable it with the following command:
```ruby
Feature.disable(:cycle_analytics_scatterplot_enabled)
```
### Disabling chart median line
This chart's median line is enabled by default. If you have a self-managed instance, an
administrator can open a Rails console and disable it with the following command:
```ruby
Feature.disable(:cycle_analytics_scatterplot_median_enabled)
```
## Permissions
The current permissions on the Project Value Stream Analytics dashboard are:
- Public projects - anyone can access.
- Internal projects - any authenticated user can access.
- Private projects - any member Guest and above can access.
You can [read more about permissions](../../ci/yaml/README.md) in general.
NOTE: **Note:**
As of GitLab 12.3, the project-level page is deprecated. You should access
project-level Value Stream Analytics from **Analytics > Value Stream Analytics** in the top
navigation bar. We will ensure that the same project-level functionality is available
to CE users in the new analytics space.
For Value Stream Analytics functionality introduced in GitLab 12.3 and later:
- Users must have Reporter access or above.
- Features are available only on
[Premium or Silver tiers](https://about.gitlab.com/pricing/) and above.
## More resources
Learn more about Value Stream Analytics in the following resources:
- [Value Stream Analytics feature page](https://about.gitlab.com/product/cycle-analytics/).
- [Value Stream Analytics feature preview](https://about.gitlab.com/blog/2016/09/16/feature-preview-introducing-cycle-analytics/).
- [Value Stream Analytics feature highlight](https://about.gitlab.com/blog/2016/09/21/cycle-analytics-feature-highlight/).
...@@ -45,7 +45,7 @@ GitLab is a Git-based platform that integrates a great number of essential tools ...@@ -45,7 +45,7 @@ GitLab is a Git-based platform that integrates a great number of essential tools
- Building, testing, and deploying with built-in [Continuous Integration](../ci/README.md). - Building, testing, and deploying with built-in [Continuous Integration](../ci/README.md).
- Deploying personal and professional static websites with [GitLab Pages](project/pages/index.md). - Deploying personal and professional static websites with [GitLab Pages](project/pages/index.md).
- Integrating with Docker by using [GitLab Container Registry](packages/container_registry/index.md). - Integrating with Docker by using [GitLab Container Registry](packages/container_registry/index.md).
- Tracking the development lifecycle by using [GitLab Cycle Analytics](project/cycle_analytics.md). - Tracking the development lifecycle by using [GitLab Value Stream Analytics](project/cycle_analytics.md).
With GitLab Enterprise Edition, you can also: With GitLab Enterprise Edition, you can also:
......
...@@ -139,10 +139,10 @@ The following table depicts the various user permission levels in a project. ...@@ -139,10 +139,10 @@ The following table depicts the various user permission levels in a project.
| Force push to protected branches (*4*) | | | | | | | Force push to protected branches (*4*) | | | | | |
| Remove protected branches (*4*) | | | | | | | Remove protected branches (*4*) | | | | | |
\* Owner permission is only available at the group or personal namespace level (and for instance admins) and is inherited by its projects. \* Owner permission is only available at the group or personal namespace level (and for instance admins) and is inherited by its projects.
(*1*): Guest users are able to perform this action on public and internal projects, but not private projects. (*1*): Guest users are able to perform this action on public and internal projects, but not private projects.
(*2*): Guest users can only view the confidential issues they created themselves. (*2*): Guest users can only view the confidential issues they created themselves.
(*3*): If **Public pipelines** is enabled in **Project Settings > CI/CD**. (*3*): If **Public pipelines** is enabled in **Project Settings > CI/CD**.
(*4*): Not allowed for Guest, Reporter, Developer, Maintainer, or Owner. See [Protected Branches](./project/protected_branches.md). (*4*): Not allowed for Guest, Reporter, Developer, Maintainer, or Owner. See [Protected Branches](./project/protected_branches.md).
## Project features permissions ## Project features permissions
...@@ -165,10 +165,10 @@ Maintainers and Developers from pushing to a protected branch. Read through the ...@@ -165,10 +165,10 @@ Maintainers and Developers from pushing to a protected branch. Read through the
[Allowed to Merge and Allowed to Push settings](project/protected_branches.md#using-the-allowed-to-merge-and-allowed-to-push-settings) [Allowed to Merge and Allowed to Push settings](project/protected_branches.md#using-the-allowed-to-merge-and-allowed-to-push-settings)
to learn more. to learn more.
### Cycle Analytics permissions ### Value Stream Analytics permissions
Find the current permissions on the Cycle Analytics dashboard on Find the current permissions on the Value Stream Analytics dashboard, as described in
the [documentation on Cycle Analytics permissions](analytics/cycle_analytics.md#permissions). [related documentation](analytics/value_stream_analytics.md#permissions).
### Issue Board permissions ### Issue Board permissions
......
--- ---
redirect_to: '../analytics/cycle_analytics.md' redirect_to: '../analytics/value_stream_analytics.md'
--- ---
This document was moved to [another location](../analytics/cycle_analytics.md) This document was moved to [another location](../analytics/value_stream_analytics.md)
...@@ -87,7 +87,7 @@ When you create a project in GitLab, you'll have access to a large number of ...@@ -87,7 +87,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Wiki](wiki/index.md): document your GitLab project in an integrated Wiki. - [Wiki](wiki/index.md): document your GitLab project in an integrated Wiki.
- [Snippets](../snippets.md): store, share and collaborate on code snippets. - [Snippets](../snippets.md): store, share and collaborate on code snippets.
- [Cycle Analytics](cycle_analytics.md): review your development lifecycle. - [Value Stream Analytics](cycle_analytics.md): review your development lifecycle.
- [Insights](insights/index.md): configure the Insights that matter for your projects. **(ULTIMATE)** - [Insights](insights/index.md): configure the Insights that matter for your projects. **(ULTIMATE)**
- [Security Dashboard](security_dashboard.md): Security Dashboard. **(ULTIMATE)** - [Security Dashboard](security_dashboard.md): Security Dashboard. **(ULTIMATE)**
- [Syntax highlighting](highlighting.md): an alternative to customize - [Syntax highlighting](highlighting.md): an alternative to customize
......
...@@ -203,7 +203,7 @@ export default { ...@@ -203,7 +203,7 @@ export default {
<template> <template>
<div class="js-cycle-analytics"> <div class="js-cycle-analytics">
<div class="page-title-holder d-flex align-items-center"> <div class="page-title-holder d-flex align-items-center">
<h3 class="page-title">{{ __('Cycle Analytics') }}</h3> <h3 class="page-title">{{ __('Value Stream Analytics') }}</h3>
</div> </div>
<div class="mw-100"> <div class="mw-100">
<div <div
...@@ -239,7 +239,7 @@ export default { ...@@ -239,7 +239,7 @@ export default {
</div> </div>
<gl-empty-state <gl-empty-state
v-if="shouldRenderEmptyState" v-if="shouldRenderEmptyState"
:title="__('Cycle Analytics can help you determine your team’s velocity')" :title="__('Value Stream Analytics can help you determine your team’s velocity')"
:description=" :description="
__( __(
'Start by choosing a group to see how your team is spending time. You can then drill down to the project level.', 'Start by choosing a group to see how your team is spending time. You can then drill down to the project level.',
...@@ -251,11 +251,11 @@ export default { ...@@ -251,11 +251,11 @@ export default {
<gl-empty-state <gl-empty-state
v-if="hasNoAccessError" v-if="hasNoAccessError"
class="js-empty-state" class="js-empty-state"
:title="__('You don’t have access to Cycle Analytics for this group')" :title="__('You don’t have access to Value Stream Analytics for this group')"
:svg-path="noAccessSvgPath" :svg-path="noAccessSvgPath"
:description=" :description="
__( __(
'Only \'Reporter\' roles and above on tiers Premium / Silver and above can see Cycle Analytics.', 'Only \'Reporter\' roles and above on tiers Premium / Silver and above can see Value Stream Analytics.',
) )
" "
/> />
......
...@@ -14,7 +14,7 @@ export default { ...@@ -14,7 +14,7 @@ export default {
}, },
setSelectedGroup(selectedGroup) { setSelectedGroup(selectedGroup) {
this.selectedGroup = selectedGroup; this.selectedGroup = selectedGroup;
this.renderSelectedGroup(`/groups/${selectedGroup.path}/-/cycle_analytics`); this.renderSelectedGroup(`/groups/${selectedGroup.path}/-/value_stream_analytics`);
}, },
setSelectedProjects(selectedProjects) { setSelectedProjects(selectedProjects) {
this.selectedProjectIds = selectedProjects.map(value => value.id); this.selectedProjectIds = selectedProjects.map(value => value.id);
......
...@@ -119,7 +119,7 @@ export const receiveCycleAnalyticsDataError = ({ commit }, { response }) => { ...@@ -119,7 +119,7 @@ export const receiveCycleAnalyticsDataError = ({ commit }, { response }) => {
commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR, status); commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR, status);
if (status !== httpStatus.FORBIDDEN) if (status !== httpStatus.FORBIDDEN)
createFlash(__('There was an error while fetching cycle analytics data.')); createFlash(__('There was an error while fetching value stream analytics data.'));
}; };
export const fetchCycleAnalyticsData = ({ dispatch }) => { export const fetchCycleAnalyticsData = ({ dispatch }) => {
...@@ -147,7 +147,7 @@ export const requestSummaryData = ({ commit }) => commit(types.REQUEST_SUMMARY_D ...@@ -147,7 +147,7 @@ export const requestSummaryData = ({ commit }) => commit(types.REQUEST_SUMMARY_D
export const receiveSummaryDataError = ({ commit }, error) => { export const receiveSummaryDataError = ({ commit }, error) => {
commit(types.RECEIVE_SUMMARY_DATA_ERROR, error); commit(types.RECEIVE_SUMMARY_DATA_ERROR, error);
createFlash(__('There was an error while fetching cycle analytics summary data.')); createFlash(__('There was an error while fetching value stream analytics summary data.'));
}; };
export const receiveSummaryDataSuccess = ({ commit }, data) => export const receiveSummaryDataSuccess = ({ commit }, data) =>
...@@ -198,7 +198,7 @@ export const fetchGroupLabels = ({ dispatch, state }) => { ...@@ -198,7 +198,7 @@ export const fetchGroupLabels = ({ dispatch, state }) => {
export const receiveGroupStagesAndEventsError = ({ commit }, error) => { export const receiveGroupStagesAndEventsError = ({ commit }, error) => {
commit(types.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR, error); commit(types.RECEIVE_GROUP_STAGES_AND_EVENTS_ERROR, error);
createFlash(__('There was an error fetching cycle analytics stages.')); createFlash(__('There was an error fetching value stream analytics stages.'));
}; };
export const receiveGroupStagesAndEventsSuccess = ({ state, commit, dispatch }, data) => { export const receiveGroupStagesAndEventsSuccess = ({ state, commit, dispatch }, data) => {
...@@ -209,7 +209,7 @@ export const receiveGroupStagesAndEventsSuccess = ({ state, commit, dispatch }, ...@@ -209,7 +209,7 @@ export const receiveGroupStagesAndEventsSuccess = ({ state, commit, dispatch },
dispatch('setSelectedStage', firstStage); dispatch('setSelectedStage', firstStage);
dispatch('fetchStageData', firstStage.slug); dispatch('fetchStageData', firstStage.slug);
} else { } else {
createFlash(__('There was an error while fetching cycle analytics data.')); createFlash(__('There was an error while fetching value stream analytics data.'));
} }
}; };
...@@ -386,7 +386,7 @@ export const receiveDurationDataSuccess = ({ commit, state, dispatch }, data) => ...@@ -386,7 +386,7 @@ export const receiveDurationDataSuccess = ({ commit, state, dispatch }, data) =>
export const receiveDurationDataError = ({ commit }) => { export const receiveDurationDataError = ({ commit }) => {
commit(types.RECEIVE_DURATION_DATA_ERROR); commit(types.RECEIVE_DURATION_DATA_ERROR);
createFlash(__('There was an error while fetching cycle analytics duration data.')); createFlash(__('There was an error while fetching value stream analytics duration data.'));
}; };
export const fetchDurationData = ({ state, dispatch, getters }) => { export const fetchDurationData = ({ state, dispatch, getters }) => {
...@@ -430,7 +430,7 @@ export const receiveDurationMedianDataSuccess = ({ commit }, data) => ...@@ -430,7 +430,7 @@ export const receiveDurationMedianDataSuccess = ({ commit }, data) =>
export const receiveDurationMedianDataError = ({ commit }) => { export const receiveDurationMedianDataError = ({ commit }) => {
commit(types.RECEIVE_DURATION_MEDIAN_DATA_ERROR); commit(types.RECEIVE_DURATION_MEDIAN_DATA_ERROR);
createFlash(__('There was an error while fetching cycle analytics duration median data.')); createFlash(__('There was an error while fetching value stream analytics duration median data.'));
}; };
export const fetchDurationMedianData = ({ state, dispatch }) => { export const fetchDurationMedianData = ({ state, dispatch }) => {
......
...@@ -45,7 +45,7 @@ export const getLabelEventsIdentifiers = (events = []) => ...@@ -45,7 +45,7 @@ export const getLabelEventsIdentifiers = (events = []) =>
/** /**
* Checks if the specified stage is in memory or persisted to storage based on the id * Checks if the specified stage is in memory or persisted to storage based on the id
* *
* Default cycle analytics stages are initially stored in memory, when they are first * Default value stream analytics stages are initially stored in memory, when they are first
* created the id for the stage is the name of the stage in lowercase. This string id * created the id for the stage is the name of the stage in lowercase. This string id
* is used to fetch stage data (events, median calculation) * is used to fetch stage data (events, median calculation)
* *
......
...@@ -17,12 +17,13 @@ export default { ...@@ -17,12 +17,13 @@ export default {
projectPackagesPath: '/api/:version/projects/:id/packages', projectPackagesPath: '/api/:version/projects/:id/packages',
projectPackagePath: '/api/:version/projects/:id/packages/:package_id', projectPackagePath: '/api/:version/projects/:id/packages/:package_id',
cycleAnalyticsTasksByTypePath: '/-/analytics/type_of_work/tasks_by_type', cycleAnalyticsTasksByTypePath: '/-/analytics/type_of_work/tasks_by_type',
cycleAnalyticsSummaryDataPath: '/-/analytics/cycle_analytics/summary', cycleAnalyticsSummaryDataPath: '/-/analytics/value_stream_analytics/summary',
cycleAnalyticsGroupStagesAndEventsPath: '/-/analytics/cycle_analytics/stages', cycleAnalyticsGroupStagesAndEventsPath: '/-/analytics/value_stream_analytics/stages',
cycleAnalyticsStageEventsPath: '/-/analytics/cycle_analytics/stages/:stage_id/records', cycleAnalyticsStageEventsPath: '/-/analytics/value_stream_analytics/stages/:stage_id/records',
cycleAnalyticsStageMedianPath: '/-/analytics/cycle_analytics/stages/:stage_id/median', cycleAnalyticsStageMedianPath: '/-/analytics/value_stream_analytics/stages/:stage_id/median',
cycleAnalyticsStagePath: '/-/analytics/cycle_analytics/stages/:stage_id', cycleAnalyticsStagePath: '/-/analytics/value_stream_analytics/stages/:stage_id',
cycleAnalyticsDurationChartPath: '/-/analytics/cycle_analytics/stages/:stage_id/duration_chart', cycleAnalyticsDurationChartPath:
'/-/analytics/value_stream_analytics/stages/:stage_id/duration_chart',
codeReviewAnalyticsPath: '/api/:version/analytics/code_review', codeReviewAnalyticsPath: '/api/:version/analytics/code_review',
countriesPath: '/-/countries', countriesPath: '/-/countries',
countryStatesPath: '/-/country_states', countryStatesPath: '/-/country_states',
......
- page_title _("Cycle Analytics") - page_title _("Value Stream Analytics")
- initial_data = @request_params.valid? ? @request_params.to_data_attributes : {} - initial_data = @request_params.valid? ? @request_params.to_data_attributes : {}
#js-cycle-analytics-app{ data: initial_data.merge({ empty_state_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"), #js-cycle-analytics-app{ data: initial_data.merge({ empty_state_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"),
......
...@@ -25,12 +25,12 @@ ...@@ -25,12 +25,12 @@
.nav-icon-container .nav-icon-container
= sprite_icon('repeat') = sprite_icon('repeat')
%span.nav-item-name %span.nav-item-name
= _('Cycle Analytics') = _('Value Stream Analytics')
%ul.sidebar-sub-level-items.is-fly-out-only %ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :cycle_analytics, html_options: { class: "fly-out-top-item qa-sidebar-cycle-analytics-fly-out" } ) do = nav_link(controller: :cycle_analytics, html_options: { class: "fly-out-top-item qa-sidebar-cycle-analytics-fly-out" } ) do
= link_to analytics_cycle_analytics_path do = link_to analytics_cycle_analytics_path do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
= _('Cycle Analytics') = _('Value Stream Analytics')
= render_ce 'layouts/nav/sidebar/instance_statistics_links' = render_ce 'layouts/nav/sidebar/instance_statistics_links'
......
...@@ -6,8 +6,8 @@ namespace :analytics do ...@@ -6,8 +6,8 @@ namespace :analytics do
resource :productivity_analytics, only: :show, constraints: -> (req) { Feature.disabled?(:group_level_productivity_analytics) && Gitlab::Analytics.productivity_analytics_enabled? } resource :productivity_analytics, only: :show, constraints: -> (req) { Feature.disabled?(:group_level_productivity_analytics) && Gitlab::Analytics.productivity_analytics_enabled? }
constraints(-> (req) { Gitlab::Analytics.cycle_analytics_enabled? }) do constraints(-> (req) { Gitlab::Analytics.cycle_analytics_enabled? }) do
resource :cycle_analytics, only: :show resource :cycle_analytics, only: :show, path: 'value_stream_analytics'
namespace :cycle_analytics do scope module: :cycle_analytics, as: 'cycle_analytics', path: 'value_stream_analytics' do
resources :stages, only: [:index, :create, :update, :destroy] do resources :stages, only: [:index, :create, :update, :destroy] do
member do member do
get :duration_chart get :duration_chart
...@@ -15,8 +15,9 @@ namespace :analytics do ...@@ -15,8 +15,9 @@ namespace :analytics do
get :records get :records
end end
end end
resource :summary, controller: :summary, only: [:show] resource :summary, controller: :summary, only: :show
end end
get '/cycle_analytics', to: redirect('-/analytics/value_stream_analytics')
end end
constraints(::Constraints::FeatureConstrainer.new(Gitlab::Analytics::TASKS_BY_TYPE_CHART_FEATURE_FLAG)) do constraints(::Constraints::FeatureConstrainer.new(Gitlab::Analytics::TASKS_BY_TYPE_CHART_FEATURE_FLAG)) do
......
...@@ -19,8 +19,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -19,8 +19,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
get '/analytics', to: redirect('groups/%{group_id}/-/contribution_analytics') get '/analytics', to: redirect('groups/%{group_id}/-/contribution_analytics')
resource :contribution_analytics, only: [:show] resource :contribution_analytics, only: [:show]
resource :cycle_analytics, only: [:show] resource :cycle_analytics, only: [:show], path: 'value_stream_analytics'
namespace :cycle_analytics do scope module: :cycle_analytics, as: 'cycle_analytics', path: 'value_stream_analytics' do
scope :events, controller: 'events' do scope :events, controller: 'events' do
get :issue get :issue
get :plan get :plan
......
...@@ -160,7 +160,7 @@ class Gitlab::Seeder::CustomizableCycleAnalytics ...@@ -160,7 +160,7 @@ class Gitlab::Seeder::CustomizableCycleAnalytics
def merge_requests def merge_requests
@merge_requests ||= Array.new(MERGE_REQUEST_COUNT).map do |i| @merge_requests ||= Array.new(MERGE_REQUEST_COUNT).map do |i|
opts = { opts = {
title: 'Customized Cycle Analytics merge_request', title: 'Customized Value Stream Analytics merge_request',
description: "some description", description: "some description",
source_branch: "#{FFaker::Lorem.word}-#{i}-#{SecureRandom.hex(5)}", source_branch: "#{FFaker::Lorem.word}-#{i}-#{SecureRandom.hex(5)}",
target_branch: 'master' target_branch: 'master'
......
...@@ -16,7 +16,7 @@ describe Analytics::AnalyticsController do ...@@ -16,7 +16,7 @@ describe Analytics::AnalyticsController do
describe 'GET index' do describe 'GET index' do
describe 'redirects to the first enabled analytics page' do describe 'redirects to the first enabled analytics page' do
it 'redirects to cycle analytics' do it 'redirects to value stream analytics' do
stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true) stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true)
get :index get :index
......
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe 'Group Cycle Analytics', :js do describe 'Group Value Stream Analytics', :js do
let!(:user) { create(:user) } let!(:user) { create(:user) }
let!(:group) { create(:group, name: "CA-test-group") } let!(:group) { create(:group, name: "CA-test-group") }
let!(:project) { create(:project, :repository, namespace: group, group: group, name: "Cool fun project") } let!(:project) { create(:project, :repository, namespace: group, group: group, name: "Cool fun project") }
...@@ -32,7 +32,7 @@ describe 'Group Cycle Analytics', :js do ...@@ -32,7 +32,7 @@ describe 'Group Cycle Analytics', :js do
it 'displays an empty state before a group is selected' do it 'displays an empty state before a group is selected' do
element = page.find('.row.empty-state') element = page.find('.row.empty-state')
expect(element).to have_content("Cycle Analytics can help you determine your team’s velocity") expect(element).to have_content("Value Stream Analytics can help you determine your team’s velocity")
expect(element.find('.svg-content img')['src']).to have_content('illustrations/analytics/cycle-analytics-empty-chart') expect(element.find('.svg-content img')['src']).to have_content('illustrations/analytics/cycle-analytics-empty-chart')
end end
...@@ -78,7 +78,7 @@ describe 'Group Cycle Analytics', :js do ...@@ -78,7 +78,7 @@ describe 'Group Cycle Analytics', :js do
it 'displays empty text' do it 'displays empty text' do
[ [
'Cycle Analytics can help you determine your team’s velocity', 'Value Stream Analytics can help you determine your team’s velocity',
'Start by choosing a group to see how your team is spending time. You can then drill down to the project level.' 'Start by choosing a group to see how your team is spending time. You can then drill down to the project level.'
].each do |content| ].each do |content|
expect(page).to have_content(content) expect(page).to have_content(content)
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
}, },
{ {
"highlight": false, "highlight": false,
"title": "Cycle Analytics" "title": "Value Stream Analytics"
}, },
{ {
"highlight": false, "highlight": false,
......
...@@ -424,12 +424,12 @@ describe('Cycle Analytics component', () => { ...@@ -424,12 +424,12 @@ describe('Cycle Analytics component', () => {
const defaultRequests = { const defaultRequests = {
fetchSummaryData: { fetchSummaryData: {
status: defaultStatus, status: defaultStatus,
endpoint: `/-/analytics/cycle_analytics/summary`, endpoint: `/-/analytics/value_stream_analytics/summary`,
response: [...mockData.summaryData], response: [...mockData.summaryData],
}, },
fetchGroupStagesAndEvents: { fetchGroupStagesAndEvents: {
status: defaultStatus, status: defaultStatus,
endpoint: `/-/analytics/cycle_analytics/stages`, endpoint: `/-/analytics/value_stream_analytics/stages`,
response: { ...mockData.customizableStagesAndEvents }, response: { ...mockData.customizableStagesAndEvents },
}, },
fetchGroupLabels: { fetchGroupLabels: {
...@@ -448,19 +448,19 @@ describe('Cycle Analytics component', () => { ...@@ -448,19 +448,19 @@ describe('Cycle Analytics component', () => {
if (mockFetchDurationData) { if (mockFetchDurationData) {
mock mock
.onGet(/analytics\/cycle_analytics\/stages\/\d+\/duration_chart/) .onGet(/analytics\/value_stream_analytics\/stages\/\d+\/duration_chart/)
.reply(defaultStatus, [...mockData.rawDurationData]); .reply(defaultStatus, [...mockData.rawDurationData]);
} }
if (mockFetchStageMedian) { if (mockFetchStageMedian) {
mock mock
.onGet(/analytics\/cycle_analytics\/stages\/\d+\/median/) .onGet(/analytics\/value_stream_analytics\/stages\/\d+\/median/)
.reply(defaultStatus, { value: null }); .reply(defaultStatus, { value: null });
} }
if (mockFetchStageData) { if (mockFetchStageData) {
mock mock
.onGet(/analytics\/cycle_analytics\/stages\/\d+\/records/) .onGet(/analytics\/value_stream_analytics\/stages\/\d+\/records/)
.reply(defaultStatus, mockData.issueEvents); .reply(defaultStatus, mockData.issueEvents);
} }
...@@ -497,14 +497,14 @@ describe('Cycle Analytics component', () => { ...@@ -497,14 +497,14 @@ describe('Cycle Analytics component', () => {
overrides: { overrides: {
fetchSummaryData: { fetchSummaryData: {
status: httpStatusCodes.NOT_FOUND, status: httpStatusCodes.NOT_FOUND,
endpoint: '/-/analytics/cycle_analytics/summary', endpoint: '/-/analytics/value_stream_analytics/summary',
response: { response: { status: httpStatusCodes.NOT_FOUND } }, response: { response: { status: httpStatusCodes.NOT_FOUND } },
}, },
}, },
}); });
return selectGroupAndFindError( return selectGroupAndFindError(
'There was an error while fetching cycle analytics summary data.', 'There was an error while fetching value stream analytics summary data.',
); );
}); });
...@@ -531,14 +531,14 @@ describe('Cycle Analytics component', () => { ...@@ -531,14 +531,14 @@ describe('Cycle Analytics component', () => {
mockRequestCycleAnalyticsData({ mockRequestCycleAnalyticsData({
overrides: { overrides: {
fetchGroupStagesAndEvents: { fetchGroupStagesAndEvents: {
endPoint: '/-/analytics/cycle_analytics/stages', endPoint: '/-/analytics/value_stream_analytics/stages',
status: httpStatusCodes.NOT_FOUND, status: httpStatusCodes.NOT_FOUND,
response: { response: { status: httpStatusCodes.NOT_FOUND } }, response: { response: { status: httpStatusCodes.NOT_FOUND } },
}, },
}, },
}); });
return selectGroupAndFindError('There was an error fetching cycle analytics stages.'); return selectGroupAndFindError('There was an error fetching value stream analytics stages.');
}); });
it('will display an error if the fetchStageData request fails', () => { it('will display an error if the fetchStageData request fails', () => {
...@@ -572,7 +572,7 @@ describe('Cycle Analytics component', () => { ...@@ -572,7 +572,7 @@ describe('Cycle Analytics component', () => {
return waitForPromises().catch(() => { return waitForPromises().catch(() => {
expect(findFlashError().innerText.trim()).toEqual( expect(findFlashError().innerText.trim()).toEqual(
'There was an error while fetching cycle analytics duration data.', 'There was an error while fetching value stream analytics duration data.',
); );
}); });
}); });
...@@ -588,7 +588,7 @@ describe('Cycle Analytics component', () => { ...@@ -588,7 +588,7 @@ describe('Cycle Analytics component', () => {
return waitForPromises().catch(() => { return waitForPromises().catch(() => {
expect(findFlashError().innerText.trim()).toEqual( expect(findFlashError().innerText.trim()).toEqual(
'There was an error while fetching cycle analytics duration data.', 'There was an error while fetching value stream analytics duration data.',
); );
}); });
}); });
......
...@@ -4,7 +4,7 @@ import { groupLabels } from '../mock_data'; ...@@ -4,7 +4,7 @@ import { groupLabels } from '../mock_data';
const selectedLabel = groupLabels[groupLabels.length - 1]; const selectedLabel = groupLabels[groupLabels.length - 1];
describe('Cycle Analytics LabelsSelector', () => { describe('Value Stream Analytics LabelsSelector', () => {
function createComponent({ props = {}, shallow = true } = {}) { function createComponent({ props = {}, shallow = true } = {}) {
const func = shallow ? shallowMount : mount; const func = shallow ? shallowMount : mount;
return func(LabelsSelector, { return func(LabelsSelector, {
......
...@@ -11,10 +11,10 @@ import { toYmd } from 'ee/analytics/shared/utils'; ...@@ -11,10 +11,10 @@ import { toYmd } from 'ee/analytics/shared/utils';
import { transformRawTasksByTypeData } from 'ee/analytics/cycle_analytics/utils'; import { transformRawTasksByTypeData } from 'ee/analytics/cycle_analytics/utils';
const endpoints = { const endpoints = {
customizableCycleAnalyticsStagesAndEvents: 'analytics/cycle_analytics/stages.json', // customizable stages and events endpoint customizableCycleAnalyticsStagesAndEvents: 'analytics/value_stream_analytics/stages.json', // customizable stages and events endpoint
stageEvents: stage => `analytics/cycle_analytics/stages/${stage}/records.json`, stageEvents: stage => `analytics/value_stream_analytics/stages/${stage}/records.json`,
stageMedian: stage => `analytics/cycle_analytics/stages/${stage}/median.json`, stageMedian: stage => `analytics/value_stream_analytics/stages/${stage}/median.json`,
summaryData: 'analytics/cycle_analytics/summary.json', summaryData: 'analytics/value_stream_analytics/summary.json',
}; };
export const groupLabels = mockLabels.map(({ title, ...rest }) => ({ ...rest, name: title })); export const groupLabels = mockLabels.map(({ title, ...rest }) => ({ ...rest, name: title }));
......
...@@ -22,20 +22,20 @@ import { ...@@ -22,20 +22,20 @@ import {
const stageData = { events: [] }; const stageData = { events: [] };
const error = new Error('Request failed with status code 404'); const error = new Error('Request failed with status code 404');
const flashErrorMessage = 'There was an error while fetching cycle analytics data.'; const flashErrorMessage = 'There was an error while fetching value stream analytics data.';
const selectedGroup = { fullPath: group.path }; const selectedGroup = { fullPath: group.path };
const [selectedStage] = stages; const [selectedStage] = stages;
const selectedStageSlug = selectedStage.slug; const selectedStageSlug = selectedStage.slug;
const endpoints = { const endpoints = {
groupLabels: `/groups/${group.path}/-/labels`, groupLabels: `/groups/${group.path}/-/labels`,
summaryData: '/analytics/cycle_analytics/summary', summaryData: '/analytics/value_stream_analytics/summary',
durationData: /analytics\/cycle_analytics\/stages\/\d+\/duration_chart/, durationData: /analytics\/value_stream_analytics\/stages\/\d+\/duration_chart/,
stageData: /analytics\/cycle_analytics\/stages\/\d+\/records/, stageData: /analytics\/value_stream_analytics\/stages\/\d+\/records/,
stageMedian: /analytics\/cycle_analytics\/stages\/\d+\/median/, stageMedian: /analytics\/value_stream_analytics\/stages\/\d+\/median/,
baseStagesEndpoint: '/analytics/cycle_analytics/stages', baseStagesEndpoint: '/analytics/value_stream_analytics/stages',
}; };
const stageEndpoint = ({ stageId }) => `/-/analytics/cycle_analytics/stages/${stageId}`; const stageEndpoint = ({ stageId }) => `/-/analytics/value_stream_analytics/stages/${stageId}`;
describe('Cycle analytics actions', () => { describe('Cycle analytics actions', () => {
let state; let state;
...@@ -276,7 +276,7 @@ describe('Cycle analytics actions', () => { ...@@ -276,7 +276,7 @@ describe('Cycle analytics actions', () => {
state = { ...state, selectedGroup, startDate, endDate }; state = { ...state, selectedGroup, startDate, endDate };
}); });
it(`dispatches actions for required cycle analytics data`, done => { it(`dispatches actions for required value stream analytics analytics data`, done => {
testAction( testAction(
actions.fetchCycleAnalyticsData, actions.fetchCycleAnalyticsData,
state, state,
...@@ -368,7 +368,9 @@ describe('Cycle analytics actions', () => { ...@@ -368,7 +368,9 @@ describe('Cycle analytics actions', () => {
commit: () => {}, commit: () => {},
}) })
.then(() => { .then(() => {
shouldFlashAMessage('There was an error while fetching cycle analytics summary data.'); shouldFlashAMessage(
'There was an error while fetching value stream analytics summary data.',
);
done(); done();
}) })
.catch(done.fail); .catch(done.fail);
...@@ -394,7 +396,7 @@ describe('Cycle analytics actions', () => { ...@@ -394,7 +396,7 @@ describe('Cycle analytics actions', () => {
commit: () => {}, commit: () => {},
}) })
.then(() => { .then(() => {
shouldFlashAMessage('There was an error fetching cycle analytics stages.'); shouldFlashAMessage('There was an error fetching value stream analytics stages.');
done(); done();
}) })
.catch(done.fail); .catch(done.fail);
...@@ -425,7 +427,9 @@ describe('Cycle analytics actions', () => { ...@@ -425,7 +427,9 @@ describe('Cycle analytics actions', () => {
{}, {},
); );
shouldFlashAMessage('There was an error while fetching cycle analytics duration data.'); shouldFlashAMessage(
'There was an error while fetching value stream analytics duration data.',
);
}); });
describe('with an existing error', () => { describe('with an existing error', () => {
...@@ -925,7 +929,9 @@ describe('Cycle analytics actions', () => { ...@@ -925,7 +929,9 @@ describe('Cycle analytics actions', () => {
commit: () => {}, commit: () => {},
}); });
shouldFlashAMessage('There was an error while fetching cycle analytics duration data.'); shouldFlashAMessage(
'There was an error while fetching value stream analytics duration data.',
);
}); });
}); });
...@@ -1149,7 +1155,7 @@ describe('Cycle analytics actions', () => { ...@@ -1149,7 +1155,7 @@ describe('Cycle analytics actions', () => {
}); });
shouldFlashAMessage( shouldFlashAMessage(
'There was an error while fetching cycle analytics duration median data.', 'There was an error while fetching value stream analytics duration median data.',
); );
}); });
}); });
......
...@@ -380,13 +380,13 @@ describe('Api', () => { ...@@ -380,13 +380,13 @@ describe('Api', () => {
}); });
describe('cycleAnalyticsSummaryData', () => { describe('cycleAnalyticsSummaryData', () => {
it('fetches cycle analytics summary data', done => { it('fetches value stream analytics summary data', done => {
const response = [{ value: 0, title: 'New Issues' }, { value: 0, title: 'Deploys' }]; const response = [{ value: 0, title: 'New Issues' }, { value: 0, title: 'Deploys' }];
const params = { const params = {
...defaultParams, ...defaultParams,
}; };
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/summary`; const expectedUrl = `${dummyUrlRoot}/-/analytics/value_stream_analytics/summary`;
mock.onGet(expectedUrl).reply(200, response); mock.onGet(expectedUrl).reply(200, response);
Api.cycleAnalyticsSummaryData(params) Api.cycleAnalyticsSummaryData(params)
...@@ -410,7 +410,7 @@ describe('Api', () => { ...@@ -410,7 +410,7 @@ describe('Api', () => {
'cycle_analytics[created_after]': createdAfter, 'cycle_analytics[created_after]': createdAfter,
'cycle_analytics[created_before]': createdBefore, 'cycle_analytics[created_before]': createdBefore,
}; };
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/stages`; const expectedUrl = `${dummyUrlRoot}/-/analytics/value_stream_analytics/stages`;
mock.onGet(expectedUrl).reply(200, response); mock.onGet(expectedUrl).reply(200, response);
Api.cycleAnalyticsGroupStagesAndEvents(groupId, params) Api.cycleAnalyticsGroupStagesAndEvents(groupId, params)
...@@ -432,7 +432,7 @@ describe('Api', () => { ...@@ -432,7 +432,7 @@ describe('Api', () => {
const params = { const params = {
...defaultParams, ...defaultParams,
}; };
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/stages/${stageId}/records`; const expectedUrl = `${dummyUrlRoot}/-/analytics/value_stream_analytics/stages/${stageId}/records`;
mock.onGet(expectedUrl).reply(200, response); mock.onGet(expectedUrl).reply(200, response);
Api.cycleAnalyticsStageEvents(groupId, stageId, params) Api.cycleAnalyticsStageEvents(groupId, stageId, params)
...@@ -454,7 +454,7 @@ describe('Api', () => { ...@@ -454,7 +454,7 @@ describe('Api', () => {
const params = { const params = {
...defaultParams, ...defaultParams,
}; };
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/stages/${stageId}/median`; const expectedUrl = `${dummyUrlRoot}/-/analytics/value_stream_analytics/stages/${stageId}/median`;
mock.onGet(expectedUrl).reply(200, response); mock.onGet(expectedUrl).reply(200, response);
Api.cycleAnalyticsStageMedian(groupId, stageId, params) Api.cycleAnalyticsStageMedian(groupId, stageId, params)
...@@ -480,7 +480,7 @@ describe('Api', () => { ...@@ -480,7 +480,7 @@ describe('Api', () => {
end_event_identifier: 'issue_closed', end_event_identifier: 'issue_closed',
end_event_label_id: null, end_event_label_id: null,
}; };
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/stages`; const expectedUrl = `${dummyUrlRoot}/-/analytics/value_stream_analytics/stages`;
mock.onPost(expectedUrl).reply(200, response); mock.onPost(expectedUrl).reply(200, response);
Api.cycleAnalyticsCreateStage(groupId, customStage) Api.cycleAnalyticsCreateStage(groupId, customStage)
...@@ -502,7 +502,7 @@ describe('Api', () => { ...@@ -502,7 +502,7 @@ describe('Api', () => {
name: 'nice-stage', name: 'nice-stage',
hidden: true, hidden: true,
}; };
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/stages/${stageId}`; const expectedUrl = `${dummyUrlRoot}/-/analytics/value_stream_analytics/stages/${stageId}`;
mock.onPut(expectedUrl).reply(200, response); mock.onPut(expectedUrl).reply(200, response);
Api.cycleAnalyticsUpdateStage(stageId, groupId, stageData) Api.cycleAnalyticsUpdateStage(stageId, groupId, stageData)
...@@ -520,7 +520,7 @@ describe('Api', () => { ...@@ -520,7 +520,7 @@ describe('Api', () => {
describe('cycleAnalyticsRemoveStage', () => { describe('cycleAnalyticsRemoveStage', () => {
it('deletes the specified data', done => { it('deletes the specified data', done => {
const response = { id: stageId, hidden: true, custom: true }; const response = { id: stageId, hidden: true, custom: true };
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/stages/${stageId}`; const expectedUrl = `${dummyUrlRoot}/-/analytics/value_stream_analytics/stages/${stageId}`;
mock.onDelete(expectedUrl).reply(200, response); mock.onDelete(expectedUrl).reply(200, response);
Api.cycleAnalyticsRemoveStage(stageId, groupId) Api.cycleAnalyticsRemoveStage(stageId, groupId)
...@@ -541,7 +541,7 @@ describe('Api', () => { ...@@ -541,7 +541,7 @@ describe('Api', () => {
const params = { const params = {
...defaultParams, ...defaultParams,
}; };
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/stages/thursday/duration_chart`; const expectedUrl = `${dummyUrlRoot}/-/analytics/value_stream_analytics/stages/thursday/duration_chart`;
mock.onGet(expectedUrl).reply(200, response); mock.onGet(expectedUrl).reply(200, response);
Api.cycleAnalyticsDurationChart(stageId, params) Api.cycleAnalyticsDurationChart(stageId, params)
......
...@@ -140,7 +140,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do ...@@ -140,7 +140,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
end end
default_stages.each do |endpoint| default_stages.each do |endpoint|
it "cycle_analytics/events/#{endpoint}.json" do it "value_stream_analytics/events/#{endpoint}.json" do
get endpoint, params: { group_id: group, format: :json } get endpoint, params: { group_id: group, format: :json }
expect(response).to be_successful expect(response).to be_successful
...@@ -160,7 +160,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do ...@@ -160,7 +160,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
sign_in(user) sign_in(user)
end end
it 'cycle_analytics/mock_data.json' do it 'value_stream_analytics/mock_data.json' do
get(:show, params: { get(:show, params: {
group_id: group.name, group_id: group.name,
cycle_analytics: { start_date: 30 } cycle_analytics: { start_date: 30 }
...@@ -194,21 +194,21 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do ...@@ -194,21 +194,21 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
sign_in(user) sign_in(user)
end end
it 'analytics/cycle_analytics/stages.json' do it 'analytics/value_stream_analytics/stages.json' do
get(:index, params: { group_id: group.name }, format: :json) get(:index, params: { group_id: group.name }, format: :json)
expect(response).to be_successful expect(response).to be_successful
end end
Gitlab::Analytics::CycleAnalytics::DefaultStages.all.each do |stage| Gitlab::Analytics::CycleAnalytics::DefaultStages.all.each do |stage|
it "analytics/cycle_analytics/stages/#{stage[:name]}/records.json" do it "analytics/value_stream_analytics/stages/#{stage[:name]}/records.json" do
stage_id = group.cycle_analytics_stages.find_by(name: stage[:name]).id stage_id = group.cycle_analytics_stages.find_by(name: stage[:name]).id
get(:records, params: params.merge({ id: stage_id }), format: :json) get(:records, params: params.merge({ id: stage_id }), format: :json)
expect(response).to be_successful expect(response).to be_successful
end end
it "analytics/cycle_analytics/stages/#{stage[:name]}/median.json" do it "analytics/value_stream_analytics/stages/#{stage[:name]}/median.json" do
stage_id = group.cycle_analytics_stages.find_by(name: stage[:name]).id stage_id = group.cycle_analytics_stages.find_by(name: stage[:name]).id
get(:median, params: params.merge({ id: stage_id }), format: :json) get(:median, params: params.merge({ id: stage_id }), format: :json)
...@@ -216,13 +216,13 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do ...@@ -216,13 +216,13 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
end end
end end
it "analytics/cycle_analytics/stages/label-based-stage/records.json" do it "analytics/value_stream_analytics/stages/label-based-stage/records.json" do
get(:records, params: params.merge({ id: label_based_stage.id }), format: :json) get(:records, params: params.merge({ id: label_based_stage.id }), format: :json)
expect(response).to be_successful expect(response).to be_successful
end end
it "analytics/cycle_analytics/stages/label-based-stage/median.json" do it "analytics/value_stream_analytics/stages/label-based-stage/median.json" do
get(:median, params: params.merge({ id: label_based_stage.id }), format: :json) get(:median, params: params.merge({ id: label_based_stage.id }), format: :json)
expect(response).to be_successful expect(response).to be_successful
...@@ -243,7 +243,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do ...@@ -243,7 +243,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
sign_in(user) sign_in(user)
end end
it 'analytics/cycle_analytics/summary.json' do it 'analytics/value_stream_analytics/summary.json' do
get(:show, params: params, format: :json) get(:show, params: params, format: :json)
expect(response).to be_successful expect(response).to be_successful
......
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe 'cycle analytics events' do describe 'value stream analytics events' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group)} let(:group) { create(:group)}
let(:project) { create(:project, :repository, namespace: group, public_builds: false) } let(:project) { create(:project, :repository, namespace: group, public_builds: false) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
describe 'GET /:namespace/-/cycle_analytics/events/:stage' do describe 'GET /:namespace/-/value_stream_analytics/events/:stage' do
before do before do
stub_licensed_features(cycle_analytics_for_groups: true) stub_licensed_features(cycle_analytics_for_groups: true)
group.add_developer(user) group.add_developer(user)
......
...@@ -31,7 +31,7 @@ describe 'Analytics' do ...@@ -31,7 +31,7 @@ describe 'Analytics' do
it 'succeeds' do it 'succeeds' do
expect(Gitlab::Analytics).to receive(:cycle_analytics_enabled?).and_call_original expect(Gitlab::Analytics).to receive(:cycle_analytics_enabled?).and_call_original
expect(get('/-/analytics/cycle_analytics')).to route_to('analytics/cycle_analytics#show') expect(get('/-/analytics/value_stream_analytics')).to route_to('analytics/cycle_analytics#show')
end end
end end
......
...@@ -33,12 +33,12 @@ describe 'layouts/nav/sidebar/_analytics' do ...@@ -33,12 +33,12 @@ describe 'layouts/nav/sidebar/_analytics' do
expect(rendered).to match(/<use xlink:href=".+?icons-.+?#comment">/) expect(rendered).to match(/<use xlink:href=".+?icons-.+?#comment">/)
end end
it 'has `Cycle Analytics` link' do it 'has `Value Stream Analytics` link' do
stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true) stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true)
render render
expect(rendered).to have_content('Cycle Analytics') expect(rendered).to have_content('Value Stream Analytics')
expect(rendered).to include(analytics_cycle_analytics_path) expect(rendered).to include(analytics_cycle_analytics_path)
expect(rendered).to match(/<use xlink:href=".+?icons-.+?#repeat">/) expect(rendered).to match(/<use xlink:href=".+?icons-.+?#repeat">/)
end end
...@@ -84,7 +84,7 @@ describe 'layouts/nav/sidebar/_analytics' do ...@@ -84,7 +84,7 @@ describe 'layouts/nav/sidebar/_analytics' do
disable_all_analytics_feature_flags disable_all_analytics_feature_flags
expect(rendered).not_to have_content('Productivity Analytics') expect(rendered).not_to have_content('Productivity Analytics')
expect(rendered).not_to have_content('Cycle Analytics') expect(rendered).not_to have_content('Value Stream Analytics')
end end
context 'and user has access to instance statistics features' do context 'and user has access to instance statistics features' do
......
# frozen_string_literal: true # frozen_string_literal: true
# This module represents the default Cycle Analytics stages that are currently provided by CE # This module represents the default Value Stream Analytics stages that are currently provided by CE
# Each method returns a hash that can be used to build a new stage object. # Each method returns a hash that can be used to build a new stage object.
# #
# Example: # Example:
......
...@@ -50,7 +50,7 @@ module Gitlab ...@@ -50,7 +50,7 @@ module Gitlab
# Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time). # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time).
# Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time). # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time).
# We compute the (end_time - start_time) interval, and give it an alias based on the current # We compute the (end_time - start_time) interval, and give it an alias based on the current
# cycle analytics stage. # value stream analytics stage.
median_datetime(cte_table, interval_query(project_ids), name) median_datetime(cte_table, interval_query(project_ids), name)
end end
......
...@@ -5802,15 +5802,6 @@ msgstr "" ...@@ -5802,15 +5802,6 @@ msgstr ""
msgid "Customize your pipeline configuration." msgid "Customize your pipeline configuration."
msgstr "" msgstr ""
msgid "Cycle Analytics"
msgstr ""
msgid "Cycle Analytics can help you determine your team’s velocity"
msgstr ""
msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr ""
msgid "CycleAnalyticsEvent|Issue closed" msgid "CycleAnalyticsEvent|Issue closed"
msgstr "" msgstr ""
...@@ -6714,15 +6705,15 @@ msgstr "" ...@@ -6714,15 +6705,15 @@ msgstr ""
msgid "Dismiss" msgid "Dismiss"
msgstr "" msgstr ""
msgid "Dismiss Cycle Analytics introduction box"
msgstr ""
msgid "Dismiss DevOps Score introduction" msgid "Dismiss DevOps Score introduction"
msgstr "" msgstr ""
msgid "Dismiss Merge Request promotion" msgid "Dismiss Merge Request promotion"
msgstr "" msgstr ""
msgid "Dismiss Value Stream Analytics introduction box"
msgstr ""
msgid "Dismiss trial promotion" msgid "Dismiss trial promotion"
msgstr "" msgstr ""
...@@ -10459,7 +10450,7 @@ msgstr "" ...@@ -10459,7 +10450,7 @@ msgstr ""
msgid "Interval Pattern" msgid "Interval Pattern"
msgstr "" msgstr ""
msgid "Introducing Cycle Analytics" msgid "Introducing Value Stream Analytics"
msgstr "" msgstr ""
msgid "Introducing Your DevOps Score" msgid "Introducing Your DevOps Score"
...@@ -13042,7 +13033,7 @@ msgstr "" ...@@ -13042,7 +13033,7 @@ msgstr ""
msgid "One or more of your personal access tokens will expire in %{days_to_expire} days or less." msgid "One or more of your personal access tokens will expire in %{days_to_expire} days or less."
msgstr "" msgstr ""
msgid "Only 'Reporter' roles and above on tiers Premium / Silver and above can see Cycle Analytics." msgid "Only 'Reporter' roles and above on tiers Premium / Silver and above can see Value Stream Analytics."
msgstr "" msgstr ""
msgid "Only Project Members" msgid "Only Project Members"
...@@ -19182,9 +19173,6 @@ msgstr "" ...@@ -19182,9 +19173,6 @@ msgstr ""
msgid "There was an error fetching configuration for charts" msgid "There was an error fetching configuration for charts"
msgstr "" msgstr ""
msgid "There was an error fetching cycle analytics stages."
msgstr ""
msgid "There was an error fetching data for the selected stage" msgid "There was an error fetching data for the selected stage"
msgstr "" msgstr ""
...@@ -19200,6 +19188,9 @@ msgstr "" ...@@ -19200,6 +19188,9 @@ msgstr ""
msgid "There was an error fetching the Designs" msgid "There was an error fetching the Designs"
msgstr "" msgstr ""
msgid "There was an error fetching value stream analytics stages."
msgstr ""
msgid "There was an error gathering the chart data" msgid "There was an error gathering the chart data"
msgstr "" msgstr ""
...@@ -19242,16 +19233,16 @@ msgstr "" ...@@ -19242,16 +19233,16 @@ msgstr ""
msgid "There was an error when unsubscribing from this label." msgid "There was an error when unsubscribing from this label."
msgstr "" msgstr ""
msgid "There was an error while fetching cycle analytics data." msgid "There was an error while fetching value stream analytics data."
msgstr "" msgstr ""
msgid "There was an error while fetching cycle analytics duration data." msgid "There was an error while fetching value stream analytics duration data."
msgstr "" msgstr ""
msgid "There was an error while fetching cycle analytics duration median data." msgid "There was an error while fetching value stream analytics duration median data."
msgstr "" msgstr ""
msgid "There was an error while fetching cycle analytics summary data." msgid "There was an error while fetching value stream analytics summary data."
msgstr "" msgstr ""
msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again." msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
...@@ -21100,6 +21091,15 @@ msgstr "" ...@@ -21100,6 +21091,15 @@ msgstr ""
msgid "Value" msgid "Value"
msgstr "" msgstr ""
msgid "Value Stream Analytics"
msgstr ""
msgid "Value Stream Analytics can help you determine your team’s velocity"
msgstr ""
msgid "Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr ""
msgid "Variables" msgid "Variables"
msgstr "" msgstr ""
...@@ -21893,10 +21893,10 @@ msgstr "" ...@@ -21893,10 +21893,10 @@ msgstr ""
msgid "You don't have sufficient permission to perform this action." msgid "You don't have sufficient permission to perform this action."
msgstr "" msgstr ""
msgid "You don’t have access to Cycle Analytics for this group" msgid "You don’t have access to Productivity Analytics in this group"
msgstr "" msgstr ""
msgid "You don’t have access to Productivity Analytics in this group" msgid "You don’t have access to Value Stream Analytics for this group"
msgstr "" msgstr ""
msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}." msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}."
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
describe 'Cycle Analytics', :js do describe 'Value Stream Analytics', :js do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:guest) { create(:user) } let(:guest) { create(:user) }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
...@@ -23,7 +23,7 @@ describe 'Cycle Analytics', :js do ...@@ -23,7 +23,7 @@ describe 'Cycle Analytics', :js do
end end
it 'shows introductory message' do it 'shows introductory message' do
expect(page).to have_content('Introducing Cycle Analytics') expect(page).to have_content('Introducing Value Stream Analytics')
end end
it 'shows pipeline summary' do it 'shows pipeline summary' do
...@@ -38,7 +38,7 @@ describe 'Cycle Analytics', :js do ...@@ -38,7 +38,7 @@ describe 'Cycle Analytics', :js do
end end
end end
context "when there's cycle analytics data" do context "when there's value stream analytics data" do
before do before do
allow_next_instance_of(Gitlab::ReferenceExtractor) do |instance| allow_next_instance_of(Gitlab::ReferenceExtractor) do |instance|
allow(instance).to receive(:issues).and_return([issue]) allow(instance).to receive(:issues).and_return([issue])
......
...@@ -67,8 +67,8 @@ describe 'Project navbar' do ...@@ -67,8 +67,8 @@ describe 'Project navbar' do
nav_sub_items: [ nav_sub_items: [
_('CI / CD Analytics'), _('CI / CD Analytics'),
(_('Code Review') if Gitlab.ee?), (_('Code Review') if Gitlab.ee?),
_('Cycle Analytics'), _('Repository Analytics'),
_('Repository Analytics') _('Value Stream Analytics')
] ]
}, },
{ {
......
...@@ -267,11 +267,11 @@ msgstr "Eventos de notificaciones personalizadas" ...@@ -267,11 +267,11 @@ msgstr "Eventos de notificaciones personalizadas"
msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}." msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}."
msgstr "Los niveles de notificación personalizados son los mismos que los niveles participantes. Con los niveles de notificación personalizados, también recibirá notificaciones para eventos seleccionados. Para obtener más información, consulte %{notification_link}." msgstr "Los niveles de notificación personalizados son los mismos que los niveles participantes. Con los niveles de notificación personalizados, también recibirá notificaciones para eventos seleccionados. Para obtener más información, consulte %{notification_link}."
msgid "Cycle Analytics" msgid "Value Stream Analytics"
msgstr "Cycle Analytics" msgstr "Value Stream Analytics"
msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." msgid "Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr "Cycle Analytics ofrece una visión general de cuánto tiempo tarda en pasar de idea a producción en su proyecto." msgstr "Value Stream Analytics ofrece una visión general de cuánto tiempo tarda en pasar de idea a producción en su proyecto."
msgid "CycleAnalyticsStage|Code" msgid "CycleAnalyticsStage|Code"
msgstr "Código" msgstr "Código"
...@@ -412,8 +412,8 @@ msgstr "Importar repositorio" ...@@ -412,8 +412,8 @@ msgstr "Importar repositorio"
msgid "Interval Pattern" msgid "Interval Pattern"
msgstr "Patrón de intervalo" msgstr "Patrón de intervalo"
msgid "Introducing Cycle Analytics" msgid "Introducing Value Stream Analytics"
msgstr "Introducción a Cycle Analytics" msgstr "Introducción a Value Stream Analytics"
msgid "Jobs for last month" msgid "Jobs for last month"
msgstr "Trabajos del mes pasado" msgstr "Trabajos del mes pasado"
......
...@@ -16,8 +16,10 @@ describe('Cycle analytics banner', () => { ...@@ -16,8 +16,10 @@ describe('Cycle analytics banner', () => {
vm.$destroy(); vm.$destroy();
}); });
it('should render cycle analytics information', () => { it('should render value stream analytics information', () => {
expect(vm.$el.querySelector('h4').textContent.trim()).toEqual('Introducing Cycle Analytics'); expect(vm.$el.querySelector('h4').textContent.trim()).toEqual(
'Introducing Value Stream Analytics',
);
expect( expect(
vm.$el vm.$el
...@@ -25,7 +27,7 @@ describe('Cycle analytics banner', () => { ...@@ -25,7 +27,7 @@ describe('Cycle analytics banner', () => {
.textContent.trim() .textContent.trim()
.replace(/[\r\n]+/g, ' '), .replace(/[\r\n]+/g, ' '),
).toContain( ).toContain(
'Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.', 'Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project.',
); );
expect(vm.$el.querySelector('a').textContent.trim()).toEqual('Read more'); expect(vm.$el.querySelector('a').textContent.trim()).toEqual('Read more');
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
require 'spec_helper' require 'spec_helper'
describe 'cycle analytics events' do describe 'value stream analytics events' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository, public_builds: false) } let(:project) { create(:project, :repository, public_builds: false) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
describe 'GET /:namespace/:project/cycle_analytics/events/issues' do describe 'GET /:namespace/:project/value_stream_analytics/events/issues' do
before do before do
project.add_developer(user) project.add_developer(user)
......
...@@ -160,4 +160,31 @@ describe 'layouts/nav/sidebar/_project' do ...@@ -160,4 +160,31 @@ describe 'layouts/nav/sidebar/_project' do
end end
end end
end end
describe 'value stream analytics entry' do
let(:read_cycle_analytics) { true }
before do
allow(view).to receive(:can?).with(nil, :read_cycle_analytics, project).and_return(read_cycle_analytics)
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: false, thing: project })
end
describe 'when value stream analytics is enabled' do
it 'shows the value stream analytics entry' do
render
expect(rendered).to have_link('Value Stream Analytics', href: project_cycle_analytics_path(project))
end
end
describe 'when value stream analytics is disabled' do
let(:read_cycle_analytics) { false }
it 'does not show the value stream analytics entry' do
render
expect(rendered).not_to have_link('Value Stream Analytics', href: project_cycle_analytics_path(project))
end
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment