Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
李少辉-开发者
gitlab-foss
提交
bf8c2072
G
gitlab-foss
项目概览
李少辉-开发者
/
gitlab-foss
通知
15
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
gitlab-foss
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
bf8c2072
编写于
12月 07, 2017
作者:
O
Oswaldo Ferreira
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Cache merged and closed events data in merge_request_metrics table
上级
2cbb2d0e
变更
42
隐藏空白更改
内联
并排
Showing
42 changed file
with
799 addition
and
98 deletion
+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
未找到文件。
app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
已删除
100644 → 0
浏览文件 @
2cbb2d0e
class
EventEntity
<
Grape
::
Entity
expose
:author
,
using:
UserEntity
expose
:updated_at
end
app/serializers/merge_request_metrics_entity.rb
0 → 100644
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
已删除
100644 → 0
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
浏览文件 @
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
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录