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
bf8c2072
Commit
bf8c2072
authored
Dec 07, 2017
by
Oswaldo Ferreira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Cache merged and closed events data in merge_request_metrics table
parent
2cbb2d0e
Changes
42
Hide whitespace changes
Inline
Side-by-side
Showing
42 changed files
with
799 additions
and
98 deletions
+799
-98
app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
...erge_request_widget/components/states/mr_widget_closed.js
+3
-3
app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js
...erge_request_widget/components/states/mr_widget_merged.js
+3
-3
app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
...cripts/vue_merge_request_widget/stores/mr_widget_store.js
+18
-24
app/models/concerns/issuable.rb
app/models/concerns/issuable.rb
+5
-6
app/models/issue.rb
app/models/issue.rb
+5
-0
app/models/merge_request/metrics.rb
app/models/merge_request/metrics.rb
+2
-8
app/serializers/event_entity.rb
app/serializers/event_entity.rb
+0
-4
app/serializers/merge_request_metrics_entity.rb
app/serializers/merge_request_metrics_entity.rb
+6
-0
app/serializers/merge_request_widget_entity.rb
app/serializers/merge_request_widget_entity.rb
+29
-3
app/services/event_create_service.rb
app/services/event_create_service.rb
+1
-1
app/services/merge_request_metrics_service.rb
app/services/merge_request_metrics_service.rb
+19
-0
app/services/merge_requests/base_service.rb
app/services/merge_requests/base_service.rb
+4
-0
app/services/merge_requests/close_service.rb
app/services/merge_requests/close_service.rb
+12
-1
app/services/merge_requests/post_merge_service.rb
app/services/merge_requests/post_merge_service.rb
+10
-1
app/services/merge_requests/reopen_service.rb
app/services/merge_requests/reopen_service.rb
+12
-1
changelogs/unreleased/osw-introduce-merge-request-statistics.yml
...ogs/unreleased/osw-introduce-merge-request-statistics.yml
+5
-0
db/migrate/20171127151038_add_events_related_columns_to_merge_request_metrics.rb
...38_add_events_related_columns_to_merge_request_metrics.rb
+37
-0
db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb
...hedule_populate_merge_request_metrics_with_events_data.rb
+68
-0
db/schema.rb
db/schema.rb
+5
-0
lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data.rb
...ration/populate_merge_request_metrics_with_events_data.rb
+135
-0
spec/fixtures/api/schemas/entities/merge_request_metrics.json
.../fixtures/api/schemas/entities/merge_request_metrics.json
+21
-0
spec/fixtures/api/schemas/entities/merge_request_widget.json
spec/fixtures/api/schemas/entities/merge_request_widget.json
+4
-2
spec/fixtures/api/schemas/entities/user.json
spec/fixtures/api/schemas/entities/user.json
+17
-0
spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
.../vue_mr_widget/components/states/mr_widget_closed_spec.js
+8
-5
spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
.../vue_mr_widget/components/states/mr_widget_merged_spec.js
+7
-4
spec/javascripts/vue_mr_widget/mock_data.js
spec/javascripts/vue_mr_widget/mock_data.js
+5
-4
spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
...n/populate_merge_request_metrics_with_events_data_spec.rb
+124
-0
spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
...e_populate_merge_request_metrics_with_events_data_spec.rb
+24
-0
spec/models/issue_spec.rb
spec/models/issue_spec.rb
+26
-0
spec/models/merge_request/metrics_spec.rb
spec/models/merge_request/metrics_spec.rb
+5
-10
spec/models/merge_request_spec.rb
spec/models/merge_request_spec.rb
+19
-0
spec/requests/api/notes_spec.rb
spec/requests/api/notes_spec.rb
+1
-1
spec/requests/api/project_milestones_spec.rb
spec/requests/api/project_milestones_spec.rb
+1
-1
spec/requests/api/v3/milestones_spec.rb
spec/requests/api/v3/milestones_spec.rb
+1
-1
spec/requests/api/v3/notes_spec.rb
spec/requests/api/v3/notes_spec.rb
+1
-1
spec/serializers/event_entity_spec.rb
spec/serializers/event_entity_spec.rb
+0
-13
spec/serializers/merge_request_widget_entity_spec.rb
spec/serializers/merge_request_widget_entity_spec.rb
+74
-0
spec/services/create_deployment_service_spec.rb
spec/services/create_deployment_service_spec.rb
+1
-1
spec/services/merge_requests/close_service_spec.rb
spec/services/merge_requests/close_service_spec.rb
+13
-0
spec/services/merge_requests/post_merge_service_spec.rb
spec/services/merge_requests/post_merge_service_spec.rb
+13
-0
spec/services/merge_requests/reopen_service_spec.rb
spec/services/merge_requests/reopen_service_spec.rb
+13
-0
spec/services/update_merge_request_metrics_service_spec.rb
spec/services/update_merge_request_metrics_service_spec.rb
+42
-0
No files found.
app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
View file @
bf8c2072
...
@@ -16,9 +16,9 @@ export default {
...
@@ -16,9 +16,9 @@ export default {
<div class="media-body">
<div class="media-body">
<mr-widget-author-and-time
<mr-widget-author-and-time
actionText="Closed by"
actionText="Closed by"
:author="mr.
closedEvent.author
"
:author="mr.
metrics.closedBy
"
:dateTitle="mr.
closedEvent.updat
edAt"
:dateTitle="mr.
metrics.clos
edAt"
:dateReadable="mr.
closedEvent.formattedUpdat
edAt"
:dateReadable="mr.
metrics.readableClos
edAt"
/>
/>
<section class="mr-info-list">
<section class="mr-info-list">
<p>
<p>
...
...
app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.js
View file @
bf8c2072
...
@@ -68,9 +68,9 @@ export default {
...
@@ -68,9 +68,9 @@ export default {
<div class="space-children">
<div class="space-children">
<mr-widget-author-and-time
<mr-widget-author-and-time
actionText="Merged by"
actionText="Merged by"
:author="mr.me
rgedEvent.author
"
:author="mr.me
trics.mergedBy
"
:date-title="mr.me
rgedEvent.updat
edAt"
:date-title="mr.me
trics.merg
edAt"
:date-readable="mr.me
rgedEvent.formattedUpdat
edAt" />
:date-readable="mr.me
trics.readableMerg
edAt" />
<a
<a
v-if="mr.canRevertInCurrentMR"
v-if="mr.canRevertInCurrentMR"
v-tooltip
v-tooltip
...
...
app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
View file @
bf8c2072
...
@@ -39,9 +39,8 @@ export default class MergeRequestStore {
...
@@ -39,9 +39,8 @@ export default class MergeRequestStore {
}
}
this
.
updatedAt
=
data
.
updated_at
;
this
.
updatedAt
=
data
.
updated_at
;
this
.
mergedEvent
=
MergeRequestStore
.
getEventObject
(
data
.
merge_event
);
this
.
metrics
=
MergeRequestStore
.
buildMetrics
(
data
.
metrics
);
this
.
closedEvent
=
MergeRequestStore
.
getEventObject
(
data
.
closed_event
);
this
.
setToMWPSBy
=
MergeRequestStore
.
formatUserObject
(
data
.
merge_user
||
{});
this
.
setToMWPSBy
=
MergeRequestStore
.
getAuthorObject
({
author
:
data
.
merge_user
||
{}
});
this
.
mergeUserId
=
data
.
merge_user_id
;
this
.
mergeUserId
=
data
.
merge_user_id
;
this
.
currentUserId
=
gon
.
current_user_id
;
this
.
currentUserId
=
gon
.
current_user_id
;
this
.
sourceBranchPath
=
data
.
source_branch_path
;
this
.
sourceBranchPath
=
data
.
source_branch_path
;
...
@@ -125,43 +124,38 @@ export default class MergeRequestStore {
...
@@ -125,43 +124,38 @@ export default class MergeRequestStore {
return
this
.
state
===
stateKey
.
nothingToMerge
;
return
this
.
state
===
stateKey
.
nothingToMerge
;
}
}
static
getEventObject
(
event
)
{
static
buildMetrics
(
metrics
)
{
return
{
return
{
author
:
MergeRequestStore
.
getAuthorObject
(
event
),
mergedBy
:
MergeRequestStore
.
formatUserObject
(
metrics
.
merged_by
),
updatedAt
:
formatDate
(
MergeRequestStore
.
getEventUpdatedAtDate
(
event
)),
closedBy
:
MergeRequestStore
.
formatUserObject
(
metrics
.
closed_by
),
formattedUpdatedAt
:
MergeRequestStore
.
getEventDate
(
event
),
mergedAt
:
formatDate
(
metrics
.
merged_at
),
closedAt
:
formatDate
(
metrics
.
closed_at
),
readableMergedAt
:
MergeRequestStore
.
getReadableDate
(
metrics
.
merged_at
),
readableClosedAt
:
MergeRequestStore
.
getReadableDate
(
metrics
.
closed_at
),
};
};
}
}
static
getAuthorObject
(
event
)
{
static
formatUserObject
(
user
)
{
if
(
!
event
)
{
if
(
!
user
)
{
return
{};
return
{};
}
}
return
{
return
{
name
:
event
.
autho
r
.
name
||
''
,
name
:
use
r
.
name
||
''
,
username
:
event
.
autho
r
.
username
||
''
,
username
:
use
r
.
username
||
''
,
webUrl
:
event
.
autho
r
.
web_url
||
''
,
webUrl
:
use
r
.
web_url
||
''
,
avatarUrl
:
event
.
autho
r
.
avatar_url
||
''
,
avatarUrl
:
use
r
.
avatar_url
||
''
,
};
};
}
}
static
get
EventUpdatedAtDate
(
event
)
{
static
get
ReadableDate
(
date
)
{
if
(
!
event
)
{
if
(
!
date
)
{
return
''
;
return
''
;
}
}
return
event
.
updated_at
;
}
static
getEventDate
(
event
)
{
const
timeagoInstance
=
new
Timeago
();
const
timeagoInstance
=
new
Timeago
();
if
(
!
event
)
{
return
timeagoInstance
.
format
(
date
);
return
''
;
}
return
timeagoInstance
.
format
(
MergeRequestStore
.
getEventUpdatedAtDate
(
event
));
}
}
}
}
app/models/concerns/issuable.rb
View file @
bf8c2072
...
@@ -96,7 +96,7 @@ module Issuable
...
@@ -96,7 +96,7 @@ module Issuable
strip_attributes
:title
strip_attributes
:title
after_save
:
record
_metrics
,
unless: :imported?
after_save
:
ensure
_metrics
,
unless: :imported?
# We want to use optimistic lock for cases when only title or description are involved
# We want to use optimistic lock for cases when only title or description are involved
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
...
@@ -335,11 +335,6 @@ module Issuable
...
@@ -335,11 +335,6 @@ module Issuable
false
false
end
end
def
record_metrics
metrics
=
self
.
metrics
||
create_metrics
metrics
.
record!
end
##
##
# Override in issuable specialization
# Override in issuable specialization
#
#
...
@@ -347,6 +342,10 @@ module Issuable
...
@@ -347,6 +342,10 @@ module Issuable
false
false
end
end
def
ensure_metrics
self
.
metrics
||
create_metrics
end
##
##
# Overriden in MergeRequest
# Overriden in MergeRequest
#
#
...
...
app/models/issue.rb
View file @
bf8c2072
...
@@ -276,6 +276,11 @@ class Issue < ActiveRecord::Base
...
@@ -276,6 +276,11 @@ class Issue < ActiveRecord::Base
private
private
def
ensure_metrics
super
metrics
.
record!
end
# Returns `true` if the given User can read the current Issue.
# Returns `true` if the given User can read the current Issue.
#
#
# This method duplicates the same check of issue_policy.rb
# This method duplicates the same check of issue_policy.rb
...
...
app/models/merge_request/metrics.rb
View file @
bf8c2072
class
MergeRequest::Metrics
<
ActiveRecord
::
Base
class
MergeRequest::Metrics
<
ActiveRecord
::
Base
belongs_to
:merge_request
belongs_to
:merge_request
belongs_to
:pipeline
,
class_name:
'Ci::Pipeline'
,
foreign_key: :pipeline_id
belongs_to
:pipeline
,
class_name:
'Ci::Pipeline'
,
foreign_key: :pipeline_id
belongs_to
:latest_closed_by
,
class_name:
'User'
def
record!
belongs_to
:merged_by
,
class_name:
'User'
if
merge_request
.
merged?
&&
self
.
merged_at
.
blank?
self
.
merged_at
=
Time
.
now
end
self
.
save
end
end
end
app/serializers/event_entity.rb
deleted
100644 → 0
View file @
2cbb2d0e
class
EventEntity
<
Grape
::
Entity
expose
:author
,
using:
UserEntity
expose
:updated_at
end
app/serializers/merge_request_metrics_entity.rb
0 → 100644
View file @
bf8c2072
class
MergeRequestMetricsEntity
<
Grape
::
Entity
expose
:latest_closed_at
,
as: :closed_at
expose
:merged_at
expose
:latest_closed_by
,
as: :closed_by
,
using:
UserEntity
expose
:merged_by
,
using:
UserEntity
end
app/serializers/merge_request_widget_entity.rb
View file @
bf8c2072
...
@@ -17,9 +17,11 @@ class MergeRequestWidgetEntity < IssuableEntity
...
@@ -17,9 +17,11 @@ class MergeRequestWidgetEntity < IssuableEntity
merge_request
.
project
.
merge_requests_ff_only_enabled
merge_request
.
project
.
merge_requests_ff_only_enabled
end
end
# Events
expose
:metrics
do
|
merge_request
|
expose
:merge_event
,
using:
EventEntity
metrics
=
build_metrics
(
merge_request
)
expose
:closed_event
,
using:
EventEntity
MergeRequestMetricsEntity
.
new
(
metrics
).
as_json
end
# User entities
# User entities
expose
:merge_user
,
using:
UserEntity
expose
:merge_user
,
using:
UserEntity
...
@@ -178,4 +180,28 @@ class MergeRequestWidgetEntity < IssuableEntity
...
@@ -178,4 +180,28 @@ class MergeRequestWidgetEntity < IssuableEntity
@presenters
||=
{}
@presenters
||=
{}
@presenters
[
merge_request
]
||=
MergeRequestPresenter
.
new
(
merge_request
,
current_user:
current_user
)
@presenters
[
merge_request
]
||=
MergeRequestPresenter
.
new
(
merge_request
,
current_user:
current_user
)
end
end
# Once SchedulePopulateMergeRequestMetricsWithEventsData fully runs,
# we can remove this method and just serialize MergeRequest#metrics
# instead. See https://gitlab.com/gitlab-org/gitlab-ce/issues/41587
def
build_metrics
(
merge_request
)
# There's no need to query and serialize metrics data for merge requests that are not
# merged or closed.
case
merge_request
.
state
when
'merged'
merge_request
.
metrics
&
.
merged_by_id
?
merge_request
.
metrics
:
build_metrics_from_events
(
merge_request
)
when
'closed'
merge_request
.
metrics
&
.
latest_closed_by_id
?
merge_request
.
metrics
:
build_metrics_from_events
(
merge_request
)
end
end
def
build_metrics_from_events
(
merge_request
)
closed_event
=
merge_request
.
closed_event
merge_event
=
merge_request
.
merge_event
MergeRequest
::
Metrics
.
new
(
latest_closed_at:
closed_event
&
.
updated_at
,
latest_closed_by:
closed_event
&
.
author
,
merged_at:
merge_event
&
.
updated_at
,
merged_by:
merge_event
&
.
author
)
end
end
end
app/services/event_create_service.rb
View file @
bf8c2072
...
@@ -103,6 +103,6 @@ class EventCreateService
...
@@ -103,6 +103,6 @@ class EventCreateService
author_id:
current_user
.
id
author_id:
current_user
.
id
)
)
Event
.
create
(
attributes
)
Event
.
create
!
(
attributes
)
end
end
end
end
app/services/merge_request_metrics_service.rb
0 → 100644
View file @
bf8c2072
class
MergeRequestMetricsService
delegate
:update!
,
to: :@merge_request_metrics
def
initialize
(
merge_request_metrics
)
@merge_request_metrics
=
merge_request_metrics
end
def
merge
(
event
)
update!
(
merged_by_id:
event
.
author_id
,
merged_at:
event
.
created_at
)
end
def
close
(
event
)
update!
(
latest_closed_by_id:
event
.
author_id
,
latest_closed_at:
event
.
created_at
)
end
def
reopen
update!
(
latest_closed_by_id:
nil
,
latest_closed_at:
nil
)
end
end
app/services/merge_requests/base_service.rb
View file @
bf8c2072
...
@@ -24,6 +24,10 @@ module MergeRequests
...
@@ -24,6 +24,10 @@ module MergeRequests
private
private
def
merge_request_metrics_service
(
merge_request
)
MergeRequestMetricsService
.
new
(
merge_request
.
metrics
)
end
def
create_assignee_note
(
merge_request
)
def
create_assignee_note
(
merge_request
)
SystemNoteService
.
change_assignee
(
SystemNoteService
.
change_assignee
(
merge_request
,
merge_request
.
project
,
current_user
,
merge_request
.
assignee
)
merge_request
,
merge_request
.
project
,
current_user
,
merge_request
.
assignee
)
...
...
app/services/merge_requests/close_service.rb
View file @
bf8c2072
...
@@ -8,7 +8,7 @@ module MergeRequests
...
@@ -8,7 +8,7 @@ module MergeRequests
merge_request
.
allow_broken
=
true
merge_request
.
allow_broken
=
true
if
merge_request
.
close
if
merge_request
.
close
event_service
.
close_mr
(
merge_request
,
current_user
)
create_event
(
merge_request
)
create_note
(
merge_request
)
create_note
(
merge_request
)
notification_service
.
close_mr
(
merge_request
,
current_user
)
notification_service
.
close_mr
(
merge_request
,
current_user
)
todo_service
.
close_merge_request
(
merge_request
,
current_user
)
todo_service
.
close_merge_request
(
merge_request
,
current_user
)
...
@@ -19,5 +19,16 @@ module MergeRequests
...
@@ -19,5 +19,16 @@ module MergeRequests
merge_request
merge_request
end
end
private
def
create_event
(
merge_request
)
# Making sure MergeRequest::Metrics updates are in sync with
# Event creation.
Event
.
transaction
do
close_event
=
event_service
.
close_mr
(
merge_request
,
current_user
)
merge_request_metrics_service
(
merge_request
).
close
(
close_event
)
end
end
end
end
end
end
app/services/merge_requests/post_merge_service.rb
View file @
bf8c2072
...
@@ -9,7 +9,7 @@ module MergeRequests
...
@@ -9,7 +9,7 @@ module MergeRequests
close_issues
(
merge_request
)
close_issues
(
merge_request
)
todo_service
.
merge_merge_request
(
merge_request
,
current_user
)
todo_service
.
merge_merge_request
(
merge_request
,
current_user
)
merge_request
.
mark_as_merged
merge_request
.
mark_as_merged
create_
merge_event
(
merge_request
,
current_user
)
create_
event
(
merge_request
)
create_note
(
merge_request
)
create_note
(
merge_request
)
notification_service
.
merge_mr
(
merge_request
,
current_user
)
notification_service
.
merge_mr
(
merge_request
,
current_user
)
execute_hooks
(
merge_request
,
'merge'
)
execute_hooks
(
merge_request
,
'merge'
)
...
@@ -34,5 +34,14 @@ module MergeRequests
...
@@ -34,5 +34,14 @@ module MergeRequests
def
create_merge_event
(
merge_request
,
current_user
)
def
create_merge_event
(
merge_request
,
current_user
)
EventCreateService
.
new
.
merge_mr
(
merge_request
,
current_user
)
EventCreateService
.
new
.
merge_mr
(
merge_request
,
current_user
)
end
end
def
create_event
(
merge_request
)
# Making sure MergeRequest::Metrics updates are in sync with
# Event creation.
Event
.
transaction
do
merge_event
=
create_merge_event
(
merge_request
,
current_user
)
merge_request_metrics_service
(
merge_request
).
merge
(
merge_event
)
end
end
end
end
end
end
app/services/merge_requests/reopen_service.rb
View file @
bf8c2072
...
@@ -4,7 +4,7 @@ module MergeRequests
...
@@ -4,7 +4,7 @@ module MergeRequests
return
merge_request
unless
can?
(
current_user
,
:update_merge_request
,
merge_request
)
return
merge_request
unless
can?
(
current_user
,
:update_merge_request
,
merge_request
)
if
merge_request
.
reopen
if
merge_request
.
reopen
event_service
.
reopen_mr
(
merge_request
,
current_user
)
create_event
(
merge_request
)
create_note
(
merge_request
,
'reopened'
)
create_note
(
merge_request
,
'reopened'
)
notification_service
.
reopen_mr
(
merge_request
,
current_user
)
notification_service
.
reopen_mr
(
merge_request
,
current_user
)
execute_hooks
(
merge_request
,
'reopen'
)
execute_hooks
(
merge_request
,
'reopen'
)
...
@@ -16,5 +16,16 @@ module MergeRequests
...
@@ -16,5 +16,16 @@ module MergeRequests
merge_request
merge_request
end
end
private
def
create_event
(
merge_request
)
# Making sure MergeRequest::Metrics updates are in sync with
# Event creation.
Event
.
transaction
do
event_service
.
reopen_mr
(
merge_request
,
current_user
)
merge_request_metrics_service
(
merge_request
).
reopen
end
end
end
end
end
end
changelogs/unreleased/osw-introduce-merge-request-statistics.yml
0 → 100644
View file @
bf8c2072
---
title
:
Cache merged and closed events data in merge_request_metrics table
merge_request
:
author
:
type
:
performance
db/migrate/20171127151038_add_events_related_columns_to_merge_request_metrics.rb
0 → 100644
View file @
bf8c2072
class
AddEventsRelatedColumnsToMergeRequestMetrics
<
ActiveRecord
::
Migration
include
Gitlab
::
Database
::
MigrationHelpers
DOWNTIME
=
false
disable_ddl_transaction!
def
up
change_table
:merge_request_metrics
do
|
t
|
t
.
references
:merged_by
,
references: :users
t
.
references
:latest_closed_by
,
references: :users
end
add_column
:merge_request_metrics
,
:latest_closed_at
,
:datetime_with_timezone
add_concurrent_foreign_key
:merge_request_metrics
,
:users
,
column: :merged_by_id
,
on_delete: :nullify
add_concurrent_foreign_key
:merge_request_metrics
,
:users
,
column: :latest_closed_by_id
,
on_delete: :nullify
end
def
down
if
foreign_keys_for
(
:merge_request_metrics
,
:merged_by_id
).
any?
remove_foreign_key
:merge_request_metrics
,
column: :merged_by_id
end
if
foreign_keys_for
(
:merge_request_metrics
,
:latest_closed_by_id
).
any?
remove_foreign_key
:merge_request_metrics
,
column: :latest_closed_by_id
end
remove_columns
:merge_request_metrics
,
:merged_by_id
,
:latest_closed_by_id
,
:latest_closed_at
end
end
db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb
0 → 100644
View file @
bf8c2072
# frozen_string_literal: true
# rubocop:disable GitlabSecurity/SqlInjection
class
SchedulePopulateMergeRequestMetricsWithEventsData
<
ActiveRecord
::
Migration
DOWNTIME
=
false
BATCH_SIZE
=
10_000
MIGRATION
=
'PopulateMergeRequestMetricsWithEventsData'
disable_ddl_transaction!
class
MergeRequest
<
ActiveRecord
::
Base
self
.
table_name
=
'merge_requests'
include
::
EachBatch
end
def
up
merge_requests
=
MergeRequest
.
where
(
"id IN (
#{
updatable_merge_requests_union_sql
}
)"
).
reorder
(
:id
)
say
'Scheduling `PopulateMergeRequestMetricsWithEventsData` jobs'
# It will update around 4_000_000 records in batches of 10_000 merge
# requests (running between 10 minutes) and should take around 66 hours to complete.
# Apparently, production PostgreSQL is able to vacuum 10k-20k dead_tuples by
# minute, and at maximum, each of these jobs should UPDATE 20k records.
#
# More information about the updates in `PopulateMergeRequestMetricsWithEventsData` class.
#
merge_requests
.
each_batch
(
of:
BATCH_SIZE
)
do
|
relation
,
index
|
range
=
relation
.
pluck
(
'MIN(id)'
,
'MAX(id)'
).
first
BackgroundMigrationWorker
.
perform_in
(
index
*
10
.
minutes
,
MIGRATION
,
range
)
end
end
def
down
execute
"update merge_request_metrics set latest_closed_at = null"
execute
"update merge_request_metrics set latest_closed_by_id = null"
execute
"update merge_request_metrics set merged_by_id = null"
end
private
# On staging:
# Planning time: 0.682 ms
# Execution time: 22033.158 ms
#
def
updatable_merge_requests_union_sql
metrics_not_exists_clause
=
'NOT EXISTS (SELECT 1 FROM merge_request_metrics WHERE merge_request_metrics.merge_request_id = merge_requests.id)'
without_metrics_data
=
<<-
SQL
.
strip_heredoc
merge_request_metrics.merged_by_id IS NULL OR
merge_request_metrics.latest_closed_by_id IS NULL OR
merge_request_metrics.latest_closed_at IS NULL
SQL
mrs_without_metrics_record
=
MergeRequest
.
where
(
metrics_not_exists_clause
)
.
select
(
:id
)
mrs_without_events_data
=
MergeRequest
.
joins
(
'INNER JOIN merge_request_metrics ON merge_requests.id = merge_request_metrics.merge_request_id'
)
.
where
(
without_metrics_data
)
.
select
(
:id
)
Gitlab
::
SQL
::
Union
.
new
([
mrs_without_metrics_record
,
mrs_without_events_data
]).
to_sql
end
end
db/schema.rb
View file @
bf8c2072
...
@@ -1056,6 +1056,9 @@ ActiveRecord::Schema.define(version: 20171220191323) do
...
@@ -1056,6 +1056,9 @@ ActiveRecord::Schema.define(version: 20171220191323) do
t
.
datetime
"created_at"
,
null:
false
t
.
datetime
"created_at"
,
null:
false
t
.
datetime
"updated_at"
,
null:
false
t
.
datetime
"updated_at"
,
null:
false
t
.
integer
"pipeline_id"
t
.
integer
"pipeline_id"
t
.
integer
"merged_by_id"
t
.
integer
"latest_closed_by_id"
t
.
datetime_with_timezone
"latest_closed_at"
end
end
add_index
"merge_request_metrics"
,
[
"first_deployed_to_production_at"
],
name:
"index_merge_request_metrics_on_first_deployed_to_production_at"
,
using: :btree
add_index
"merge_request_metrics"
,
[
"first_deployed_to_production_at"
],
name:
"index_merge_request_metrics_on_first_deployed_to_production_at"
,
using: :btree
...
@@ -1995,6 +1998,8 @@ ActiveRecord::Schema.define(version: 20171220191323) do
...
@@ -1995,6 +1998,8 @@ ActiveRecord::Schema.define(version: 20171220191323) do
add_foreign_key
"merge_request_diffs"
,
"merge_requests"
,
name:
"fk_8483f3258f"
,
on_delete: :cascade
add_foreign_key
"merge_request_diffs"
,
"merge_requests"
,
name:
"fk_8483f3258f"
,
on_delete: :cascade
add_foreign_key
"merge_request_metrics"
,
"ci_pipelines"
,
column:
"pipeline_id"
,
on_delete: :cascade
add_foreign_key
"merge_request_metrics"
,
"ci_pipelines"
,
column:
"pipeline_id"
,
on_delete: :cascade
add_foreign_key
"merge_request_metrics"
,
"merge_requests"
,
on_delete: :cascade
add_foreign_key
"merge_request_metrics"
,
"merge_requests"
,
on_delete: :cascade
add_foreign_key
"merge_request_metrics"
,
"users"
,
column:
"latest_closed_by_id"
,
name:
"fk_ae440388cc"
,
on_delete: :nullify
add_foreign_key
"merge_request_metrics"
,
"users"
,
column:
"merged_by_id"
,
name:
"fk_7f28d925f3"
,
on_delete: :nullify
add_foreign_key
"merge_requests"
,
"ci_pipelines"
,
column:
"head_pipeline_id"
,
name:
"fk_fd82eae0b9"
,
on_delete: :nullify
add_foreign_key
"merge_requests"
,
"ci_pipelines"
,
column:
"head_pipeline_id"
,
name:
"fk_fd82eae0b9"
,
on_delete: :nullify
add_foreign_key
"merge_requests"
,
"merge_request_diffs"
,
column:
"latest_merge_request_diff_id"
,
name:
"fk_06067f5644"
,
on_delete: :nullify
add_foreign_key
"merge_requests"
,
"merge_request_diffs"
,
column:
"latest_merge_request_diff_id"
,
name:
"fk_06067f5644"
,
on_delete: :nullify
add_foreign_key
"merge_requests"
,
"milestones"
,
name:
"fk_6a5165a692"
,
on_delete: :nullify
add_foreign_key
"merge_requests"
,
"milestones"
,
name:
"fk_6a5165a692"
,
on_delete: :nullify
...
...
lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data.rb
0 → 100644
View file @
bf8c2072
# frozen_string_literal: true
# rubocop:disable Metrics/LineLength
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/ClassLength
# rubocop:disable Style/Documentation
module
Gitlab
module
BackgroundMigration
class
PopulateMergeRequestMetricsWithEventsData
def
perform
(
min_merge_request_id
,
max_merge_request_id
)
insert_metrics_for_range
(
min_merge_request_id
,
max_merge_request_id
)
update_metrics_with_events_data
(
min_merge_request_id
,
max_merge_request_id
)
end
# Inserts merge_request_metrics records for merge_requests without it for
# a given merge request batch.
def
insert_metrics_for_range
(
min
,
max
)
metrics_not_exists_clause
=
<<-
SQL
.
strip_heredoc
NOT EXISTS (SELECT 1 FROM merge_request_metrics
WHERE merge_request_metrics.merge_request_id = merge_requests.id)
SQL
MergeRequest
.
where
(
metrics_not_exists_clause
).
where
(
id:
min
..
max
).
each_batch
do
|
batch
|
select_sql
=
batch
.
select
(
:id
,
:created_at
,
:updated_at
).
to_sql
execute
(
"INSERT INTO merge_request_metrics (merge_request_id, created_at, updated_at)
#{
select_sql
}
"
)
end
end
def
update_metrics_with_events_data
(
min
,
max
)
if
Gitlab
::
Database
.
postgresql?
# Uses WITH syntax in order to update merged and closed events with a single UPDATE.
# WITH is not supported by MySQL.
update_events_for_range
(
min
,
max
)
else
update_merged_events_for_range
(
min
,
max
)
update_closed_events_for_range
(
min
,
max
)
end
end
private
# Updates merge_request_metrics latest_closed_at, latest_closed_by_id and merged_by_id
# based on the latest event records on events table for a given merge request batch.
def
update_events_for_range
(
min
,
max
)
sql
=
<<-
SQL
.
strip_heredoc
WITH events_for_update AS (
SELECT DISTINCT ON (target_id, action) target_id, action, author_id, updated_at
FROM events
WHERE target_id BETWEEN
#{
min
}
AND
#{
max
}
AND target_type = 'MergeRequest'
AND action IN (
#{
Event
::
CLOSED
}
,
#{
Event
::
MERGED
}
)
ORDER BY target_id, action, id DESC
)
UPDATE merge_request_metrics met
SET latest_closed_at = latest_closed.updated_at,
latest_closed_by_id = latest_closed.author_id,
merged_by_id = latest_merged.author_id
FROM (SELECT * FROM events_for_update WHERE action =
#{
Event
::
CLOSED
}
) AS latest_closed
FULL OUTER JOIN
(SELECT * FROM events_for_update WHERE action =
#{
Event
::
MERGED
}
) AS latest_merged
USING (target_id)
WHERE target_id = merge_request_id;
SQL
execute
(
sql
)
end
# Updates merge_request_metrics latest_closed_at, latest_closed_by_id based on the latest closed
# records on events table for a given merge request batch.
def
update_closed_events_for_range
(
min
,
max
)
sql
=
<<-
SQL
.
strip_heredoc
UPDATE merge_request_metrics metrics,
(
#{
select_events
(
min
,
max
,
Event
::
CLOSED
)
}
) closed_events
SET metrics.latest_closed_by_id = closed_events.author_id,
metrics.latest_closed_at = closed_events.updated_at
#{
where_matches_closed_events
}
;
SQL
execute
(
sql
)
end
# Updates merge_request_metrics merged_by_id based on the latest merged
# records on events table for a given merge request batch.
def
update_merged_events_for_range
(
min
,
max
)
sql
=
<<-
SQL
.
strip_heredoc
UPDATE merge_request_metrics metrics,
(
#{
select_events
(
min
,
max
,
Event
::
MERGED
)
}
) merged_events
SET metrics.merged_by_id = merged_events.author_id
#{
where_matches_merged_events
}
;
SQL
execute
(
sql
)
end
def
execute
(
sql
)
@connection
||=
ActiveRecord
::
Base
.
connection
@connection
.
execute
(
sql
)
end
def
select_events
(
min
,
max
,
action
)
select_max_event_id
=
<<-
SQL
.
strip_heredoc
SELECT max(id)
FROM events
WHERE action =
#{
action
}
AND target_type = 'MergeRequest'
AND target_id BETWEEN
#{
min
}
AND
#{
max
}
GROUP BY target_id
SQL
<<-
SQL
.
strip_heredoc
SELECT author_id, updated_at, target_id
FROM events
WHERE id IN(
#{
select_max_event_id
}
)
SQL
end
def
where_matches_closed_events
<<-
SQL
.
strip_heredoc
WHERE metrics.merge_request_id = closed_events.target_id
AND metrics.latest_closed_at IS NULL
AND metrics.latest_closed_by_id IS NULL
SQL
end
def
where_matches_merged_events
<<-
SQL
.
strip_heredoc
WHERE metrics.merge_request_id = merged_events.target_id
AND metrics.merged_by_id IS NULL
SQL
end
end
end
end
spec/fixtures/api/schemas/entities/merge_request_metrics.json
0 → 100644
View file @
bf8c2072
{
"type"
:
"object"
,
"required"
:
[
"closed_at"
,
"merged_at"
,
"closed_by"
,
"merged_by"
],
"properties"
:
{
"closed_at"
:
{
"type"
:
[
"datetime"
,
"null"
]
},
"merged_at"
:
{
"type"
:
[
"datetime"
,
"null"
]
},
"closed_by"
:
{
"oneOf"
:
[
{
"type"
:
"null"
},
{
"$ref"
:
"user.json"
}
]
},
"merged_by"
:
{
"oneOf"
:
[
{
"type"
:
"null"
},
{
"$ref"
:
"user.json"
}
]
}
},
"additionalProperties"
:
false
}
spec/fixtures/api/schemas/entities/merge_request_widget.json
View file @
bf8c2072
...
@@ -31,8 +31,10 @@
...
@@ -31,8 +31,10 @@
"source_project_id"
:
{
"type"
:
"integer"
},
"source_project_id"
:
{
"type"
:
"integer"
},
"target_branch"
:
{
"type"
:
"string"
},
"target_branch"
:
{
"type"
:
"string"
},
"target_project_id"
:
{
"type"
:
"integer"
},
"target_project_id"
:
{
"type"
:
"integer"
},
"merge_event"
:
{
"type"
:
[
"object"
,
"null"
]
},
"metrics"
:
{
"closed_event"
:
{
"type"
:
[
"object"
,
"null"
]
},
"type"
:
"object"
,
"$ref"
:
"merge_request_metrics.json"
},
"author"
:
{
"type"
:
[
"object"
,
"null"
]
},
"author"
:
{
"type"
:
[
"object"
,
"null"
]
},
"merge_user"
:
{
"type"
:
[
"object"
,
"null"
]
},
"merge_user"
:
{
"type"
:
[
"object"
,
"null"
]
},
"diff_head_sha"
:
{
"type"
:
[
"string"
,
"null"
]
},
"diff_head_sha"
:
{
"type"
:
[
"string"
,
"null"
]
},
...
...
spec/fixtures/api/schemas/entities/user.json
0 → 100644
View file @
bf8c2072
{
"type"
:
"object"
,
"required"
:
[
"id"
,
"state"
,
"avatar_url"
,
"web_url"
,
"path"
],
"properties"
:
{
"id"
:
{
"type"
:
"integer"
},
"state"
:
{
"type"
:
"string"
},
"avatar_url"
:
{
"type"
:
"string"
},
"web_url"
:
{
"type"
:
"string"
},
"path"
:
{
"type"
:
"string"
}
}
}
spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
View file @
bf8c2072
...
@@ -4,13 +4,16 @@ import closedComponent from '~/vue_merge_request_widget/components/states/mr_wid
...
@@ -4,13 +4,16 @@ import closedComponent from '~/vue_merge_request_widget/components/states/mr_wid
const
mr
=
{
const
mr
=
{
targetBranch
:
'
good-branch
'
,
targetBranch
:
'
good-branch
'
,
targetBranchPath
:
'
/good-branch
'
,
targetBranchPath
:
'
/good-branch
'
,
closedEvent
:
{
metrics
:
{
author
:
{
mergedBy
:
{},
mergedAt
:
'
mergedUpdatedAt
'
,
closedBy
:
{
name
:
'
Fatih Acet
'
,
name
:
'
Fatih Acet
'
,
username
:
'
fatihacet
'
,
username
:
'
fatihacet
'
,
},
},
updatedAt
:
'
closedEventUpdatedAt
'
,
closedAt
:
'
closedEventUpdatedAt
'
,
formattedUpdatedAt
:
''
,
readableMergedAt
:
''
,
readableClosedAt
:
''
,
},
},
updatedAt
:
'
mrUpdatedAt
'
,
updatedAt
:
'
mrUpdatedAt
'
,
closedAt
:
'
1 day ago
'
,
closedAt
:
'
1 day ago
'
,
...
@@ -56,7 +59,7 @@ describe('MRWidgetClosed', () => {
...
@@ -56,7 +59,7 @@ describe('MRWidgetClosed', () => {
it
(
'
should have correct elements
'
,
()
=>
{
it
(
'
should have correct elements
'
,
()
=>
{
expect
(
el
.
querySelector
(
'
h4
'
).
textContent
).
toContain
(
'
Closed by
'
);
expect
(
el
.
querySelector
(
'
h4
'
).
textContent
).
toContain
(
'
Closed by
'
);
expect
(
el
.
querySelector
(
'
h4
'
).
textContent
).
toContain
(
mr
.
closedEvent
.
author
.
name
);
expect
(
el
.
querySelector
(
'
h4
'
).
textContent
).
toContain
(
mr
.
metrics
.
closedBy
.
name
);
expect
(
el
.
textContent
).
toContain
(
'
The changes were not merged into
'
);
expect
(
el
.
textContent
).
toContain
(
'
The changes were not merged into
'
);
expect
(
el
.
querySelector
(
'
.label-branch
'
).
getAttribute
(
'
href
'
)).
toEqual
(
mr
.
targetBranchPath
);
expect
(
el
.
querySelector
(
'
.label-branch
'
).
getAttribute
(
'
href
'
)).
toEqual
(
mr
.
targetBranchPath
);
expect
(
el
.
querySelector
(
'
.label-branch
'
).
textContent
).
toContain
(
mr
.
targetBranch
);
expect
(
el
.
querySelector
(
'
.label-branch
'
).
textContent
).
toContain
(
mr
.
targetBranch
);
...
...
spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
View file @
bf8c2072
...
@@ -14,10 +14,13 @@ const createComponent = () => {
...
@@ -14,10 +14,13 @@ const createComponent = () => {
canRevertInCurrentMR
:
true
,
canRevertInCurrentMR
:
true
,
canRemoveSourceBranch
:
true
,
canRemoveSourceBranch
:
true
,
sourceBranchRemoved
:
true
,
sourceBranchRemoved
:
true
,
mergedEvent
:
{
metrics
:
{
author
:
{},
mergedBy
:
{},
updatedAt
:
'
mergedUpdatedAt
'
,
mergedAt
:
'
mergedUpdatedAt
'
,
formattedUpdatedAt
:
''
,
readableMergedAt
:
''
,
closedBy
:
{},
closedAt
:
'
mergedUpdatedAt
'
,
readableClosedAt
:
''
,
},
},
updatedAt
:
'
mrUpdatedAt
'
,
updatedAt
:
'
mrUpdatedAt
'
,
targetBranch
,
targetBranch
,
...
...
spec/javascripts/vue_mr_widget/mock_data.js
View file @
bf8c2072
...
@@ -33,8 +33,8 @@ export default {
...
@@ -33,8 +33,8 @@ export default {
"
source_project_id
"
:
19
,
"
source_project_id
"
:
19
,
"
target_branch
"
:
"
master
"
,
"
target_branch
"
:
"
master
"
,
"
target_project_id
"
:
19
,
"
target_project_id
"
:
19
,
"
me
rge_event
"
:
{
"
me
trics
"
:
{
"
author
"
:
{
"
merged_by
"
:
{
"
name
"
:
"
Administrator
"
,
"
name
"
:
"
Administrator
"
,
"
username
"
:
"
root
"
,
"
username
"
:
"
root
"
,
"
id
"
:
1
,
"
id
"
:
1
,
...
@@ -42,9 +42,10 @@ export default {
...
@@ -42,9 +42,10 @@ export default {
"
avatar_url
"
:
"
http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon
"
,
"
avatar_url
"
:
"
http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon
"
,
"
web_url
"
:
"
http://localhost:3000/root
"
"
web_url
"
:
"
http://localhost:3000/root
"
},
},
"
updated_at
"
:
"
2017-04-07T15:39:25.696Z
"
"
merged_at
"
:
"
2017-04-07T15:39:25.696Z
"
,
"
closed_by
"
:
null
,
"
closed_at
"
:
null
},
},
"
closed_event
"
:
null
,
"
author
"
:
{
"
author
"
:
{
"
name
"
:
"
Administrator
"
,
"
name
"
:
"
Administrator
"
,
"
username
"
:
"
root
"
,
"
username
"
:
"
root
"
,
...
...
spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
0 → 100644
View file @
bf8c2072
require
'rails_helper'
describe
Gitlab
::
BackgroundMigration
::
PopulateMergeRequestMetricsWithEventsData
,
:migration
,
schema:
20171128214150
do
describe
'#perform'
do
let
(
:mr_with_event
)
{
create
(
:merge_request
)
}
let!
(
:merged_event
)
{
create
(
:event
,
:merged
,
target:
mr_with_event
)
}
let!
(
:closed_event
)
{
create
(
:event
,
:closed
,
target:
mr_with_event
)
}
before
do
# Make sure no metrics are created and kept through after_* callbacks.
mr_with_event
.
metrics
.
destroy!
end
it
'inserts metrics and updates closed and merged events'
do
subject
.
perform
(
mr_with_event
.
id
,
mr_with_event
.
id
)
mr_with_event
.
reload
expect
(
mr_with_event
.
metrics
).
to
have_attributes
(
latest_closed_by_id:
closed_event
.
author_id
,
merged_by_id:
merged_event
.
author_id
)
expect
(
mr_with_event
.
metrics
.
latest_closed_at
.
to_s
).
to
eq
(
closed_event
.
updated_at
.
to_s
)
end
end
describe
'#insert_metrics_for_range'
do
let!
(
:mrs_without_metrics
)
{
create_list
(
:merge_request
,
3
)
}
let!
(
:mrs_with_metrics
)
{
create_list
(
:merge_request
,
2
)
}
before
do
# Make sure no metrics are created and kept through after_* callbacks.
mrs_without_metrics
.
each
{
|
m
|
m
.
metrics
.
destroy!
}
end
it
'inserts merge_request_metrics for merge_requests without one'
do
expect
{
subject
.
insert_metrics_for_range
(
MergeRequest
.
first
.
id
,
MergeRequest
.
last
.
id
)
}
.
to
change
(
MergeRequest
::
Metrics
,
:count
).
from
(
2
).
to
(
5
)
mrs_without_metrics
.
each
do
|
mr_without_metrics
|
expect
(
mr_without_metrics
.
reload
.
metrics
).
to
be_present
end
end
it
'does not inserts merge_request_metrics for MRs out of given range'
do
expect
{
subject
.
insert_metrics_for_range
(
mrs_with_metrics
.
first
.
id
,
mrs_with_metrics
.
last
.
id
)
}
.
not_to
change
(
MergeRequest
::
Metrics
,
:count
).
from
(
2
)
end
end
describe
'#update_metrics_with_events_data'
do
context
'closed events data update'
do
let
(
:users
)
{
create_list
(
:user
,
3
)
}
let
(
:mrs_with_event
)
{
create_list
(
:merge_request
,
3
)
}
before
do
create_list
(
:event
,
2
,
:closed
,
author:
users
.
first
,
target:
mrs_with_event
.
first
)
create_list
(
:event
,
3
,
:closed
,
author:
users
.
second
,
target:
mrs_with_event
.
second
)
create
(
:event
,
:closed
,
author:
users
.
third
,
target:
mrs_with_event
.
third
)
end
it
'migrates multiple MR metrics with closed event data'
do
mr_without_event
=
create
(
:merge_request
)
create
(
:event
,
:merged
)
subject
.
update_metrics_with_events_data
(
mrs_with_event
.
first
.
id
,
mrs_with_event
.
last
.
id
)
mrs_with_event
.
each
do
|
mr_with_event
|
latest_event
=
Event
.
where
(
action:
3
,
target:
mr_with_event
).
last
mr_with_event
.
metrics
.
reload
expect
(
mr_with_event
.
metrics
.
latest_closed_by
).
to
eq
(
latest_event
.
author
)
expect
(
mr_with_event
.
metrics
.
latest_closed_at
.
to_s
).
to
eq
(
latest_event
.
updated_at
.
to_s
)
end
expect
(
mr_without_event
.
metrics
.
reload
).
to
have_attributes
(
latest_closed_by_id:
nil
,
latest_closed_at:
nil
)
end
it
'does not updates metrics out of given range'
do
out_of_range_mr
=
create
(
:merge_request
)
create
(
:event
,
:closed
,
author:
users
.
last
,
target:
out_of_range_mr
)
expect
{
subject
.
perform
(
mrs_with_event
.
first
.
id
,
mrs_with_event
.
second
.
id
)
}
.
not_to
change
{
out_of_range_mr
.
metrics
.
reload
.
merged_by
}
.
from
(
nil
)
end
end
context
'merged events data update'
do
let
(
:users
)
{
create_list
(
:user
,
3
)
}
let
(
:mrs_with_event
)
{
create_list
(
:merge_request
,
3
)
}
before
do
create_list
(
:event
,
2
,
:merged
,
author:
users
.
first
,
target:
mrs_with_event
.
first
)
create_list
(
:event
,
3
,
:merged
,
author:
users
.
second
,
target:
mrs_with_event
.
second
)
create
(
:event
,
:merged
,
author:
users
.
third
,
target:
mrs_with_event
.
third
)
end
it
'migrates multiple MR metrics with merged event data'
do
mr_without_event
=
create
(
:merge_request
)
create
(
:event
,
:merged
)
subject
.
update_metrics_with_events_data
(
mrs_with_event
.
first
.
id
,
mrs_with_event
.
last
.
id
)
mrs_with_event
.
each
do
|
mr_with_event
|
latest_event
=
Event
.
where
(
action:
Event
::
MERGED
,
target:
mr_with_event
).
last
expect
(
mr_with_event
.
metrics
.
reload
.
merged_by
).
to
eq
(
latest_event
.
author
)
end
expect
(
mr_without_event
.
metrics
.
reload
).
to
have_attributes
(
merged_by_id:
nil
)
end
it
'does not updates metrics out of given range'
do
out_of_range_mr
=
create
(
:merge_request
)
create
(
:event
,
:merged
,
author:
users
.
last
,
target:
out_of_range_mr
)
expect
{
subject
.
perform
(
mrs_with_event
.
first
.
id
,
mrs_with_event
.
second
.
id
)
}
.
not_to
change
{
out_of_range_mr
.
metrics
.
reload
.
merged_by
}
.
from
(
nil
)
end
end
end
end
spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb
0 → 100644
View file @
bf8c2072
require
'spec_helper'
require
Rails
.
root
.
join
(
'db'
,
'post_migrate'
,
'20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb'
)
describe
SchedulePopulateMergeRequestMetricsWithEventsData
,
:migration
,
:sidekiq
do
let!
(
:mrs
)
{
create_list
(
:merge_request
,
3
)
}
it
'correctly schedules background migrations'
do
stub_const
(
"
#{
described_class
.
name
}
::BATCH_SIZE"
,
2
)
Sidekiq
::
Testing
.
fake!
do
Timecop
.
freeze
do
migrate!
expect
(
described_class
::
MIGRATION
)
.
to
be_scheduled_migration
(
10
.
minutes
,
mrs
.
first
.
id
,
mrs
.
second
.
id
)
expect
(
described_class
::
MIGRATION
)
.
to
be_scheduled_migration
(
20
.
minutes
,
mrs
.
third
.
id
,
mrs
.
third
.
id
)
expect
(
BackgroundMigrationWorker
.
jobs
.
size
).
to
eq
(
2
)
end
end
end
end
spec/models/issue_spec.rb
View file @
bf8c2072
...
@@ -23,6 +23,32 @@ describe Issue do
...
@@ -23,6 +23,32 @@ describe Issue do
it
{
is_expected
.
to
have_db_index
(
:deleted_at
)
}
it
{
is_expected
.
to
have_db_index
(
:deleted_at
)
}
end
end
describe
'callbacks'
do
describe
'#ensure_metrics'
do
it
'creates metrics after saving'
do
issue
=
create
(
:issue
)
expect
(
issue
.
metrics
).
to
be_persisted
expect
(
Issue
::
Metrics
.
count
).
to
eq
(
1
)
end
it
'does not create duplicate metrics for an issue'
do
issue
=
create
(
:issue
)
issue
.
close!
expect
(
issue
.
metrics
).
to
be_persisted
expect
(
Issue
::
Metrics
.
count
).
to
eq
(
1
)
end
it
'records current metrics'
do
expect_any_instance_of
(
Issue
::
Metrics
).
to
receive
(
:record!
)
create
(
:issue
)
end
end
end
describe
'#order_by_position_and_priority'
do
describe
'#order_by_position_and_priority'
do
let
(
:project
)
{
create
:project
}
let
(
:project
)
{
create
:project
}
let
(
:p1
)
{
create
(
:label
,
title:
'P1'
,
project:
project
,
priority:
1
)
}
let
(
:p1
)
{
create
(
:label
,
title:
'P1'
,
project:
project
,
priority:
1
)
}
...
...
spec/models/merge_request/metrics_spec.rb
View file @
bf8c2072
require
'spec_helper'
require
'spec_helper'
describe
MergeRequest
::
Metrics
do
describe
MergeRequest
::
Metrics
do
subject
{
create
(
:merge_request
)
}
subject
{
described_class
.
new
}
describe
"when recording the default set of metrics on merge request save"
do
describe
'associations'
do
it
"records the merge time"
do
it
{
is_expected
.
to
belong_to
(
:merge_request
)
}
time
=
Time
.
now
it
{
is_expected
.
to
belong_to
(
:latest_closed_by
).
class_name
(
'User'
)
}
Timecop
.
freeze
(
time
)
{
subject
.
mark_as_merged
}
it
{
is_expected
.
to
belong_to
(
:merged_by
).
class_name
(
'User'
)
}
metrics
=
subject
.
metrics
expect
(
metrics
).
to
be_present
expect
(
metrics
.
merged_at
).
to
be_like_time
(
time
)
end
end
end
end
end
spec/models/merge_request_spec.rb
View file @
bf8c2072
...
@@ -65,6 +65,25 @@ describe MergeRequest do
...
@@ -65,6 +65,25 @@ describe MergeRequest do
end
end
end
end
describe
'callbacks'
do
describe
'#ensure_merge_request_metrics'
do
it
'creates metrics after saving'
do
merge_request
=
create
(
:merge_request
)
expect
(
merge_request
.
metrics
).
to
be_persisted
expect
(
MergeRequest
::
Metrics
.
count
).
to
eq
(
1
)
end
it
'does not duplicate metrics for a merge request'
do
merge_request
=
create
(
:merge_request
)
merge_request
.
mark_as_merged!
expect
(
MergeRequest
::
Metrics
.
count
).
to
eq
(
1
)
end
end
end
describe
'respond to'
do
describe
'respond to'
do
it
{
is_expected
.
to
respond_to
(
:unchecked?
)
}
it
{
is_expected
.
to
respond_to
(
:unchecked?
)
}
it
{
is_expected
.
to
respond_to
(
:can_be_merged?
)
}
it
{
is_expected
.
to
respond_to
(
:can_be_merged?
)
}
...
...
spec/requests/api/notes_spec.rb
View file @
bf8c2072
...
@@ -464,7 +464,7 @@ describe API::Notes do
...
@@ -464,7 +464,7 @@ describe API::Notes do
describe
"POST /projects/:id/noteable/:noteable_id/notes to test observer on create"
do
describe
"POST /projects/:id/noteable/:noteable_id/notes to test observer on create"
do
it
"creates an activity event when an issue note is created"
do
it
"creates an activity event when an issue note is created"
do
expect
(
Event
).
to
receive
(
:create
)
expect
(
Event
).
to
receive
(
:create
!
)
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
iid
}
/notes"
,
user
),
body:
'hi!'
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
iid
}
/notes"
,
user
),
body:
'hi!'
end
end
...
...
spec/requests/api/project_milestones_spec.rb
View file @
bf8c2072
...
@@ -16,7 +16,7 @@ describe API::ProjectMilestones do
...
@@ -16,7 +16,7 @@ describe API::ProjectMilestones do
describe
'PUT /projects/:id/milestones/:milestone_id to test observer on close'
do
describe
'PUT /projects/:id/milestones/:milestone_id to test observer on close'
do
it
'creates an activity event when an milestone is closed'
do
it
'creates an activity event when an milestone is closed'
do
expect
(
Event
).
to
receive
(
:create
)
expect
(
Event
).
to
receive
(
:create
!
)
put
api
(
"/projects/
#{
project
.
id
}
/milestones/
#{
milestone
.
id
}
"
,
user
),
put
api
(
"/projects/
#{
project
.
id
}
/milestones/
#{
milestone
.
id
}
"
,
user
),
state_event:
'close'
state_event:
'close'
...
...
spec/requests/api/v3/milestones_spec.rb
View file @
bf8c2072
...
@@ -161,7 +161,7 @@ describe API::V3::Milestones do
...
@@ -161,7 +161,7 @@ describe API::V3::Milestones do
describe
'PUT /projects/:id/milestones/:milestone_id to test observer on close'
do
describe
'PUT /projects/:id/milestones/:milestone_id to test observer on close'
do
it
'creates an activity event when an milestone is closed'
do
it
'creates an activity event when an milestone is closed'
do
expect
(
Event
).
to
receive
(
:create
)
expect
(
Event
).
to
receive
(
:create
!
)
put
v3_api
(
"/projects/
#{
project
.
id
}
/milestones/
#{
milestone
.
id
}
"
,
user
),
put
v3_api
(
"/projects/
#{
project
.
id
}
/milestones/
#{
milestone
.
id
}
"
,
user
),
state_event:
'close'
state_event:
'close'
...
...
spec/requests/api/v3/notes_spec.rb
View file @
bf8c2072
...
@@ -302,7 +302,7 @@ describe API::V3::Notes do
...
@@ -302,7 +302,7 @@ describe API::V3::Notes do
describe
"POST /projects/:id/noteable/:noteable_id/notes to test observer on create"
do
describe
"POST /projects/:id/noteable/:noteable_id/notes to test observer on create"
do
it
"creates an activity event when an issue note is created"
do
it
"creates an activity event when an issue note is created"
do
expect
(
Event
).
to
receive
(
:create
)
expect
(
Event
).
to
receive
(
:create
!
)
post
v3_api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
id
}
/notes"
,
user
),
body:
'hi!'
post
v3_api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
id
}
/notes"
,
user
),
body:
'hi!'
end
end
...
...
spec/serializers/event_entity_spec.rb
deleted
100644 → 0
View file @
2cbb2d0e
require
'spec_helper'
describe
EventEntity
do
subject
{
described_class
.
represent
(
create
(
:event
)).
as_json
}
it
'exposes author'
do
expect
(
subject
).
to
include
(
:author
)
end
it
'exposes core elements of event'
do
expect
(
subject
).
to
include
(
:updated_at
)
end
end
spec/serializers/merge_request_widget_entity_spec.rb
View file @
bf8c2072
...
@@ -35,6 +35,80 @@ describe MergeRequestWidgetEntity do
...
@@ -35,6 +35,80 @@ describe MergeRequestWidgetEntity do
end
end
end
end
describe
'metrics'
do
context
'when metrics record exists with merged data'
do
before
do
resource
.
mark_as_merged!
resource
.
metrics
.
update!
(
merged_by:
user
)
end
it
'matches merge request metrics schema'
do
expect
(
subject
[
:metrics
].
with_indifferent_access
)
.
to
match_schema
(
'entities/merge_request_metrics'
)
end
it
'returns values from metrics record'
do
expect
(
subject
.
dig
(
:metrics
,
:merged_by
,
:id
))
.
to
eq
(
resource
.
metrics
.
merged_by_id
)
end
end
context
'when metrics record exists with closed data'
do
before
do
resource
.
close!
resource
.
metrics
.
update!
(
latest_closed_by:
user
)
end
it
'matches merge request metrics schema'
do
expect
(
subject
[
:metrics
].
with_indifferent_access
)
.
to
match_schema
(
'entities/merge_request_metrics'
)
end
it
'returns values from metrics record'
do
expect
(
subject
.
dig
(
:metrics
,
:closed_by
,
:id
))
.
to
eq
(
resource
.
metrics
.
latest_closed_by_id
)
end
end
context
'when metrics does not exists'
do
before
do
resource
.
metrics
.
destroy!
resource
.
reload
end
context
'when events exists'
do
let!
(
:closed_event
)
{
create
(
:event
,
:closed
,
project:
project
,
target:
resource
)
}
let!
(
:merge_event
)
{
create
(
:event
,
:merged
,
project:
project
,
target:
resource
)
}
it
'matches merge request metrics schema'
do
expect
(
subject
[
:metrics
].
with_indifferent_access
)
.
to
match_schema
(
'entities/merge_request_metrics'
)
end
it
'returns values from events record'
do
expect
(
subject
.
dig
(
:metrics
,
:merged_by
,
:id
))
.
to
eq
(
merge_event
.
author_id
)
expect
(
subject
.
dig
(
:metrics
,
:closed_by
,
:id
))
.
to
eq
(
closed_event
.
author_id
)
expect
(
subject
.
dig
(
:metrics
,
:merged_at
).
to_s
)
.
to
eq
(
merge_event
.
updated_at
.
to_s
)
expect
(
subject
.
dig
(
:metrics
,
:closed_at
).
to_s
)
.
to
eq
(
closed_event
.
updated_at
.
to_s
)
end
end
context
'when events does not exists'
do
it
'matches merge request metrics schema'
do
expect
(
subject
[
:metrics
].
with_indifferent_access
)
.
to
match_schema
(
'entities/merge_request_metrics'
)
end
end
end
end
it
'has email_patches_path'
do
it
'has email_patches_path'
do
expect
(
subject
[
:email_patches_path
])
expect
(
subject
[
:email_patches_path
])
.
to
eq
(
"/
#{
resource
.
project
.
full_path
}
/merge_requests/
#{
resource
.
iid
}
.patch"
)
.
to
eq
(
"/
#{
resource
.
project
.
full_path
}
/merge_requests/
#{
resource
.
iid
}
.patch"
)
...
...
spec/services/create_deployment_service_spec.rb
View file @
bf8c2072
...
@@ -266,7 +266,7 @@ describe CreateDeploymentService do
...
@@ -266,7 +266,7 @@ describe CreateDeploymentService do
context
"while updating the 'first_deployed_to_production_at' time"
do
context
"while updating the 'first_deployed_to_production_at' time"
do
before
do
before
do
merge_request
.
m
ark_as_merged
merge_request
.
m
etrics
.
update!
(
merged_at:
Time
.
now
)
end
end
context
"for merge requests merged before the current deploy"
do
context
"for merge requests merged before the current deploy"
do
...
...
spec/services/merge_requests/close_service_spec.rb
View file @
bf8c2072
...
@@ -52,6 +52,19 @@ describe MergeRequests::CloseService do
...
@@ -52,6 +52,19 @@ describe MergeRequests::CloseService do
end
end
end
end
it
'updates metrics'
do
metrics
=
merge_request
.
metrics
metrics_service
=
double
(
MergeRequestMetricsService
)
allow
(
MergeRequestMetricsService
)
.
to
receive
(
:new
)
.
with
(
metrics
)
.
and_return
(
metrics_service
)
expect
(
metrics_service
).
to
receive
(
:close
)
described_class
.
new
(
project
,
user
,
{}).
execute
(
merge_request
)
end
it
'refreshes the number of open merge requests for a valid MR'
,
:use_clean_rails_memory_store_caching
do
it
'refreshes the number of open merge requests for a valid MR'
,
:use_clean_rails_memory_store_caching
do
service
=
described_class
.
new
(
project
,
user
,
{})
service
=
described_class
.
new
(
project
,
user
,
{})
...
...
spec/services/merge_requests/post_merge_service_spec.rb
View file @
bf8c2072
...
@@ -22,5 +22,18 @@ describe MergeRequests::PostMergeService do
...
@@ -22,5 +22,18 @@ describe MergeRequests::PostMergeService do
expect
{
service
.
execute
(
merge_request
)
}
expect
{
service
.
execute
(
merge_request
)
}
.
to
change
{
project
.
open_merge_requests_count
}.
from
(
1
).
to
(
0
)
.
to
change
{
project
.
open_merge_requests_count
}.
from
(
1
).
to
(
0
)
end
end
it
'updates metrics'
do
metrics
=
merge_request
.
metrics
metrics_service
=
double
(
MergeRequestMetricsService
)
allow
(
MergeRequestMetricsService
)
.
to
receive
(
:new
)
.
with
(
metrics
)
.
and_return
(
metrics_service
)
expect
(
metrics_service
).
to
receive
(
:merge
)
described_class
.
new
(
project
,
user
,
{}).
execute
(
merge_request
)
end
end
end
end
end
spec/services/merge_requests/reopen_service_spec.rb
View file @
bf8c2072
...
@@ -47,6 +47,19 @@ describe MergeRequests::ReopenService do
...
@@ -47,6 +47,19 @@ describe MergeRequests::ReopenService do
end
end
end
end
it
'updates metrics'
do
metrics
=
merge_request
.
metrics
service
=
double
(
MergeRequestMetricsService
)
allow
(
MergeRequestMetricsService
)
.
to
receive
(
:new
)
.
with
(
metrics
)
.
and_return
(
service
)
expect
(
service
).
to
receive
(
:reopen
)
described_class
.
new
(
project
,
user
,
{}).
execute
(
merge_request
)
end
it
'refreshes the number of open merge requests for a valid MR'
do
it
'refreshes the number of open merge requests for a valid MR'
do
service
=
described_class
.
new
(
project
,
user
,
{})
service
=
described_class
.
new
(
project
,
user
,
{})
...
...
spec/services/update_merge_request_metrics_service_spec.rb
0 → 100644
View file @
bf8c2072
require
'rails_helper'
describe
MergeRequestMetricsService
do
let
(
:metrics
)
{
create
(
:merge_request
).
metrics
}
describe
'#merge'
do
it
'updates metrics'
do
user
=
create
(
:user
)
service
=
described_class
.
new
(
metrics
)
event
=
double
(
Event
,
author_id:
user
.
id
,
created_at:
Time
.
now
)
service
.
merge
(
event
)
expect
(
metrics
.
merged_by
).
to
eq
(
user
)
expect
(
metrics
.
merged_at
).
to
eq
(
event
.
created_at
)
end
end
describe
'#close'
do
it
'updates metrics'
do
user
=
create
(
:user
)
service
=
described_class
.
new
(
metrics
)
event
=
double
(
Event
,
author_id:
user
.
id
,
created_at:
Time
.
now
)
service
.
close
(
event
)
expect
(
metrics
.
latest_closed_by
).
to
eq
(
user
)
expect
(
metrics
.
latest_closed_at
).
to
eq
(
event
.
created_at
)
end
end
describe
'#reopen'
do
it
'updates metrics'
do
service
=
described_class
.
new
(
metrics
)
service
.
reopen
expect
(
metrics
.
latest_closed_by
).
to
be_nil
expect
(
metrics
.
latest_closed_at
).
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