Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
李少辉-开发者
gitlab-foss
提交
de784ac1
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,发现更多精彩内容 >>
提交
de784ac1
编写于
3月 09, 2019
作者:
H
Hiroyuki Sato
提交者:
Nick Thomas
3月 09, 2019
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Filter merge requests by target branch
上级
6908c5f7
变更
17
显示空白变更内容
内联
并排
Showing
17 changed file
with
204 addition
and
16 deletion
+204
-16
app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js
...ts/filtered_search/add_extra_tokens_for_merge_requests.js
+12
-0
app/assets/javascripts/filtered_search/available_dropdown_mappings.js
...avascripts/filtered_search/available_dropdown_mappings.js
+31
-0
app/assets/javascripts/filtered_search/filtered_search_manager.js
...ts/javascripts/filtered_search/filtered_search_manager.js
+3
-10
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
...ascripts/filtered_search/filtered_search_visual_tokens.js
+11
-1
app/assets/stylesheets/framework/filters.scss
app/assets/stylesheets/framework/filters.scss
+10
-1
app/controllers/autocomplete_controller.rb
app/controllers/autocomplete_controller.rb
+8
-1
app/finders/merge_requests_finder.rb
app/finders/merge_requests_finder.rb
+1
-1
app/models/merge_request.rb
app/models/merge_request.rb
+16
-0
app/views/shared/issuable/_search_bar.html.haml
app/views/shared/issuable/_search_bar.html.haml
+5
-0
changelogs/unreleased/filter-merge-requests-by-target-branch.yml
...ogs/unreleased/filter-merge-requests-by-target-branch.yml
+5
-0
config/routes.rb
config/routes.rb
+1
-0
spec/controllers/autocomplete_controller_spec.rb
spec/controllers/autocomplete_controller_spec.rb
+31
-0
spec/features/merge_requests/user_filters_by_target_branch_spec.rb
...ures/merge_requests/user_filters_by_target_branch_spec.rb
+45
-0
spec/finders/merge_requests_finder_spec.rb
spec/finders/merge_requests_finder_spec.rb
+1
-1
spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
...pts/filtered_search/filtered_search_visual_tokens_spec.js
+4
-0
spec/javascripts/helpers/filtered_search_spec_helper.js
spec/javascripts/helpers/filtered_search_spec_helper.js
+1
-1
spec/models/merge_request_spec.rb
spec/models/merge_request_spec.rb
+19
-0
未找到文件。
app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js
浏览文件 @
de784ac1
...
...
@@ -13,4 +13,16 @@ export default IssuableTokenKeys => {
IssuableTokenKeys
.
tokenKeys
.
push
(
wipToken
);
IssuableTokenKeys
.
tokenKeysWithAlternative
.
push
(
wipToken
);
const
targetBranchToken
=
{
key
:
'
target-branch
'
,
type
:
'
string
'
,
param
:
''
,
symbol
:
''
,
icon
:
'
arrow-right
'
,
tag
:
'
branch
'
,
};
IssuableTokenKeys
.
tokenKeys
.
push
(
targetBranchToken
);
IssuableTokenKeys
.
tokenKeysWithAlternative
.
push
(
targetBranchToken
);
};
app/assets/javascripts/filtered_search/available_dropdown_mappings.js
浏览文件 @
de784ac1
...
...
@@ -5,6 +5,7 @@ import DropdownEmoji from './dropdown_emoji';
import
NullDropdown
from
'
./null_dropdown
'
;
import
DropdownAjaxFilter
from
'
./dropdown_ajax_filter
'
;
import
DropdownUtils
from
'
./dropdown_utils
'
;
import
{
mergeUrlParams
}
from
'
../lib/utils/url_utility
'
;
export
default
class
AvailableDropdownMappings
{
constructor
(
container
,
baseEndpoint
,
groupsOnly
,
includeAncestorGroups
,
includeDescendantGroups
)
{
...
...
@@ -13,6 +14,7 @@ export default class AvailableDropdownMappings {
this
.
groupsOnly
=
groupsOnly
;
this
.
includeAncestorGroups
=
includeAncestorGroups
;
this
.
includeDescendantGroups
=
includeDescendantGroups
;
this
.
filteredSearchInput
=
this
.
container
.
querySelector
(
'
.filtered-search
'
);
}
getAllowedMappings
(
supportedTokens
)
{
...
...
@@ -102,6 +104,15 @@ export default class AvailableDropdownMappings {
},
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-runner-tag
'
),
},
'
target-branch
'
:
{
reference
:
null
,
gl
:
DropdownNonUser
,
extraArguments
:
{
endpoint
:
this
.
getMergeRequestTargetBranchesEndpoint
(),
symbol
:
''
,
},
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-target-branch
'
),
},
};
}
...
...
@@ -130,4 +141,24 @@ export default class AvailableDropdownMappings {
getRunnerTagsEndpoint
()
{
return
`
${
this
.
baseEndpoint
}
/admin/runners/tag_list.json`
;
}
getMergeRequestTargetBranchesEndpoint
()
{
const
endpoint
=
`
${
gon
.
relative_url_root
||
''
}
/autocomplete/merge_request_target_branches.json`
;
const
params
=
{
group_id
:
this
.
getGroupId
(),
project_id
:
this
.
getProjectId
(),
};
return
mergeUrlParams
(
params
,
endpoint
);
}
getGroupId
()
{
return
this
.
filteredSearchInput
.
getAttribute
(
'
data-group-id
'
)
||
''
;
}
getProjectId
()
{
return
this
.
filteredSearchInput
.
getAttribute
(
'
data-project-id
'
)
||
''
;
}
}
app/assets/javascripts/filtered_search/filtered_search_manager.js
浏览文件 @
de784ac1
...
...
@@ -504,14 +504,7 @@ export default class FilteredSearchManager {
const
match
=
this
.
filteredSearchTokenKeys
.
searchByKeyParam
(
keyParam
);
if
(
match
)
{
// Use lastIndexOf because the token key is allowed to contain underscore
// e.g. 'my_reaction' is the token key of 'my_reaction_emoji'
const
lastIndexOf
=
keyParam
.
lastIndexOf
(
'
_
'
);
let
sanitizedKey
=
lastIndexOf
!==
-
1
?
keyParam
.
slice
(
0
,
lastIndexOf
)
:
keyParam
;
// Replace underscore with hyphen in the sanitizedkey.
// e.g. 'my_reaction' => 'my-reaction'
sanitizedKey
=
sanitizedKey
.
replace
(
'
_
'
,
'
-
'
);
const
{
symbol
}
=
match
;
const
{
key
,
symbol
}
=
match
;
let
quotationsToUse
=
''
;
if
(
sanitizedValue
.
indexOf
(
'
'
)
!==
-
1
)
{
...
...
@@ -520,10 +513,10 @@ export default class FilteredSearchManager {
}
hasFilteredSearch
=
true
;
const
canEdit
=
this
.
canEdit
&&
this
.
canEdit
(
sanitizedK
ey
,
sanitizedValue
);
const
canEdit
=
this
.
canEdit
&&
this
.
canEdit
(
k
ey
,
sanitizedValue
);
const
{
uppercaseTokenName
,
capitalizeTokenValue
}
=
match
;
FilteredSearchVisualTokens
.
addFilterVisualToken
(
sanitizedK
ey
,
k
ey
,
`
${
symbol
}${
quotationsToUse
}${
sanitizedValue
}${
quotationsToUse
}
`
,
{
canEdit
,
...
...
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
浏览文件 @
de784ac1
...
...
@@ -69,11 +69,21 @@ export default class FilteredSearchVisualTokens {
}
static
addVisualTokenElement
(
name
,
value
,
options
=
{})
{
const
{
isSearchTerm
=
false
,
canEdit
,
uppercaseTokenName
,
capitalizeTokenValue
}
=
options
;
const
{
isSearchTerm
=
false
,
canEdit
,
uppercaseTokenName
,
capitalizeTokenValue
,
tokenClass
=
`search-token-
${
name
.
toLowerCase
()}
`
,
}
=
options
;
const
li
=
document
.
createElement
(
'
li
'
);
li
.
classList
.
add
(
'
js-visual-token
'
);
li
.
classList
.
add
(
isSearchTerm
?
'
filtered-search-term
'
:
'
filtered-search-token
'
);
if
(
!
isSearchTerm
)
{
li
.
classList
.
add
(
tokenClass
);
}
if
(
value
)
{
li
.
innerHTML
=
FilteredSearchVisualTokens
.
createVisualTokenElementHTML
({
canEdit
,
...
...
app/assets/stylesheets/framework/filters.scss
浏览文件 @
de784ac1
...
...
@@ -108,6 +108,8 @@
}
.value-container
{
display
:
flex
;
align-items
:
center
;
background-color
:
$white-normal
;
color
:
$filter-value-text-color
;
border-radius
:
0
2px
2px
0
;
...
...
@@ -121,7 +123,7 @@
.remove-token
{
display
:
inline-block
;
padding-left
:
4
px
;
padding-left
:
8
px
;
padding-right
:
0
;
.fa-close
{
...
...
@@ -412,3 +414,10 @@
padding
:
8px
16px
;
text-align
:
center
;
}
.search-token-target-branch
{
.value
{
font-family
:
$monospace-font
;
font-size
:
13px
;
}
}
app/controllers/autocomplete_controller.rb
浏览文件 @
de784ac1
# frozen_string_literal: true
class
AutocompleteController
<
ApplicationController
skip_before_action
:authenticate_user!
,
only:
[
:users
,
:award_emojis
]
skip_before_action
:authenticate_user!
,
only:
[
:users
,
:award_emojis
,
:merge_request_target_branches
]
def
users
project
=
Autocomplete
::
ProjectFinder
...
...
@@ -38,4 +38,11 @@ class AutocompleteController < ApplicationController
def
award_emojis
render
json:
AwardedEmojiFinder
.
new
(
current_user
).
execute
end
def
merge_request_target_branches
merge_requests
=
MergeRequestsFinder
.
new
(
current_user
,
params
).
execute
target_branches
=
merge_requests
.
recent_target_branches
render
json:
target_branches
.
map
{
|
target_branch
|
{
title:
target_branch
}
}
end
end
app/finders/merge_requests_finder.rb
浏览文件 @
de784ac1
...
...
@@ -29,7 +29,7 @@
#
class
MergeRequestsFinder
<
IssuableFinder
def
self
.
scalar_params
@scalar_params
||=
super
+
[
:wip
]
@scalar_params
||=
super
+
[
:wip
,
:target_branch
]
end
def
klass
...
...
app/models/merge_request.rb
浏览文件 @
de784ac1
...
...
@@ -203,6 +203,22 @@ class MergeRequest < ActiveRecord::Base
'!'
end
# Returns the top 100 target branches
#
# The returned value is a Array containing branch names
# sort by updated_at of merge request:
#
# ['master', 'develop', 'production']
#
# limit - The maximum number of target branch to return.
def
self
.
recent_target_branches
(
limit:
100
)
group
(
:target_branch
)
.
select
(
:target_branch
)
.
reorder
(
'MAX(merge_requests.updated_at) DESC'
)
.
limit
(
limit
)
.
pluck
(
:target_branch
)
end
def
rebase_in_progress?
strong_memoize
(
:rebase_in_progress
)
do
# The source project can be deleted
...
...
app/views/shared/issuable/_search_bar.html.haml
浏览文件 @
de784ac1
...
...
@@ -137,6 +137,11 @@
%li
.filter-dropdown-item
{
data:
{
value:
'no'
,
capitalize:
true
}
}
%button
.btn.btn-link
{
type:
'button'
}
=
_
(
'No'
)
#js-dropdown-target-branch
.filtered-search-input-dropdown-menu.dropdown-menu
%ul
.filter-dropdown
{
data:
{
dynamic:
true
,
dropdown:
true
}
}
%li
.filter-dropdown-item
%button
.btn.btn-link.js-data-value.monospace
{{title}}
=
render_if_exists
'shared/issuable/filter_weight'
,
type:
type
...
...
changelogs/unreleased/filter-merge-requests-by-target-branch.yml
0 → 100644
浏览文件 @
de784ac1
---
title
:
Add target branch filter to merge requests search bar
merge_request
:
24380
author
:
Hiroyuki Sato
type
:
added
config/routes.rb
浏览文件 @
de784ac1
...
...
@@ -43,6 +43,7 @@ Rails.application.routes.draw do
get
'/autocomplete/users/:id'
=>
'autocomplete#user'
get
'/autocomplete/projects'
=>
'autocomplete#projects'
get
'/autocomplete/award_emojis'
=>
'autocomplete#award_emojis'
get
'/autocomplete/merge_request_target_branches'
=>
'autocomplete#merge_request_target_branches'
# Search
get
'search'
=>
'search#show'
...
...
spec/controllers/autocomplete_controller_spec.rb
浏览文件 @
de784ac1
...
...
@@ -371,5 +371,36 @@ describe AutocompleteController do
expect
(
json_response
[
3
]).
to
match
(
'name'
=>
'thumbsdown'
)
end
end
context
'Get merge_request_target_branches'
do
let
(
:user2
)
{
create
(
:user
)
}
let!
(
:merge_request1
)
{
create
(
:merge_request
,
source_project:
project
,
target_branch:
'feature'
)
}
context
'unauthorized user'
do
it
'returns empty json'
do
get
:merge_request_target_branches
expect
(
json_response
).
to
be_empty
end
end
context
'sign in as user without any accesible merge requests'
do
it
'returns empty json'
do
sign_in
(
user2
)
get
:merge_request_target_branches
expect
(
json_response
).
to
be_empty
end
end
context
'sign in as user with a accesible merge request'
do
it
'returns json'
do
sign_in
(
user
)
get
:merge_request_target_branches
expect
(
json_response
).
to
contain_exactly
({
'title'
=>
'feature'
})
end
end
end
end
end
spec/features/merge_requests/user_filters_by_target_branch_spec.rb
0 → 100644
浏览文件 @
de784ac1
require
'rails_helper'
describe
'Merge Requests > User filters by target branch'
,
:js
do
include
FilteredSearchHelpers
let!
(
:project
)
{
create
(
:project
,
:public
,
:repository
)
}
let!
(
:user
)
{
project
.
creator
}
let!
(
:mr1
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
,
source_branch:
'feature'
,
target_branch:
'master'
)
}
let!
(
:mr2
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
,
source_branch:
'feature'
,
target_branch:
'merged-target'
)
}
before
do
sign_in
(
user
)
visit
project_merge_requests_path
(
project
)
end
context
'filtering by target-branch:master'
do
it
'applies the filter'
do
input_filtered_search
(
'target-branch:master'
)
expect
(
page
).
to
have_issuable_counts
(
open:
1
,
closed:
0
,
all:
1
)
expect
(
page
).
to
have_content
mr1
.
title
expect
(
page
).
not_to
have_content
mr2
.
title
end
end
context
'filtering by target-branch:merged-target'
do
it
'applies the filter'
do
input_filtered_search
(
'target-branch:merged-target'
)
expect
(
page
).
to
have_issuable_counts
(
open:
1
,
closed:
0
,
all:
1
)
expect
(
page
).
not_to
have_content
mr1
.
title
expect
(
page
).
to
have_content
mr2
.
title
end
end
context
'filtering by target-branch:feature'
do
it
'applies the filter'
do
input_filtered_search
(
'target-branch:feature'
)
expect
(
page
).
to
have_issuable_counts
(
open:
0
,
closed:
0
,
all:
0
)
expect
(
page
).
not_to
have_content
mr1
.
title
expect
(
page
).
not_to
have_content
mr2
.
title
end
end
end
spec/finders/merge_requests_finder_spec.rb
浏览文件 @
de784ac1
...
...
@@ -36,7 +36,7 @@ describe MergeRequestsFinder do
let
(
:project5
)
{
create_project_without_n_plus_1
(
group:
subgroup
)
}
let
(
:project6
)
{
create_project_without_n_plus_1
(
group:
subgroup
)
}
let!
(
:merge_request1
)
{
create
(
:merge_request
,
:simple
,
author:
user
,
source_project:
project2
,
target_project:
project1
)
}
let!
(
:merge_request1
)
{
create
(
:merge_request
,
author:
user
,
source_project:
project2
,
target_project:
project1
,
target_branch:
'merged-target'
)
}
let!
(
:merge_request2
)
{
create
(
:merge_request
,
:conflict
,
author:
user
,
source_project:
project2
,
target_project:
project1
,
state:
'closed'
)
}
let!
(
:merge_request3
)
{
create
(
:merge_request
,
:simple
,
author:
user
,
source_project:
project2
,
target_project:
project2
,
state:
'locked'
,
title:
'thing WIP thing'
)
}
let!
(
:merge_request4
)
{
create
(
:merge_request
,
:simple
,
author:
user
,
source_project:
project3
,
target_project:
project3
,
title:
'WIP thing'
)
}
...
...
spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
浏览文件 @
de784ac1
...
...
@@ -293,6 +293,7 @@ describe('Filtered Search Visual Tokens', () => {
subject
.
addVisualTokenElement
(
'
milestone
'
);
const
token
=
tokensContainer
.
querySelector
(
'
.js-visual-token
'
);
expect
(
token
.
classList
.
contains
(
'
search-token-milestone
'
)).
toEqual
(
true
);
expect
(
token
.
classList
.
contains
(
'
filtered-search-token
'
)).
toEqual
(
true
);
expect
(
token
.
querySelector
(
'
.name
'
).
innerText
).
toEqual
(
'
milestone
'
);
expect
(
token
.
querySelector
(
'
.value
'
)).
toEqual
(
null
);
...
...
@@ -302,6 +303,7 @@ describe('Filtered Search Visual Tokens', () => {
subject
.
addVisualTokenElement
(
'
label
'
,
'
Frontend
'
);
const
token
=
tokensContainer
.
querySelector
(
'
.js-visual-token
'
);
expect
(
token
.
classList
.
contains
(
'
search-token-label
'
)).
toEqual
(
true
);
expect
(
token
.
classList
.
contains
(
'
filtered-search-token
'
)).
toEqual
(
true
);
expect
(
token
.
querySelector
(
'
.name
'
).
innerText
).
toEqual
(
'
label
'
);
expect
(
token
.
querySelector
(
'
.value
'
).
innerText
).
toEqual
(
'
Frontend
'
);
...
...
@@ -317,10 +319,12 @@ describe('Filtered Search Visual Tokens', () => {
const
labelToken
=
tokens
[
0
];
const
assigneeToken
=
tokens
[
1
];
expect
(
labelToken
.
classList
.
contains
(
'
search-token-label
'
)).
toEqual
(
true
);
expect
(
labelToken
.
classList
.
contains
(
'
filtered-search-token
'
)).
toEqual
(
true
);
expect
(
labelToken
.
querySelector
(
'
.name
'
).
innerText
).
toEqual
(
'
label
'
);
expect
(
labelToken
.
querySelector
(
'
.value
'
).
innerText
).
toEqual
(
'
Frontend
'
);
expect
(
assigneeToken
.
classList
.
contains
(
'
search-token-assignee
'
)).
toEqual
(
true
);
expect
(
assigneeToken
.
classList
.
contains
(
'
filtered-search-token
'
)).
toEqual
(
true
);
expect
(
assigneeToken
.
querySelector
(
'
.name
'
).
innerText
).
toEqual
(
'
assignee
'
);
expect
(
assigneeToken
.
querySelector
(
'
.value
'
).
innerText
).
toEqual
(
'
@root
'
);
...
...
spec/javascripts/helpers/filtered_search_spec_helper.js
浏览文件 @
de784ac1
...
...
@@ -5,7 +5,7 @@ export default class FilteredSearchSpecHelper {
static
createFilterVisualToken
(
name
,
value
,
isSelected
=
false
)
{
const
li
=
document
.
createElement
(
'
li
'
);
li
.
classList
.
add
(
'
js-visual-token
'
,
'
filtered-search-token
'
);
li
.
classList
.
add
(
'
js-visual-token
'
,
'
filtered-search-token
'
,
`search-token-
${
name
}
`
);
li
.
innerHTML
=
`
<div class="selectable
${
isSelected
?
'
selected
'
:
''
}
" role="button">
...
...
spec/models/merge_request_spec.rb
浏览文件 @
de784ac1
...
...
@@ -270,6 +270,25 @@ describe MergeRequest do
end
end
describe
'.recent_target_branches'
do
let
(
:project
)
{
create
(
:project
)
}
let!
(
:merge_request1
)
{
create
(
:merge_request
,
:opened
,
source_project:
project
,
target_branch:
'feature'
)
}
let!
(
:merge_request2
)
{
create
(
:merge_request
,
:closed
,
source_project:
project
,
target_branch:
'merge-test'
)
}
let!
(
:merge_request3
)
{
create
(
:merge_request
,
:opened
,
source_project:
project
,
target_branch:
'fix'
)
}
let!
(
:merge_request4
)
{
create
(
:merge_request
,
:closed
,
source_project:
project
,
target_branch:
'feature'
)
}
before
do
merge_request1
.
update_columns
(
updated_at:
1
.
day
.
since
)
merge_request2
.
update_columns
(
updated_at:
2
.
days
.
since
)
merge_request3
.
update_columns
(
updated_at:
3
.
days
.
since
)
merge_request4
.
update_columns
(
updated_at:
4
.
days
.
since
)
end
it
'returns target branches sort by updated at desc'
do
expect
(
described_class
.
recent_target_branches
).
to
match_array
([
'feature'
,
'merge-test'
,
'fix'
])
end
end
describe
'#target_branch_sha'
do
let
(
:project
)
{
create
(
:project
,
:repository
)
}
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录