Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
745e0f87
Commit
745e0f87
authored
Nov 18, 2016
by
James Lopez
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactored 1 iteration of cycle analytics and stages in general
parent
f185d9e9
Changes
70
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
70 changed files
with
830 additions
and
451 deletions
+830
-451
app/controllers/concerns/cycle_analytics_params.rb
app/controllers/concerns/cycle_analytics_params.rb
+4
-0
app/controllers/projects/cycle_analytics/events_controller.rb
...controllers/projects/cycle_analytics/events_controller.rb
+14
-18
app/controllers/projects/cycle_analytics_controller.rb
app/controllers/projects/cycle_analytics_controller.rb
+8
-46
app/models/cycle_analytics.rb
app/models/cycle_analytics.rb
+18
-42
app/models/cycle_analytics/summary.rb
app/models/cycle_analytics/summary.rb
+0
-43
app/serializers/analytics_stage_entity.rb
app/serializers/analytics_stage_entity.rb
+10
-0
app/serializers/analytics_stage_serializer.rb
app/serializers/analytics_stage_serializer.rb
+3
-0
app/serializers/analytics_summary_entity.rb
app/serializers/analytics_summary_entity.rb
+7
-0
app/serializers/analytics_summary_serializer.rb
app/serializers/analytics_summary_serializer.rb
+3
-0
lib/gitlab/cycle_analytics/base_event_fetcher.rb
lib/gitlab/cycle_analytics/base_event_fetcher.rb
+17
-9
lib/gitlab/cycle_analytics/base_query.rb
lib/gitlab/cycle_analytics/base_query.rb
+31
-0
lib/gitlab/cycle_analytics/base_stage.rb
lib/gitlab/cycle_analytics/base_stage.rb
+54
-0
lib/gitlab/cycle_analytics/code_event_fetcher.rb
lib/gitlab/cycle_analytics/code_event_fetcher.rb
+4
-4
lib/gitlab/cycle_analytics/code_stage.rb
lib/gitlab/cycle_analytics/code_stage.rb
+21
-0
lib/gitlab/cycle_analytics/event_fetcher.rb
lib/gitlab/cycle_analytics/event_fetcher.rb
+9
-0
lib/gitlab/cycle_analytics/events.rb
lib/gitlab/cycle_analytics/events.rb
+0
-38
lib/gitlab/cycle_analytics/events_query.rb
lib/gitlab/cycle_analytics/events_query.rb
+0
-37
lib/gitlab/cycle_analytics/issue_event.rb
lib/gitlab/cycle_analytics/issue_event.rb
+0
-27
lib/gitlab/cycle_analytics/issue_event_fetcher.rb
lib/gitlab/cycle_analytics/issue_event_fetcher.rb
+1
-4
lib/gitlab/cycle_analytics/issue_stage.rb
lib/gitlab/cycle_analytics/issue_stage.rb
+22
-0
lib/gitlab/cycle_analytics/plan_event_fetcher.rb
lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+4
-6
lib/gitlab/cycle_analytics/plan_stage.rb
lib/gitlab/cycle_analytics/plan_stage.rb
+22
-0
lib/gitlab/cycle_analytics/production_event_fetcher.rb
lib/gitlab/cycle_analytics/production_event_fetcher.rb
+6
-0
lib/gitlab/cycle_analytics/production_helper.rb
lib/gitlab/cycle_analytics/production_helper.rb
+9
-0
lib/gitlab/cycle_analytics/production_stage.rb
lib/gitlab/cycle_analytics/production_stage.rb
+28
-0
lib/gitlab/cycle_analytics/review_event_fetcher.rb
lib/gitlab/cycle_analytics/review_event_fetcher.rb
+1
-7
lib/gitlab/cycle_analytics/review_stage.rb
lib/gitlab/cycle_analytics/review_stage.rb
+21
-0
lib/gitlab/cycle_analytics/stage.rb
lib/gitlab/cycle_analytics/stage.rb
+9
-0
lib/gitlab/cycle_analytics/stage_summary.rb
lib/gitlab/cycle_analytics/stage_summary.rb
+23
-0
lib/gitlab/cycle_analytics/staging_event_fetcher.rb
lib/gitlab/cycle_analytics/staging_event_fetcher.rb
+4
-5
lib/gitlab/cycle_analytics/staging_stage.rb
lib/gitlab/cycle_analytics/staging_stage.rb
+22
-0
lib/gitlab/cycle_analytics/summary/base.rb
lib/gitlab/cycle_analytics/summary/base.rb
+20
-0
lib/gitlab/cycle_analytics/summary/commit.rb
lib/gitlab/cycle_analytics/summary/commit.rb
+39
-0
lib/gitlab/cycle_analytics/summary/deploy.rb
lib/gitlab/cycle_analytics/summary/deploy.rb
+11
-0
lib/gitlab/cycle_analytics/summary/issue.rb
lib/gitlab/cycle_analytics/summary/issue.rb
+21
-0
lib/gitlab/cycle_analytics/test_event.rb
lib/gitlab/cycle_analytics/test_event.rb
+0
-13
lib/gitlab/cycle_analytics/test_event_fetcher.rb
lib/gitlab/cycle_analytics/test_event_fetcher.rb
+6
-0
lib/gitlab/cycle_analytics/test_stage.rb
lib/gitlab/cycle_analytics/test_stage.rb
+29
-0
lib/gitlab/database/median.rb
lib/gitlab/database/median.rb
+5
-0
spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
+12
-0
spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
+8
-0
spec/lib/gitlab/cycle_analytics/events_spec.rb
spec/lib/gitlab/cycle_analytics/events_spec.rb
+75
-62
spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
+8
-0
spec/lib/gitlab/cycle_analytics/issue_event_spec.rb
spec/lib/gitlab/cycle_analytics/issue_event_spec.rb
+0
-10
spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
+8
-0
spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
+4
-6
spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
+8
-0
spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
...b/gitlab/cycle_analytics/production_event_fetcher_spec.rb
+8
-0
spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
+8
-0
spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
+8
-0
spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
+8
-0
spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
+2
-9
spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
+30
-0
spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
+10
-10
spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
.../lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
+12
-0
spec/lib/gitlab/cycle_analytics/staging_event_spec.rb
spec/lib/gitlab/cycle_analytics/staging_event_spec.rb
+0
-10
spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
+8
-0
spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
+12
-0
spec/lib/gitlab/cycle_analytics/test_event_spec.rb
spec/lib/gitlab/cycle_analytics/test_event_spec.rb
+0
-10
spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
+8
-0
spec/models/cycle_analytics/code_spec.rb
spec/models/cycle_analytics/code_spec.rb
+11
-11
spec/models/cycle_analytics/issue_spec.rb
spec/models/cycle_analytics/issue_spec.rb
+2
-2
spec/models/cycle_analytics/plan_spec.rb
spec/models/cycle_analytics/plan_spec.rb
+2
-2
spec/models/cycle_analytics/production_spec.rb
spec/models/cycle_analytics/production_spec.rb
+3
-3
spec/models/cycle_analytics/review_spec.rb
spec/models/cycle_analytics/review_spec.rb
+2
-2
spec/models/cycle_analytics/staging_spec.rb
spec/models/cycle_analytics/staging_spec.rb
+3
-3
spec/models/cycle_analytics/test_spec.rb
spec/models/cycle_analytics/test_spec.rb
+5
-5
spec/serializers/analytics_stage_serializer_spec.rb
spec/serializers/analytics_stage_serializer_spec.rb
+24
-0
spec/serializers/analytics_summary_serializer_spec.rb
spec/serializers/analytics_summary_serializer_spec.rb
+29
-0
spec/support/cycle_analytics_helpers/test_generation.rb
spec/support/cycle_analytics_helpers/test_generation.rb
+6
-7
No files found.
app/controllers/concerns/cycle_analytics_params.rb
View file @
745e0f87
module
CycleAnalyticsParams
extend
ActiveSupport
::
Concern
def
options
(
params
)
@options
||=
{
from:
start_date
(
params
),
current_user:
current_user
}
end
def
start_date
(
params
)
params
[
:start_date
]
==
'30'
?
30
.
days
.
ago
:
90
.
days
.
ago
end
...
...
app/controllers/projects/cycle_analytics/events_controller.rb
View file @
745e0f87
...
...
@@ -9,56 +9,52 @@ module Projects
before_action
:authorize_read_merge_request!
,
only:
[
:code
,
:review
]
def
issue
render_events
(
events
.
issue_
events
)
render_events
(
cycle_analytics
[
:issue
].
events
)
end
def
plan
render_events
(
events
.
plan_
events
)
render_events
(
cycle_analytics
[
:plan
].
events
)
end
def
code
render_events
(
events
.
code_
events
)
render_events
(
cycle_analytics
[
:code
].
events
)
end
def
test
options
[
:branch
]
=
events_params
[
:branch_name
]
options
(
events_params
)
[
:branch
]
=
events_params
[
:branch_name
]
render_events
(
events
.
test_
events
)
render_events
(
cycle_analytics
[
:test
].
events
)
end
def
review
render_events
(
events
.
review_
events
)
render_events
(
cycle_analytics
[
:review
].
events
)
end
def
staging
render_events
(
events
.
staging_
events
)
render_events
(
cycle_analytics
[
:staging
].
events
)
end
def
production
render_events
(
events
.
production_
events
)
render_events
(
cycle_analytics
[
:production
].
events
)
end
private
def
render_events
(
events
_list
)
def
render_events
(
events
)
respond_to
do
|
format
|
format
.
html
format
.
json
{
render
json:
{
events:
events
_list
}
}
format
.
json
{
render
json:
{
events:
events
}
}
end
end
def
events
@events
||=
Gitlab
::
CycleAnalytics
::
Events
.
new
(
project:
project
,
options:
options
)
end
def
options
@options
||=
{
from:
start_date
(
events_params
),
current_user:
current_user
}
def
cycle_analytics
@cycle_analytics
||=
::
CycleAnalytics
.
new
(
project
,
options
(
events_params
))
end
def
events_params
return
{}
unless
params
[
:events
].
present?
params
[
:events
].
slice
(
:start_date
,
:branch_name
)
params
[
:events
].
permit
(
:start_date
,
:branch_name
)
end
end
end
...
...
app/controllers/projects/cycle_analytics_controller.rb
View file @
745e0f87
...
...
@@ -6,11 +6,9 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
before_action
:authorize_read_cycle_analytics!
def
show
@cycle_analytics
=
::
CycleAnalytics
.
new
(
@project
,
current_user
,
from:
start_date
(
cycle_analytics_params
))
@cycle_analytics
=
::
CycleAnalytics
.
new
(
@project
,
options
(
cycle_analytics_params
))
stats_values
,
cycle_analytics_json
=
generate_cycle_analytics_data
@cycle_analytics_no_data
=
stats_values
.
blank?
@cycle_analytics_no_data
=
@cycle_analytics
.
no_stats?
respond_to
do
|
format
|
format
.
html
...
...
@@ -23,50 +21,14 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
def
cycle_analytics_params
return
{}
unless
params
[
:cycle_analytics
].
present?
{
start_date:
params
[
:cycle_analytics
][
:start_date
]
}
params
[
:cycle_analytics
].
permit
(
:start_date
)
end
def
generate_cycle_analytics_data
stats_values
=
[]
cycle_analytics_view_data
=
[[
:issue
,
"Issue"
,
"Related Issues"
,
"Time before an issue gets scheduled"
],
[
:plan
,
"Plan"
,
"Related Commits"
,
"Time before an issue starts implementation"
],
[
:code
,
"Code"
,
"Related Merge Requests"
,
"Time spent coding"
],
[
:test
,
"Test"
,
"Relative Builds Trigger by Commits"
,
"The time taken to build and test the application"
],
[
:review
,
"Review"
,
"Relative Merged Requests"
,
"The time taken to review the code"
],
[
:staging
,
"Staging"
,
"Relative Deployed Builds"
,
"The time taken in staging"
],
[
:production
,
"Production"
,
"Related Issues"
,
"The total time taken from idea to production"
]]
stats
=
cycle_analytics_view_data
.
reduce
([])
do
|
stats
,
(
stage_method
,
stage_text
,
stage_legend
,
stage_description
)
|
value
=
@cycle_analytics
.
send
(
stage_method
).
presence
stats_values
<<
value
.
abs
if
value
stats
<<
{
title:
stage_text
,
description:
stage_description
,
legend:
stage_legend
,
value:
value
&&
!
value
.
zero?
?
distance_of_time_in_words
(
value
)
:
nil
}
stats
end
issues
=
@cycle_analytics
.
summary
.
new_issues
commits
=
@cycle_analytics
.
summary
.
commits
deploys
=
@cycle_analytics
.
summary
.
deploys
summary
=
[
{
title:
"New Issue"
.
pluralize
(
issues
),
value:
issues
},
{
title:
"Commit"
.
pluralize
(
commits
),
value:
commits
},
{
title:
"Deploy"
.
pluralize
(
deploys
),
value:
deploys
}
]
cycle_analytics_hash
=
{
summary:
summary
,
stats:
stats
,
permissions:
@cycle_analytics
.
permissions
(
user:
current_user
)
def
cycle_analytics_json
{
summary:
@cycle_analytics
.
summary
,
stats:
@cycle_analytics
.
stats
,
permissions:
@cycle_analytics
.
permissions
(
user:
current_user
)
}
[
stats_values
,
cycle_analytics_hash
]
end
end
app/models/cycle_analytics.rb
View file @
745e0f87
class
CycleAnalytics
STAGES
=
%i[issue plan code test review staging production]
.
freeze
def
initialize
(
project
,
current_user
,
from
:
)
def
initialize
(
project
,
options
)
@project
=
project
@current_user
=
current_user
@from
=
from
@fetcher
=
Gitlab
::
CycleAnalytics
::
MetricsFetcher
.
new
(
project:
project
,
from:
from
,
branch:
nil
)
@options
=
options
end
def
summary
@summary
||=
Summary
.
new
(
@project
,
@current_user
,
from:
@from
)
@summary
||=
::
Gitlab
::
CycleAnalytics
::
StageSummary
.
new
(
@project
,
from:
@options
[
:from
],
current_user:
@options
[
:current_user
]).
data
end
def
permissions
(
user
:)
Gitlab
::
CycleAnalytics
::
Permissions
.
get
(
user:
user
,
project:
@project
)
def
stats
@stats
||=
stats_per_stage
end
def
issue
@fetcher
.
calculate_metric
(
:issue
,
Issue
.
arel_table
[
:created_at
],
[
Issue
::
Metrics
.
arel_table
[
:first_associated_with_milestone_at
],
Issue
::
Metrics
.
arel_table
[
:first_added_to_board_at
]])
def
no_stats?
stats
.
all?
{
|
hash
|
hash
[
:value
].
nil?
}
end
def
plan
@fetcher
.
calculate_metric
(
:plan
,
[
Issue
::
Metrics
.
arel_table
[
:first_associated_with_milestone_at
],
Issue
::
Metrics
.
arel_table
[
:first_added_to_board_at
]],
Issue
::
Metrics
.
arel_table
[
:first_mentioned_in_commit_at
])
end
def
code
@fetcher
.
calculate_metric
(
:code
,
Issue
::
Metrics
.
arel_table
[
:first_mentioned_in_commit_at
],
MergeRequest
.
arel_table
[
:created_at
])
end
def
test
@fetcher
.
calculate_metric
(
:test
,
MergeRequest
::
Metrics
.
arel_table
[
:latest_build_started_at
],
MergeRequest
::
Metrics
.
arel_table
[
:latest_build_finished_at
])
def
permissions
(
user
:)
Gitlab
::
CycleAnalytics
::
Permissions
.
get
(
user:
user
,
project:
@project
)
end
def
review
@fetcher
.
calculate_metric
(
:review
,
MergeRequest
.
arel_table
[
:created_at
],
MergeRequest
::
Metrics
.
arel_table
[
:merged_at
])
def
[]
(
stage_name
)
Gitlab
::
CycleAnalytics
::
Stage
[
stage_name
].
new
(
project:
@project
,
options:
@options
)
end
def
staging
@fetcher
.
calculate_metric
(
:staging
,
MergeRequest
::
Metrics
.
arel_table
[
:merged_at
],
MergeRequest
::
Metrics
.
arel_table
[
:first_deployed_to_production_at
])
end
private
def
production
@fetcher
.
calculate_metric
(
:production
,
Issue
.
arel_table
[
:created_at
],
MergeRequest
::
Metrics
.
arel_table
[
:first_deployed_to_production_at
])
def
stats_per_stage
STAGES
.
map
do
|
stage_name
|
self
[
stage_name
].
as_json
end
end
end
app/models/cycle_analytics/summary.rb
View file @
745e0f87
class
CycleAnalytics
class
Summary
def
initialize
(
project
,
current_user
,
from
:)
@project
=
project
@current_user
=
current_user
@from
=
from
end
def
new_issues
IssuesFinder
.
new
(
@current_user
,
project_id:
@project
.
id
).
execute
.
created_after
(
@from
).
count
end
def
commits
ref
=
@project
.
default_branch
.
presence
count_commits_for
(
ref
)
end
def
deploys
@project
.
deployments
.
where
(
"created_at > ?"
,
@from
).
count
end
private
# Don't use the `Gitlab::Git::Repository#log` method, because it enforces
# a limit. Since we need a commit count, we _can't_ enforce a limit, so
# the easiest way forward is to replicate the relevant portions of the
# `log` function here.
def
count_commits_for
(
ref
)
return
unless
ref
repository
=
@project
.
repository
.
raw_repository
sha
=
@project
.
repository
.
commit
(
ref
).
sha
cmd
=
%W(
#{
Gitlab
.
config
.
git
.
bin_path
}
--git-dir=
#{
repository
.
path
}
log)
cmd
<<
'--format=%H'
cmd
<<
"--after=
#{
@from
.
iso8601
}
"
cmd
<<
sha
raw_output
=
IO
.
popen
(
cmd
)
{
|
io
|
io
.
read
}
raw_output
.
lines
.
count
end
end
end
app/serializers/analytics_stage_entity.rb
0 → 100644
View file @
745e0f87
class
AnalyticsStageEntity
<
Grape
::
Entity
include
EntityDateHelper
expose
:title
expose
:description
expose
:median
,
as: :value
do
|
stage
|
stage
.
median
&&
!
stage
.
median
.
zero?
?
distance_of_time_in_words
(
stage
.
median
)
:
nil
end
end
app/serializers/analytics_stage_serializer.rb
0 → 100644
View file @
745e0f87
class
AnalyticsStageSerializer
<
BaseSerializer
entity
AnalyticsStageEntity
end
app/serializers/analytics_summary_entity.rb
0 → 100644
View file @
745e0f87
class
AnalyticsSummaryEntity
<
Grape
::
Entity
expose
:value
,
safe:
true
expose
:title
do
|
object
|
object
.
title
.
pluralize
(
object
.
value
)
end
end
app/serializers/analytics_summary_serializer.rb
0 → 100644
View file @
745e0f87
class
AnalyticsSummarySerializer
<
BaseSerializer
entity
AnalyticsSummaryEntity
end
lib/gitlab/cycle_analytics/base_event.rb
→
lib/gitlab/cycle_analytics/base_event
_fetcher
.rb
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
BaseEvent
include
MetricsTables
class
BaseEvent
Fetcher
include
BaseQuery
attr_reader
:
stage
,
:start_time_attrs
,
:end_time_attrs
,
:projections
,
:query
attr_reader
:
projections
,
:query
,
:stage
,
:order
def
initialize
(
project
:,
options
:)
@query
=
EventsQuery
.
new
(
project:
project
,
options:
options
)
def
initialize
(
project
:,
stage
:,
options
:)
@project
=
project
@stage
=
stage
@options
=
options
end
...
...
@@ -19,10 +19,8 @@ module Gitlab
end
.
compact
end
def
custom_query
(
_base_query
);
end
def
order
@order
||
@start_time_attrs
@order
||
default_order
end
private
...
...
@@ -34,7 +32,17 @@ module Gitlab
end
def
event_result
@event_result
||=
@query
.
execute
(
self
).
to_a
@event_result
||=
ActiveRecord
::
Base
.
connection
.
exec_query
(
events_query
.
to_sql
).
to_a
end
def
events_query
diff_fn
=
subtract_datetimes_diff
(
base_query
,
@options
[
:start_time_attrs
],
@options
[
:end_time_attrs
])
base_query
.
project
(
extract_diff_epoch
(
diff_fn
).
as
(
'total_time'
),
*
projections
).
order
(
order
.
desc
)
end
def
default_order
[
@options
[
:start_time_attrs
]].
flatten
.
first
end
def
serialize
(
_event
)
...
...
lib/gitlab/cycle_analytics/
metrics_fetcher
.rb
→
lib/gitlab/cycle_analytics/
base_query
.rb
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
MetricsFetcher
module
BaseQuery
include
MetricsTables
include
Gitlab
::
Database
::
Median
include
Gitlab
::
Database
::
DateTime
include
MetricsTables
DEPLOYMENT_METRIC_STAGES
=
%i[production staging]
def
initialize
(
project
:,
from
:,
branch
:)
@project
=
project
@project
=
project
@from
=
from
@branch
=
branch
end
def
calculate_metric
(
name
,
start_time_attrs
,
end_time_attrs
)
cte_table
=
Arel
::
Table
.
new
(
"cte_table_for_
#{
name
}
"
)
# 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).
# We compute the (end_time - start_time) interval, and give it an alias based on the current
# cycle analytics stage.
interval_query
=
Arel
::
Nodes
::
As
.
new
(
cte_table
,
subtract_datetimes
(
base_query_for
(
name
),
start_time_attrs
,
end_time_attrs
,
name
.
to_s
))
private
median_datetime
(
cte_table
,
interval_query
,
name
)
def
base_query
@base_query
||=
stage_query
end
# Join table with a row for every <issue,merge_request> pair (where the merge request
# closes the given issue) with issue and merge request metrics included. The metrics
# are loaded with an inner join, so issues / merge requests without metrics are
# automatically excluded.
def
base_query_for
(
name
)
# Load issues
def
stage_query
query
=
mr_closing_issues_table
.
join
(
issue_table
).
on
(
issue_table
[
:id
].
eq
(
mr_closing_issues_table
[
:issue_id
])).
join
(
issue_metrics_table
).
on
(
issue_table
[
:id
].
eq
(
issue_metrics_table
[
:issue_id
])).
where
(
issue_table
[
:project_id
].
eq
(
@project
.
id
)).
where
(
issue_table
[
:deleted_at
].
eq
(
nil
)).
where
(
issue_table
[
:created_at
].
gteq
(
@from
))
query
=
query
.
where
(
build_table
[
:ref
].
eq
(
@branch
))
if
name
==
:test
&&
@branch
where
(
issue_table
[
:created_at
].
gteq
(
@options
[
:from
]))
# Load merge_requests
query
=
query
.
join
(
mr_table
,
Arel
::
Nodes
::
OuterJoin
).
...
...
@@ -48,11 +24,6 @@ module Gitlab
join
(
mr_metrics_table
).
on
(
mr_table
[
:id
].
eq
(
mr_metrics_table
[
:merge_request_id
]))
if
DEPLOYMENT_METRIC_STAGES
.
include?
(
name
)
# Limit to merge requests that have been deployed to production after `@from`
query
.
where
(
mr_metrics_table
[
:first_deployed_to_production_at
].
gteq
(
@from
))
end
query
end
end
...
...
lib/gitlab/cycle_analytics/base_stage.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
BaseStage
include
BaseQuery
def
initialize
(
project
:,
options
:)
@project
=
project
@options
=
options
end
def
events
event_fetcher
.
fetch
end
def
as_json
AnalyticsStageSerializer
.
new
.
represent
(
self
).
as_json
end
def
title
name
.
to_s
.
capitalize
end
def
median
cte_table
=
Arel
::
Table
.
new
(
"cte_table_for_
#{
name
}
"
)
# 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).
# We compute the (end_time - start_time) interval, and give it an alias based on the current
# cycle analytics stage.
interval_query
=
Arel
::
Nodes
::
As
.
new
(
cte_table
,
subtract_datetimes
(
base_query
.
dup
,
start_time_attrs
,
end_time_attrs
,
name
.
to_s
))
median_datetime
(
cte_table
,
interval_query
,
name
)
end
def
name
raise
NotImplementedError
.
new
(
"Expected
#{
self
.
name
}
to implement name"
)
end
private
def
event_fetcher
@event_fetcher
||=
Gitlab
::
CycleAnalytics
::
EventFetcher
[
name
].
new
(
project:
@project
,
stage:
name
,
options:
event_options
)
end
def
event_options
@options
.
merge
(
start_time_attrs:
start_time_attrs
,
end_time_attrs:
end_time_attrs
)
end
end
end
end
lib/gitlab/cycle_analytics/
review_event
.rb
→
lib/gitlab/cycle_analytics/
code_event_fetcher
.rb
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
ReviewEvent
<
BaseEvent
class
CodeEventFetcher
<
BaseEventFetcher
include
MergeRequestAllowed
def
initialize
(
*
args
)
@stage
=
:review
@start_time_attrs
=
mr_table
[
:created_at
]
@end_time_attrs
=
mr_metrics_table
[
:merged_at
]
@projections
=
[
mr_table
[
:title
],
mr_table
[
:iid
],
mr_table
[
:id
],
mr_table
[
:created_at
],
mr_table
[
:state
],
mr_table
[
:author_id
]]
@order
=
mr_table
[
:created_at
]
super
(
*
args
)
end
private
def
serialize
(
event
)
AnalyticsMergeRequestSerializer
.
new
(
project:
@project
).
represent
(
event
).
as_json
end
...
...
lib/gitlab/cycle_analytics/code_stage.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
CodeStage
<
BaseStage
def
start_time_attrs
@start_time_attrs
||=
issue_metrics_table
[
:first_mentioned_in_commit_at
]
end
def
end_time_attrs
@end_time_attrs
||=
mr_table
[
:created_at
]
end
def
name
:code
end
def
description
"Time until first merge request"
end
end
end
end
lib/gitlab/cycle_analytics/event_fetcher.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
module
EventFetcher
def
self
.
[]
(
stage_name
)
CycleAnalytics
.
const_get
(
"
#{
stage_name
.
to_s
.
camelize
}
EventFetcher"
)
end
end
end
end
lib/gitlab/cycle_analytics/events.rb
deleted
100644 → 0
View file @
f185d9e9
module
Gitlab
module
CycleAnalytics
class
Events
def
initialize
(
project
:,
options
:)
@project
=
project
@options
=
options
end
def
issue_events
IssueEvent
.
new
(
project:
@project
,
options:
@options
).
fetch
end
def
plan_events
PlanEvent
.
new
(
project:
@project
,
options:
@options
).
fetch
end
def
code_events
CodeEvent
.
new
(
project:
@project
,
options:
@options
).
fetch
end
def
test_events
TestEvent
.
new
(
project:
@project
,
options:
@options
).
fetch
end
def
review_events
ReviewEvent
.
new
(
project:
@project
,
options:
@options
).
fetch
end
def
staging_events
StagingEvent
.
new
(
project:
@project
,
options:
@options
).
fetch
end
def
production_events
ProductionEvent
.
new
(
project:
@project
,
options:
@options
).
fetch
end
end
end
end
lib/gitlab/cycle_analytics/events_query.rb
deleted
100644 → 0
View file @
f185d9e9
module
Gitlab
module
CycleAnalytics
class
EventsQuery
attr_reader
:project
def
initialize
(
project
:,
options:
{})
@project
=
project
@from
=
options
[
:from
]
@branch
=
options
[
:branch
]
@fetcher
=
Gitlab
::
CycleAnalytics
::
MetricsFetcher
.
new
(
project:
project
,
from:
@from
,
branch:
@branch
)
end
def
execute
(
stage_class
)
@stage_class
=
stage_class
ActiveRecord
::
Base
.
connection
.
exec_query
(
query
.
to_sql
)
end
private
def
query
base_query
=
@fetcher
.
base_query_for
(
@stage_class
.
stage
)
diff_fn
=
@fetcher
.
subtract_datetimes_diff
(
base_query
,
@stage_class
.
start_time_attrs
,
@stage_class
.
end_time_attrs
)
@stage_class
.
custom_query
(
base_query
)
base_query
.
project
(
extract_epoch
(
diff_fn
).
as
(
'total_time'
),
*
@stage_class
.
projections
).
order
(
@stage_class
.
order
.
desc
)
end
def
extract_epoch
(
arel_attribute
)
return
arel_attribute
unless
Gitlab
::
Database
.
postgresql?
Arel
.
sql
(
%Q{EXTRACT(EPOCH FROM (
#{
arel_attribute
.
to_sql
}
))}
)
end
end
end
end
lib/gitlab/cycle_analytics/issue_event.rb
deleted
100644 → 0
View file @
f185d9e9
module
Gitlab
module
CycleAnalytics
class
IssueEvent
<
BaseEvent
include
IssueAllowed
def
initialize
(
*
args
)
@stage
=
:issue
@start_time_attrs
=
issue_table
[
:created_at
]
@end_time_attrs
=
[
issue_metrics_table
[
:first_associated_with_milestone_at
],
issue_metrics_table
[
:first_added_to_board_at
]]
@projections
=
[
issue_table
[
:title
],
issue_table
[
:iid
],
issue_table
[
:id
],
issue_table
[
:created_at
],
issue_table
[
:author_id
]]
super
(
*
args
)
end
private
def
serialize
(
event
)
AnalyticsIssueSerializer
.
new
(
project:
@project
).
represent
(
event
).
as_json
end
end
end
end
lib/gitlab/cycle_analytics/
production_event
.rb
→
lib/gitlab/cycle_analytics/
issue_event_fetcher
.rb
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
ProductionEvent
<
BaseEvent
class
IssueEventFetcher
<
BaseEventFetcher
include
IssueAllowed
def
initialize
(
*
args
)
@stage
=
:production
@start_time_attrs
=
issue_table
[
:created_at
]
@end_time_attrs
=
mr_metrics_table
[
:first_deployed_to_production_at
]
@projections
=
[
issue_table
[
:title
],
issue_table
[
:iid
],
issue_table
[
:id
],
...
...
lib/gitlab/cycle_analytics/issue_stage.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
IssueStage
<
BaseStage
def
start_time_attrs
@start_time_attrs
||=
issue_table
[
:created_at
]
end
def
end_time_attrs
@end_time_attrs
||=
[
issue_metrics_table
[
:first_associated_with_milestone_at
],
issue_metrics_table
[
:first_added_to_board_at
]]
end
def
name
:issue
end
def
description
"Time before an issue gets scheduled"
end
end
end
end
lib/gitlab/cycle_analytics/plan_event.rb
→
lib/gitlab/cycle_analytics/plan_event
_fetcher
.rb
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
PlanEvent
<
BaseEvent
class
PlanEvent
Fetcher
<
BaseEventFetcher
def
initialize
(
*
args
)
@stage
=
:plan
@start_time_attrs
=
issue_metrics_table
[
:first_associated_with_milestone_at
]
@end_time_attrs
=
[
issue_metrics_table
[
:first_added_to_board_at
],
issue_metrics_table
[
:first_mentioned_in_commit_at
]]
@projections
=
[
mr_diff_table
[
:st_commits
].
as
(
'commits'
),
issue_metrics_table
[
:first_mentioned_in_commit_at
]]
super
(
*
args
)
end
def
custom_query
(
base_query
)
def
events_query
base_query
.
join
(
mr_diff_table
).
on
(
mr_diff_table
[
:merge_request_id
].
eq
(
mr_table
[
:id
]))
super
end
private
...
...
lib/gitlab/cycle_analytics/plan_stage.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
PlanStage
<
BaseStage
def
start_time_attrs
@start_time_attrs
||=
[
issue_metrics_table
[
:first_associated_with_milestone_at
],
issue_metrics_table
[
:first_added_to_board_at
]]
end
def
end_time_attrs
@end_time_attrs
||=
issue_metrics_table
[
:first_mentioned_in_commit_at
]
end
def
name
:plan
end
def
description
"Time before an issue starts implementation"
end
end
end
end
lib/gitlab/cycle_analytics/production_event_fetcher.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
ProductionEventFetcher
<
IssueEventFetcher
end
end
end
lib/gitlab/cycle_analytics/production_helper.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
module
ProductionHelper
def
stage_query
super
.
where
(
mr_metrics_table
[
:first_deployed_to_production_at
].
gteq
(
@options
[
:from
]))
end
end
end
end
lib/gitlab/cycle_analytics/production_stage.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
ProductionStage
<
BaseStage
include
ProductionHelper
def
start_time_attrs
@start_time_attrs
||=
issue_table
[
:created_at
]
end
def
end_time_attrs
@end_time_attrs
||=
mr_metrics_table
[
:first_deployed_to_production_at
]
end
def
name
:production
end
def
description
"From issue creation until deploy to production"
end
def
query
# Limit to merge requests that have been deployed to production after `@from`
query
.
where
(
mr_metrics_table
[
:first_deployed_to_production_at
].
gteq
(
@from
))
end
end
end
end
lib/gitlab/cycle_analytics/
code_event
.rb
→
lib/gitlab/cycle_analytics/
review_event_fetcher
.rb
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
CodeEvent
<
BaseEvent
class
ReviewEventFetcher
<
BaseEventFetcher
include
MergeRequestAllowed
def
initialize
(
*
args
)
@stage
=
:code
@start_time_attrs
=
issue_metrics_table
[
:first_mentioned_in_commit_at
]
@end_time_attrs
=
mr_table
[
:created_at
]
@projections
=
[
mr_table
[
:title
],
mr_table
[
:iid
],
mr_table
[
:id
],
mr_table
[
:created_at
],
mr_table
[
:state
],
mr_table
[
:author_id
]]
@order
=
mr_table
[
:created_at
]
super
(
*
args
)
end
private
def
serialize
(
event
)
AnalyticsMergeRequestSerializer
.
new
(
project:
@project
).
represent
(
event
).
as_json
end
...
...
lib/gitlab/cycle_analytics/review_stage.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
ReviewStage
<
BaseStage
def
start_time_attrs
@start_time_attrs
||=
mr_table
[
:created_at
]
end
def
end_time_attrs
@end_time_attrs
||=
mr_metrics_table
[
:merged_at
]
end
def
name
:review
end
def
description
"Time between merge request creation and merge/close"
end
end
end
end
lib/gitlab/cycle_analytics/stage.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
module
Stage
def
self
.
[]
(
stage_name
)
CycleAnalytics
.
const_get
(
"
#{
stage_name
.
to_s
.
camelize
}
Stage"
)
end
end
end
end
lib/gitlab/cycle_analytics/stage_summary.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
StageSummary
def
initialize
(
project
,
from
:,
current_user
:)
@project
=
project
@from
=
from
@current_user
=
current_user
end
def
data
[
serialize
(
Summary
::
Issue
.
new
(
project:
@project
,
from:
@from
,
current_user:
@current_user
)),
serialize
(
Summary
::
Commit
.
new
(
project:
@project
,
from:
@from
)),
serialize
(
Summary
::
Deploy
.
new
(
project:
@project
,
from:
@from
))]
end
private
def
serialize
(
summary_object
)
AnalyticsSummarySerializer
.
new
.
represent
(
summary_object
).
as_json
end
end
end
end
lib/gitlab/cycle_analytics/staging_event.rb
→
lib/gitlab/cycle_analytics/staging_event
_fetcher
.rb
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
StagingEvent
<
BaseEvent
class
StagingEvent
Fetcher
<
BaseEventFetcher
def
initialize
(
*
args
)
@stage
=
:staging
@start_time_attrs
=
mr_metrics_table
[
:merged_at
]
@end_time_attrs
=
mr_metrics_table
[
:first_deployed_to_production_at
]
@projections
=
[
build_table
[
:id
]]
@order
=
build_table
[
:created_at
]
...
...
@@ -17,8 +14,10 @@ module Gitlab
super
end
def
custom_query
(
base_query
)
def
events_query
base_query
.
join
(
build_table
).
on
(
mr_metrics_table
[
:pipeline_id
].
eq
(
build_table
[
:commit_id
]))
super
end
private
...
...
lib/gitlab/cycle_analytics/staging_stage.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
StagingStage
<
BaseStage
include
ProductionHelper
def
start_time_attrs
@start_time_attrs
||=
mr_metrics_table
[
:merged_at
]
end
def
end_time_attrs
@end_time_attrs
||=
mr_metrics_table
[
:first_deployed_to_production_at
]
end
def
name
:staging
end
def
description
"From merge request merge until deploy to production"
end
end
end
end
lib/gitlab/cycle_analytics/summary/base.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
module
Summary
class
Base
def
initialize
(
project
:,
from
:)
@project
=
project
@from
=
from
end
def
title
self
.
class
.
name
.
demodulize
end
def
value
raise
NotImplementedError
.
new
(
"Expected
#{
self
.
name
}
to implement value"
)
end
end
end
end
end
lib/gitlab/cycle_analytics/summary/commit.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
module
Summary
class
Commit
<
Base
def
value
@value
||=
count_commits
end
private
# Don't use the `Gitlab::Git::Repository#log` method, because it enforces
# a limit. Since we need a commit count, we _can't_ enforce a limit, so
# the easiest way forward is to replicate the relevant portions of the
# `log` function here.
def
count_commits
return
unless
ref
repository
=
@project
.
repository
.
raw_repository
sha
=
@project
.
repository
.
commit
(
ref
).
sha
cmd
=
%W(git --git-dir=
#{
repository
.
path
}
log)
cmd
<<
'--format=%H'
cmd
<<
"--after=
#{
@from
.
iso8601
}
"
cmd
<<
sha
output
,
status
=
Gitlab
::
Popen
.
popen
(
cmd
)
raise
IOError
,
output
unless
status
.
zero?
output
.
lines
.
count
end
def
ref
@ref
||=
@project
.
default_branch
.
presence
end
end
end
end
end
lib/gitlab/cycle_analytics/summary/deploy.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
module
Summary
class
Deploy
<
Base
def
value
@value
||=
@project
.
deployments
.
where
(
"created_at > ?"
,
@from
).
count
end
end
end
end
end
lib/gitlab/cycle_analytics/summary/issue.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
module
Summary
class
Issue
<
Base
def
initialize
(
project
:,
from
:,
current_user
:)
@project
=
project
@from
=
from
@current_user
=
current_user
end
def
title
'New Issue'
end
def
value
@value
||=
IssuesFinder
.
new
(
@current_user
,
project_id:
@project
.
id
).
execute
.
created_after
(
@from
).
count
end
end
end
end
end
lib/gitlab/cycle_analytics/test_event.rb
deleted
100644 → 0
View file @
f185d9e9
module
Gitlab
module
CycleAnalytics
class
TestEvent
<
StagingEvent
def
initialize
(
*
args
)
super
(
*
args
)
@stage
=
:test
@start_time_attrs
=
mr_metrics_table
[
:latest_build_started_at
]
@end_time_attrs
=
mr_metrics_table
[
:latest_build_finished_at
]
end
end
end
end
lib/gitlab/cycle_analytics/test_event_fetcher.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
TestEventFetcher
<
StagingEventFetcher
end
end
end
lib/gitlab/cycle_analytics/test_stage.rb
0 → 100644
View file @
745e0f87
module
Gitlab
module
CycleAnalytics
class
TestStage
<
BaseStage
def
start_time_attrs
@start_time_attrs
||=
mr_metrics_table
[
:latest_build_started_at
]
end
def
end_time_attrs
@end_time_attrs
||=
mr_metrics_table
[
:latest_build_finished_at
]
end
def
name
:test
end
def
description
"Total test time for all commits/merges"
end
def
stage_query
if
@options
[
:branch
]
super
.
where
(
build_table
[
:ref
].
eq
(
@options
[
:branch
]))
else
super
end
end
end
end
end
lib/gitlab/database/median.rb
View file @
745e0f87
...
...
@@ -103,6 +103,11 @@ module Gitlab
Arel
.
sql
(
%Q{EXTRACT(EPOCH FROM "
#{
arel_attribute
.
relation
.
name
}
"."
#{
arel_attribute
.
name
}
")}
)
end
def
extract_diff_epoch
(
diff
)
return
diff
unless
Gitlab
::
Database
.
postgresql?
Arel
.
sql
(
%Q{EXTRACT(EPOCH FROM (
#{
diff
.
to_sql
}
))}
)
end
# Need to cast '0' to an INTERVAL before we can check if the interval is positive
def
zero_interval
Arel
::
Nodes
::
NamedFunction
.
new
(
"CAST"
,
[
Arel
.
sql
(
"'0' AS INTERVAL"
)])
...
...
spec/lib/gitlab/cycle_analytics/
production_event
_spec.rb
→
spec/lib/gitlab/cycle_analytics/
code_event_fetcher
_spec.rb
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_event_spec'
describe
Gitlab
::
CycleAnalytics
::
ProductionEvent
do
describe
Gitlab
::
CycleAnalytics
::
CodeEventFetcher
do
let
(
:stage_name
)
{
:code
}
it_behaves_like
'default query config'
do
it
'has
the
default order'
do
expect
(
event
.
order
).
to
eq
(
event
.
start_time_attrs
)
it
'has
a
default order'
do
expect
(
event
.
order
).
not_to
be_nil
end
end
end
spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_stage_spec'
describe
Gitlab
::
CycleAnalytics
::
CodeStage
do
let
(
:stage_name
)
{
:code
}
it_behaves_like
'base stage'
end
spec/lib/gitlab/cycle_analytics/events_spec.rb
View file @
745e0f87
This diff is collapsed.
Click to expand it.
spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_event_spec'
describe
Gitlab
::
CycleAnalytics
::
IssueEventFetcher
do
let
(
:stage_name
)
{
:issue
}
it_behaves_like
'default query config'
end
spec/lib/gitlab/cycle_analytics/issue_event_spec.rb
deleted
100644 → 0
View file @
f185d9e9
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_event_spec'
describe
Gitlab
::
CycleAnalytics
::
IssueEvent
do
it_behaves_like
'default query config'
do
it
'has the default order'
do
expect
(
event
.
order
).
to
eq
(
event
.
start_time_attrs
)
end
end
end
spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_stage_spec'
describe
Gitlab
::
CycleAnalytics
::
IssueStage
do
let
(
:stage_name
)
{
:issue
}
it_behaves_like
'base stage'
end
spec/lib/gitlab/cycle_analytics/plan_event_spec.rb
→
spec/lib/gitlab/cycle_analytics/plan_event_
fetcher_
spec.rb
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_event_spec'
describe
Gitlab
::
CycleAnalytics
::
PlanEvent
do
it_behaves_like
'default query config'
do
it
'has the default order'
do
expect
(
event
.
order
).
to
eq
(
event
.
start_time_attrs
)
end
describe
Gitlab
::
CycleAnalytics
::
PlanEventFetcher
do
let
(
:stage_name
)
{
:plan
}
it_behaves_like
'default query config'
do
context
'no commits'
do
it
'does not blow up if there are no commits'
do
allow
_any_instance_of
(
Gitlab
::
CycleAnalytics
::
EventsQuery
).
to
receive
(
:execute
).
and_return
([{}])
allow
(
event
).
to
receive
(
:event_result
).
and_return
([{}])
expect
{
event
.
fetch
}.
not_to
raise_error
end
...
...
spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_stage_spec'
describe
Gitlab
::
CycleAnalytics
::
PlanStage
do
let
(
:stage_name
)
{
:plan
}
it_behaves_like
'base stage'
end
spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_event_spec'
describe
Gitlab
::
CycleAnalytics
::
ProductionEventFetcher
do
let
(
:stage_name
)
{
:production
}
it_behaves_like
'default query config'
end
spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_stage_spec'
describe
Gitlab
::
CycleAnalytics
::
ProductionStage
do
let
(
:stage_name
)
{
:production
}
it_behaves_like
'base stage'
end
spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_event_spec'
describe
Gitlab
::
CycleAnalytics
::
ReviewEventFetcher
do
let
(
:stage_name
)
{
:review
}
it_behaves_like
'default query config'
end
spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_stage_spec'
describe
Gitlab
::
CycleAnalytics
::
ReviewStage
do
let
(
:stage_name
)
{
:review
}
it_behaves_like
'base stage'
end
spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
View file @
745e0f87
require
'spec_helper'
shared_examples
'default query config'
do
let
(
:event
)
{
described_class
.
new
(
project:
double
,
options:
{})
}
it
'has the start attributes'
do
expect
(
event
.
start_time_attrs
).
not_to
be_nil
end
let
(
:project
)
{
create
(
:empty_project
)
}
let
(
:event
)
{
described_class
.
new
(
project:
project
,
stage:
stage_name
,
options:
{
from:
1
.
day
.
ago
})
}
it
'has the stage attribute'
do
expect
(
event
.
stage
).
not_to
be_nil
end
it
'has the end attributes'
do
expect
(
event
.
end_time_attrs
).
not_to
be_nil
end
it
'has the projection attributes'
do
expect
(
event
.
projections
).
not_to
be_nil
end
...
...
spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
shared_examples
'base stage'
do
let
(
:stage
)
{
described_class
.
new
(
project:
double
,
options:
{})
}
before
do
allow
(
stage
).
to
receive
(
:median
).
and_return
(
1.12
)
allow_any_instance_of
(
Gitlab
::
CycleAnalytics
::
BaseEventFetcher
).
to
receive
(
:event_result
).
and_return
({})
end
it
'has the median data value'
do
expect
(
stage
.
as_json
[
:value
]).
not_to
be_nil
end
it
'has the median data stage'
do
expect
(
stage
.
as_json
[
:title
]).
not_to
be_nil
end
it
'has the median data description'
do
expect
(
stage
.
as_json
[
:description
]).
not_to
be_nil
end
it
'has the title'
do
expect
(
stage
.
title
).
to
eq
(
stage_name
.
to_s
.
capitalize
)
end
it
'has the events'
do
expect
(
stage
.
events
).
not_to
be_nil
end
end
spec/
models/cycle_analytics/
summary_spec.rb
→
spec/
lib/gitlab/cycle_analytics/stage_
summary_spec.rb
View file @
745e0f87
require
'spec_helper'
describe
CycleAnalytics
::
Summary
,
models:
true
do
describe
Gitlab
::
CycleAnalytics
::
Stage
Summary
,
models:
true
do
let
(
:project
)
{
create
(
:project
)
}
let
(
:from
)
{
Time
.
now
}
let
(
:from
)
{
1
.
day
.
ago
}
let
(
:user
)
{
create
(
:user
,
:admin
)
}
subject
{
described_class
.
new
(
project
,
user
,
from:
from
)
}
subject
{
described_class
.
new
(
project
,
from:
Time
.
now
,
current_user:
user
).
data
}
describe
"#new_issues"
do
it
"finds the number of issues created after the 'from date'"
do
Timecop
.
freeze
(
5
.
days
.
ago
)
{
create
(
:issue
,
project:
project
)
}
Timecop
.
freeze
(
5
.
days
.
from_now
)
{
create
(
:issue
,
project:
project
)
}
expect
(
subject
.
new_issues
).
to
eq
(
1
)
expect
(
subject
.
first
[
:value
]
).
to
eq
(
1
)
end
it
"doesn't find issues from other projects"
do
Timecop
.
freeze
(
5
.
days
.
from_now
)
{
create
(
:issue
,
project:
create
(
:project
))
}
expect
(
subject
.
new_issues
).
to
eq
(
0
)
expect
(
subject
.
first
[
:value
]
).
to
eq
(
0
)
end
end
...
...
@@ -26,19 +26,19 @@ describe CycleAnalytics::Summary, models: true do
Timecop
.
freeze
(
5
.
days
.
ago
)
{
create_commit
(
"Test message"
,
project
,
user
,
'master'
)
}
Timecop
.
freeze
(
5
.
days
.
from_now
)
{
create_commit
(
"Test message"
,
project
,
user
,
'master'
)
}
expect
(
subject
.
commits
).
to
eq
(
1
)
expect
(
subject
.
second
[
:value
]
).
to
eq
(
1
)
end
it
"doesn't find commits from other projects"
do
Timecop
.
freeze
(
5
.
days
.
from_now
)
{
create_commit
(
"Test message"
,
create
(
:project
),
user
,
'master'
)
}
expect
(
subject
.
commits
).
to
eq
(
0
)
expect
(
subject
.
second
[
:value
]
).
to
eq
(
0
)
end
it
"finds a large (> 100) snumber of commits if present"
do
Timecop
.
freeze
(
5
.
days
.
from_now
)
{
create_commit
(
"Test message"
,
project
,
user
,
'master'
,
count:
100
)
}
expect
(
subject
.
commits
).
to
eq
(
100
)
expect
(
subject
.
second
[
:value
]
).
to
eq
(
100
)
end
end
...
...
@@ -47,13 +47,13 @@ describe CycleAnalytics::Summary, models: true do
Timecop
.
freeze
(
5
.
days
.
ago
)
{
create
(
:deployment
,
project:
project
)
}
Timecop
.
freeze
(
5
.
days
.
from_now
)
{
create
(
:deployment
,
project:
project
)
}
expect
(
subject
.
deploys
).
to
eq
(
1
)
expect
(
subject
.
third
[
:value
]
).
to
eq
(
1
)
end
it
"doesn't find commits from other projects"
do
Timecop
.
freeze
(
5
.
days
.
from_now
)
{
create
(
:deployment
,
project:
create
(
:project
))
}
expect
(
subject
.
deploys
).
to
eq
(
0
)
expect
(
subject
.
third
[
:value
]
).
to
eq
(
0
)
end
end
end
spec/lib/gitlab/cycle_analytics/
review_event
_spec.rb
→
spec/lib/gitlab/cycle_analytics/
staging_event_fetcher
_spec.rb
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_event_spec'
describe
Gitlab
::
CycleAnalytics
::
ReviewEvent
do
describe
Gitlab
::
CycleAnalytics
::
StagingEventFetcher
do
let
(
:stage_name
)
{
:staging
}
it_behaves_like
'default query config'
do
it
'has
the
default order'
do
expect
(
event
.
order
).
to
eq
(
event
.
start_time_attrs
)
it
'has
a
default order'
do
expect
(
event
.
order
).
not_to
be_nil
end
end
end
spec/lib/gitlab/cycle_analytics/staging_event_spec.rb
deleted
100644 → 0
View file @
f185d9e9
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_event_spec'
describe
Gitlab
::
CycleAnalytics
::
StagingEvent
do
it_behaves_like
'default query config'
do
it
'does not have the default order'
do
expect
(
event
.
order
).
not_to
eq
(
event
.
start_time_attrs
)
end
end
end
spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_stage_spec'
describe
Gitlab
::
CycleAnalytics
::
StagingStage
do
let
(
:stage_name
)
{
:staging
}
it_behaves_like
'base stage'
end
spec/lib/gitlab/cycle_analytics/
code_event
_spec.rb
→
spec/lib/gitlab/cycle_analytics/
test_event_fetcher
_spec.rb
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_event_spec'
describe
Gitlab
::
CycleAnalytics
::
CodeEvent
do
describe
Gitlab
::
CycleAnalytics
::
TestEventFetcher
do
let
(
:stage_name
)
{
:test
}
it_behaves_like
'default query config'
do
it
'
does not have the
default order'
do
expect
(
event
.
order
).
not_to
eq
(
event
.
start_time_attrs
)
it
'
has a
default order'
do
expect
(
event
.
order
).
not_to
be_nil
end
end
end
spec/lib/gitlab/cycle_analytics/test_event_spec.rb
deleted
100644 → 0
View file @
f185d9e9
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_event_spec'
describe
Gitlab
::
CycleAnalytics
::
TestEvent
do
it_behaves_like
'default query config'
do
it
'does not have the default order'
do
expect
(
event
.
order
).
not_to
eq
(
event
.
start_time_attrs
)
end
end
end
spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
require
'lib/gitlab/cycle_analytics/shared_stage_spec'
describe
Gitlab
::
CycleAnalytics
::
TestStage
do
let
(
:stage_name
)
{
:test
}
it_behaves_like
'base stage'
end
spec/models/cycle_analytics/code_spec.rb
View file @
745e0f87
...
...
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#code', feature: true do
let
(
:project
)
{
create
(
:project
)
}
let
(
:from_date
)
{
10
.
days
.
ago
}
let
(
:user
)
{
create
(
:user
,
:admin
)
}
subject
{
CycleAnalytics
.
new
(
project
,
user
,
from:
from_date
)
}
subject
{
CycleAnalytics
.
new
(
project
,
from:
from_date
)
}
context
'with deployment'
do
generate_cycle_analytics_spec
(
...
...
@@ -16,10 +16,10 @@ describe 'CycleAnalytics#code', feature: true do
->
(
context
,
data
)
do
context
.
create_commit_referencing_issue
(
data
[
:issue
])
end
]],
end_time_conditions:
[[
"merge request that closes issue is created"
,
->
(
context
,
data
)
do
context
.
create_merge_request_closing_issue
(
data
[
:issue
])
end
]],
end_time_conditions:
[[
"merge request that closes issue is created"
,
->
(
context
,
data
)
do
context
.
create_merge_request_closing_issue
(
data
[
:issue
])
end
]],
post_fn:
->
(
context
,
data
)
do
context
.
merge_merge_requests_closing_issue
(
data
[
:issue
])
context
.
deploy_master
...
...
@@ -37,7 +37,7 @@ describe 'CycleAnalytics#code', feature: true do
deploy_master
end
expect
(
subject
.
code
).
to
be_nil
expect
(
subject
[
:code
].
median
).
to
be_nil
end
end
end
...
...
@@ -50,10 +50,10 @@ describe 'CycleAnalytics#code', feature: true do
->
(
context
,
data
)
do
context
.
create_commit_referencing_issue
(
data
[
:issue
])
end
]],
end_time_conditions:
[[
"merge request that closes issue is created"
,
->
(
context
,
data
)
do
context
.
create_merge_request_closing_issue
(
data
[
:issue
])
end
]],
end_time_conditions:
[[
"merge request that closes issue is created"
,
->
(
context
,
data
)
do
context
.
create_merge_request_closing_issue
(
data
[
:issue
])
end
]],
post_fn:
->
(
context
,
data
)
do
context
.
merge_merge_requests_closing_issue
(
data
[
:issue
])
end
)
...
...
@@ -69,7 +69,7 @@ describe 'CycleAnalytics#code', feature: true do
merge_merge_requests_closing_issue
(
issue
)
end
expect
(
subject
.
code
).
to
be_nil
expect
(
subject
[
:code
].
median
).
to
be_nil
end
end
end
...
...
spec/models/cycle_analytics/issue_spec.rb
View file @
745e0f87
...
...
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#issue', models: true do
let
(
:project
)
{
create
(
:project
)
}
let
(
:from_date
)
{
10
.
days
.
ago
}
let
(
:user
)
{
create
(
:user
,
:admin
)
}
subject
{
CycleAnalytics
.
new
(
project
,
user
,
from:
from_date
)
}
subject
{
CycleAnalytics
.
new
(
project
,
from:
from_date
)
}
generate_cycle_analytics_spec
(
phase: :issue
,
...
...
@@ -42,7 +42,7 @@ describe 'CycleAnalytics#issue', models: true do
merge_merge_requests_closing_issue
(
issue
)
end
expect
(
subject
.
issue
).
to
be_nil
expect
(
subject
[
:issue
].
median
).
to
be_nil
end
end
end
spec/models/cycle_analytics/plan_spec.rb
View file @
745e0f87
...
...
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#plan', feature: true do
let
(
:project
)
{
create
(
:project
)
}
let
(
:from_date
)
{
10
.
days
.
ago
}
let
(
:user
)
{
create
(
:user
,
:admin
)
}
subject
{
CycleAnalytics
.
new
(
project
,
user
,
from:
from_date
)
}
subject
{
CycleAnalytics
.
new
(
project
,
from:
from_date
)
}
generate_cycle_analytics_spec
(
phase: :plan
,
...
...
@@ -44,7 +44,7 @@ describe 'CycleAnalytics#plan', feature: true do
create_merge_request_closing_issue
(
issue
,
source_branch:
branch_name
)
merge_merge_requests_closing_issue
(
issue
)
expect
(
subject
.
issue
).
to
be_nil
expect
(
subject
[
:issue
].
median
).
to
be_nil
end
end
end
spec/models/cycle_analytics/production_spec.rb
View file @
745e0f87
...
...
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#production', feature: true do
let
(
:project
)
{
create
(
:project
)
}
let
(
:from_date
)
{
10
.
days
.
ago
}
let
(
:user
)
{
create
(
:user
,
:admin
)
}
subject
{
CycleAnalytics
.
new
(
project
,
user
,
from:
from_date
)
}
subject
{
CycleAnalytics
.
new
(
project
,
from:
from_date
)
}
generate_cycle_analytics_spec
(
phase: :production
,
...
...
@@ -35,7 +35,7 @@ describe 'CycleAnalytics#production', feature: true do
deploy_master
end
expect
(
subject
.
productio
n
).
to
be_nil
expect
(
subject
[
:production
].
media
n
).
to
be_nil
end
end
...
...
@@ -48,7 +48,7 @@ describe 'CycleAnalytics#production', feature: true do
deploy_master
(
environment:
'staging'
)
end
expect
(
subject
.
productio
n
).
to
be_nil
expect
(
subject
[
:production
].
media
n
).
to
be_nil
end
end
end
spec/models/cycle_analytics/review_spec.rb
View file @
745e0f87
...
...
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#review', feature: true do
let
(
:project
)
{
create
(
:project
)
}
let
(
:from_date
)
{
10
.
days
.
ago
}
let
(
:user
)
{
create
(
:user
,
:admin
)
}
subject
{
CycleAnalytics
.
new
(
project
,
user
,
from:
from_date
)
}
subject
{
CycleAnalytics
.
new
(
project
,
from:
from_date
)
}
generate_cycle_analytics_spec
(
phase: :review
,
...
...
@@ -27,7 +27,7 @@ describe 'CycleAnalytics#review', feature: true do
MergeRequests
::
MergeService
.
new
(
project
,
user
).
execute
(
create
(
:merge_request
))
end
expect
(
subject
.
review
).
to
be_nil
expect
(
subject
[
:review
].
median
).
to
be_nil
end
end
end
spec/models/cycle_analytics/staging_spec.rb
View file @
745e0f87
...
...
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#staging', feature: true do
let
(
:project
)
{
create
(
:project
)
}
let
(
:from_date
)
{
10
.
days
.
ago
}
let
(
:user
)
{
create
(
:user
,
:admin
)
}
subject
{
CycleAnalytics
.
new
(
project
,
user
,
from:
from_date
)
}
subject
{
CycleAnalytics
.
new
(
project
,
from:
from_date
)
}
generate_cycle_analytics_spec
(
phase: :staging
,
...
...
@@ -45,7 +45,7 @@ describe 'CycleAnalytics#staging', feature: true do
deploy_master
end
expect
(
subject
.
staging
).
to
be_nil
expect
(
subject
[
:staging
].
median
).
to
be_nil
end
end
...
...
@@ -58,7 +58,7 @@ describe 'CycleAnalytics#staging', feature: true do
deploy_master
(
environment:
'staging'
)
end
expect
(
subject
.
staging
).
to
be_nil
expect
(
subject
[
:staging
].
median
).
to
be_nil
end
end
end
spec/models/cycle_analytics/test_spec.rb
View file @
745e0f87
...
...
@@ -6,7 +6,7 @@ describe 'CycleAnalytics#test', feature: true do
let
(
:project
)
{
create
(
:project
)
}
let
(
:from_date
)
{
10
.
days
.
ago
}
let
(
:user
)
{
create
(
:user
,
:admin
)
}
subject
{
CycleAnalytics
.
new
(
project
,
user
,
from:
from_date
)
}
subject
{
CycleAnalytics
.
new
(
project
,
from:
from_date
)
}
generate_cycle_analytics_spec
(
phase: :test
,
...
...
@@ -35,7 +35,7 @@ describe 'CycleAnalytics#test', feature: true do
merge_merge_requests_closing_issue
(
issue
)
end
expect
(
subject
.
test
).
to
be_nil
expect
(
subject
[
:test
].
median
).
to
be_nil
end
end
...
...
@@ -48,7 +48,7 @@ describe 'CycleAnalytics#test', feature: true do
pipeline
.
succeed!
end
expect
(
subject
.
test
).
to
be_nil
expect
(
subject
[
:test
].
median
).
to
be_nil
end
end
...
...
@@ -65,7 +65,7 @@ describe 'CycleAnalytics#test', feature: true do
merge_merge_requests_closing_issue
(
issue
)
end
expect
(
subject
.
test
).
to
be_nil
expect
(
subject
[
:test
].
median
).
to
be_nil
end
end
...
...
@@ -82,7 +82,7 @@ describe 'CycleAnalytics#test', feature: true do
merge_merge_requests_closing_issue
(
issue
)
end
expect
(
subject
.
test
).
to
be_nil
expect
(
subject
[
:test
].
median
).
to
be_nil
end
end
end
spec/serializers/analytics_stage_serializer_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
describe
AnalyticsStageSerializer
do
let
(
:serializer
)
do
described_class
.
new
.
represent
(
resource
)
end
let
(
:json
)
{
serializer
.
as_json
}
let
(
:resource
)
{
Gitlab
::
CycleAnalytics
::
CodeStage
.
new
(
project:
double
,
options:
{})
}
before
do
allow_any_instance_of
(
Gitlab
::
CycleAnalytics
::
BaseStage
).
to
receive
(
:median
).
and_return
(
1.12
)
allow_any_instance_of
(
Gitlab
::
CycleAnalytics
::
BaseEventFetcher
).
to
receive
(
:event_result
).
and_return
({})
end
it
'it generates payload for single object'
do
expect
(
json
).
to
be_kind_of
Hash
end
it
'contains important elements of AnalyticsStage'
do
expect
(
json
).
to
include
(
:title
,
:description
,
:value
)
end
end
spec/serializers/analytics_summary_serializer_spec.rb
0 → 100644
View file @
745e0f87
require
'spec_helper'
describe
AnalyticsSummarySerializer
do
let
(
:serializer
)
do
described_class
.
new
.
represent
(
resource
)
end
let
(
:json
)
{
serializer
.
as_json
}
let
(
:project
)
{
create
(
:empty_project
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:resource
)
do
Gitlab
::
CycleAnalytics
::
Summary
::
Issue
.
new
(
project:
double
,
from:
1
.
day
.
ago
,
current_user:
user
)
end
before
do
allow_any_instance_of
(
Gitlab
::
CycleAnalytics
::
Summary
::
Issue
).
to
receive
(
:value
).
and_return
(
1.12
)
end
it
'it generates payload for single object'
do
expect
(
json
).
to
be_kind_of
Hash
end
it
'contains important elements of AnalyticsStage'
do
expect
(
json
).
to
include
(
:title
,
:value
)
end
end
spec/support/cycle_analytics_helpers/test_generation.rb
View file @
745e0f87
...
...
@@ -2,7 +2,6 @@
# Note: The ABC size is large here because we have a method generating test cases with
# multiple nested contexts. This shouldn't count as a violation.
module
CycleAnalyticsHelpers
module
TestGeneration
# Generate the most common set of specs that all cycle analytics phases need to have.
...
...
@@ -51,7 +50,7 @@ module CycleAnalyticsHelpers
end
median_time_difference
=
time_differences
.
sort
[
2
]
expect
(
subject
.
send
(
phase
)
).
to
be_within
(
5
).
of
(
median_time_difference
)
expect
(
subject
[
phase
].
median
).
to
be_within
(
5
).
of
(
median_time_difference
)
end
context
"when the data belongs to another project"
do
...
...
@@ -83,7 +82,7 @@ module CycleAnalyticsHelpers
# Turn off the stub before checking assertions
allow
(
self
).
to
receive
(
:project
).
and_call_original
expect
(
subject
.
send
(
phase
)
).
to
be_nil
expect
(
subject
[
phase
].
median
).
to
be_nil
end
end
...
...
@@ -106,7 +105,7 @@ module CycleAnalyticsHelpers
Timecop
.
freeze
(
end_time
+
1
.
day
)
{
post_fn
[
self
,
data
]
}
if
post_fn
expect
(
subject
.
send
(
phase
)
).
to
be_nil
expect
(
subject
[
phase
].
median
).
to
be_nil
end
end
end
...
...
@@ -126,7 +125,7 @@ module CycleAnalyticsHelpers
Timecop
.
freeze
(
end_time
+
1
.
day
)
{
post_fn
[
self
,
data
]
}
if
post_fn
end
expect
(
subject
.
send
(
phase
)
).
to
be_nil
expect
(
subject
[
phase
].
median
).
to
be_nil
end
end
end
...
...
@@ -145,7 +144,7 @@ module CycleAnalyticsHelpers
post_fn
[
self
,
data
]
if
post_fn
end
expect
(
subject
.
send
(
phase
)
).
to
be_nil
expect
(
subject
[
phase
].
median
).
to
be_nil
end
end
end
...
...
@@ -153,7 +152,7 @@ module CycleAnalyticsHelpers
context
"when none of the start / end conditions are matched"
do
it
"returns nil"
do
expect
(
subject
.
send
(
phase
)
).
to
be_nil
expect
(
subject
[
phase
].
median
).
to
be_nil
end
end
end
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment