Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
李少辉-开发者
gitlab-foss
提交
4e7d4b4d
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,发现更多精彩内容 >>
提交
4e7d4b4d
编写于
3月 16, 2017
作者:
F
Filipa Lacerda
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'issue-boards-modal-filter-bar' into 'master'
Added filter bar into add issues modal See merge request !9856
上级
4413d97b
addbf88c
变更
21
隐藏空白更改
内联
并排
Showing
21 changed file
with
210 addition
and
459 deletion
+210
-459
app/assets/javascripts/boards/components/modal/filters.js
app/assets/javascripts/boards/components/modal/filters.js
+16
-41
app/assets/javascripts/boards/components/modal/filters/label.js
...sets/javascripts/boards/components/modal/filters/label.js
+0
-54
app/assets/javascripts/boards/components/modal/filters/milestone.js
.../javascripts/boards/components/modal/filters/milestone.js
+0
-56
app/assets/javascripts/boards/components/modal/filters/user.js
...ssets/javascripts/boards/components/modal/filters/user.js
+0
-96
app/assets/javascripts/boards/components/modal/header.js
app/assets/javascripts/boards/components/modal/header.js
+4
-12
app/assets/javascripts/boards/components/modal/index.js
app/assets/javascripts/boards/components/modal/index.js
+3
-11
app/assets/javascripts/boards/filtered_search_boards.js
app/assets/javascripts/boards/filtered_search_boards.js
+9
-2
app/assets/javascripts/boards/models/list.js
app/assets/javascripts/boards/models/list.js
+2
-19
app/assets/javascripts/boards/stores/modal_store.js
app/assets/javascripts/boards/stores/modal_store.js
+3
-11
app/assets/javascripts/boards/utils/query_data.js
app/assets/javascripts/boards/utils/query_data.js
+21
-0
app/assets/javascripts/filtered_search/container.js
app/assets/javascripts/filtered_search/container.js
+14
-0
app/assets/javascripts/filtered_search/dropdown_hint.js
app/assets/javascripts/filtered_search/dropdown_hint.js
+1
-1
app/assets/javascripts/filtered_search/dropdown_utils.js
app/assets/javascripts/filtered_search/dropdown_utils.js
+4
-1
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
...ripts/filtered_search/filtered_search_dropdown_manager.js
+11
-9
app/assets/javascripts/filtered_search/filtered_search_manager.js
...ts/javascripts/filtered_search/filtered_search_manager.js
+7
-4
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
...ascripts/filtered_search/filtered_search_visual_tokens.js
+14
-12
app/assets/stylesheets/pages/boards.scss
app/assets/stylesheets/pages/boards.scss
+3
-6
app/views/projects/boards/_show.html.haml
app/views/projects/boards/_show.html.haml
+1
-0
app/views/shared/issuable/_search_bar.html.haml
app/views/shared/issuable/_search_bar.html.haml
+19
-17
spec/features/boards/add_issues_modal_spec.rb
spec/features/boards/add_issues_modal_spec.rb
+2
-0
spec/features/boards/modal_filter_spec.rb
spec/features/boards/modal_filter_spec.rb
+76
-107
未找到文件。
app/assets/javascripts/boards/components/modal/filters.js
浏览文件 @
4e7d4b4d
/* global Vue */
import
FilteredSearchBoards
from
'
../../filtered_search_boards
'
;
const
userFilter
=
require
(
'
./filters/user
'
);
import
FilteredSearchContainer
from
'
../../../filtered_search/container
'
;
const
milestoneFilter
=
require
(
'
./filters/milestone
'
);
const
labelFilter
=
require
(
'
./filters/label
'
);
module
.
exports
=
Vue
.
extend
(
{
export
default
{
name
:
'
modal-filters
'
,
name
:
'
modal-filters
'
,
props
:
{
props
:
{
projectId
:
{
store
:
{
type
:
Number
,
type
:
Object
,
required
:
true
,
},
milestonePath
:
{
type
:
String
,
required
:
true
,
},
labelPath
:
{
type
:
String
,
required
:
true
,
required
:
true
,
},
},
},
},
destroyed
()
{
mounted
()
{
gl
.
issueBoards
.
ModalStore
.
setDefaultFilter
();
FilteredSearchContainer
.
container
=
this
.
$el
;
this
.
filteredSearch
=
new
FilteredSearchBoards
(
this
.
store
);
this
.
filteredSearch
.
removeTokens
();
},
},
components
:
{
beforeDestroy
()
{
userFilter
,
this
.
filteredSearch
.
cleanup
();
milestoneFilter
,
FilteredSearchContainer
.
container
=
document
;
labelFilter
,
this
.
store
.
path
=
''
;
},
},
template
:
`
template
:
'
#js-board-modal-filter
'
,
<div class="modal-filters">
};
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-user-search js-author-search"
toggle-label="Author"
field-name="author_id"
:project-id="projectId"></user-filter>
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-assignee-search"
toggle-label="Assignee"
field-name="assignee_id"
:null-user="true"
:project-id="projectId"></user-filter>
<milestone-filter :milestone-path="milestonePath"></milestone-filter>
<label-filter :label-path="labelPath"></label-filter>
</div>
`
,
});
app/assets/javascripts/boards/components/modal/filters/label.js
已删除
100644 → 0
浏览文件 @
4413d97b
/* eslint-disable no-new */
/* global Vue */
/* global LabelsSelect */
module
.
exports
=
Vue
.
extend
({
name
:
'
filter-label
'
,
props
:
{
labelPath
:
{
type
:
String
,
required
:
true
,
},
},
mounted
()
{
new
LabelsSelect
(
this
.
$refs
.
dropdown
);
},
template
:
`
<div class="dropdown">
<button
class="dropdown-menu-toggle js-label-select js-multiselect js-extra-options"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-no="true"
:data-labels="labelPath"
ref="dropdown">
<span class="dropdown-toggle-text">
Label
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable">
<div class="dropdown-title">
Filter by label
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`
,
});
app/assets/javascripts/boards/components/modal/filters/milestone.js
已删除
100644 → 0
浏览文件 @
4413d97b
/* eslint-disable no-new */
/* global Vue */
/* global MilestoneSelect */
module
.
exports
=
Vue
.
extend
({
name
:
'
filter-milestone
'
,
props
:
{
milestonePath
:
{
type
:
String
,
required
:
true
,
},
},
mounted
()
{
new
MilestoneSelect
(
null
,
this
.
$refs
.
dropdown
);
},
template
:
`
<div class="dropdown">
<button
class="dropdown-menu-toggle js-milestone-select"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-upcoming="true"
data-show-started="true"
data-field-name="milestone_title"
:data-milestones="milestonePath"
ref="dropdown">
<span class="dropdown-toggle-text">
Milestone
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-selectable dropdown-menu-milestone">
<div class="dropdown-title">
<span>Filter by milestone</span>
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search milestones"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`
,
});
app/assets/javascripts/boards/components/modal/filters/user.js
已删除
100644 → 0
浏览文件 @
4413d97b
/* eslint-disable no-new */
/* global Vue */
/* global UsersSelect */
module
.
exports
=
Vue
.
extend
({
name
:
'
filter-user
'
,
props
:
{
toggleClassName
:
{
type
:
String
,
required
:
true
,
},
dropdownClassName
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
toggleLabel
:
{
type
:
String
,
required
:
true
,
},
fieldName
:
{
type
:
String
,
required
:
true
,
},
nullUser
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
projectId
:
{
type
:
Number
,
required
:
true
,
},
},
mounted
()
{
new
UsersSelect
(
null
,
this
.
$refs
.
dropdown
);
},
computed
:
{
currentUsername
()
{
return
gon
.
current_username
;
},
dropdownTitle
()
{
return
`Filter by
${
this
.
toggleLabel
.
toLowerCase
()}
`
;
},
inputPlaceholder
()
{
return
`Search
${
this
.
toggleLabel
.
toLowerCase
()}
`
;
},
},
template
:
`
<div class="dropdown">
<button
class="dropdown-menu-toggle js-user-search"
:class="toggleClassName"
type="button"
data-toggle="dropdown"
data-current-user="true"
:data-any-user="'Any ' + toggleLabel"
:data-null-user="nullUser"
:data-field-name="fieldName"
:data-project-id="projectId"
:data-first-user="currentUsername"
ref="dropdown">
<span class="dropdown-toggle-text">
{{ toggleLabel }}
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div
class="dropdown-menu dropdown-select dropdown-menu-user dropdown-menu-selectable"
:class="dropdownClassName">
<div class="dropdown-title">
{{ dropdownTitle }}
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
autocomplete="off"
:placeholder="inputPlaceholder" />
<i class="fa fa-search dropdown-input-search"></i>
<i
role="button"
class="fa fa-times dropdown-input-clear js-dropdown-input-clear">
</i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`
,
});
app/assets/javascripts/boards/components/modal/header.js
浏览文件 @
4e7d4b4d
/* global Vue */
import
Vue
from
'
vue
'
;
import
modalFilters
from
'
./filters
'
;
require
(
'
./tabs
'
);
require
(
'
./tabs
'
);
const
modalFilters
=
require
(
'
./filters
'
);
(()
=>
{
(()
=>
{
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
const
ModalStore
=
gl
.
issueBoards
.
ModalStore
;
...
@@ -66,16 +67,7 @@ const modalFilters = require('./filters');
...
@@ -66,16 +67,7 @@ const modalFilters = require('./filters');
<div
<div
class="add-issues-search append-bottom-10"
class="add-issues-search append-bottom-10"
v-if="showSearch">
v-if="showSearch">
<modal-filters
<modal-filters :store="filter" />
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-filters>
<input
placeholder="Search issues..."
class="form-control"
type="search"
v-model="searchTerm" />
<button
<button
type="button"
type="button"
class="btn btn-success btn-inverted prepend-left-10"
class="btn btn-success btn-inverted prepend-left-10"
...
...
app/assets/javascripts/boards/components/modal/index.js
浏览文件 @
4e7d4b4d
/* global Vue */
/* global Vue */
/* global ListIssue */
/* global ListIssue */
import
queryData
from
'
../../utils/query_data
'
;
require
(
'
./header
'
);
require
(
'
./header
'
);
require
(
'
./list
'
);
require
(
'
./list
'
);
...
@@ -47,9 +48,6 @@ require('./empty_state');
...
@@ -47,9 +48,6 @@ require('./empty_state');
page
()
{
page
()
{
this
.
loadIssues
();
this
.
loadIssues
();
},
},
searchTerm
()
{
this
.
searchOperation
();
},
showAddIssuesModal
()
{
showAddIssuesModal
()
{
if
(
this
.
showAddIssuesModal
&&
!
this
.
issues
.
length
)
{
if
(
this
.
showAddIssuesModal
&&
!
this
.
issues
.
length
)
{
this
.
loading
=
true
;
this
.
loading
=
true
;
...
@@ -72,19 +70,13 @@ require('./empty_state');
...
@@ -72,19 +70,13 @@ require('./empty_state');
},
},
},
},
methods
:
{
methods
:
{
searchOperation
:
_
.
debounce
(
function
searchOperationDebounce
()
{
this
.
loadIssues
(
true
);
},
500
),
loadIssues
(
clearIssues
=
false
)
{
loadIssues
(
clearIssues
=
false
)
{
if
(
!
this
.
showAddIssuesModal
)
return
false
;
if
(
!
this
.
showAddIssuesModal
)
return
false
;
const
queryData
=
Object
.
assign
({},
this
.
filter
,
{
return
gl
.
boardService
.
getBacklog
(
queryData
(
this
.
filter
.
path
,
{
search
:
this
.
searchTerm
,
page
:
this
.
page
,
page
:
this
.
page
,
per
:
this
.
perPage
,
per
:
this
.
perPage
,
});
})).
then
((
res
)
=>
{
return
gl
.
boardService
.
getBacklog
(
queryData
).
then
((
res
)
=>
{
const
data
=
res
.
json
();
const
data
=
res
.
json
();
if
(
clearIssues
)
{
if
(
clearIssues
)
{
...
...
app/assets/javascripts/boards/filtered_search_boards.js
浏览文件 @
4e7d4b4d
/* eslint-disable class-methods-use-this */
import
FilteredSearchContainer
from
'
../filtered_search/container
'
;
export
default
class
FilteredSearchBoards
extends
gl
.
FilteredSearchManager
{
export
default
class
FilteredSearchBoards
extends
gl
.
FilteredSearchManager
{
constructor
(
store
,
updateUrl
=
false
)
{
constructor
(
store
,
updateUrl
=
false
)
{
super
(
'
boards
'
);
super
(
'
boards
'
);
...
@@ -18,13 +21,17 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
...
@@ -18,13 +21,17 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
}
}
}
}
updat
eTokens
()
{
remov
eTokens
()
{
const
tokens
=
document
.
querySelectorAll
(
'
.js-visual-token
'
);
const
tokens
=
FilteredSearchContainer
.
container
.
querySelectorAll
(
'
.js-visual-token
'
);
// Remove all the tokens as they will be replaced by the search manager
// Remove all the tokens as they will be replaced by the search manager
[].
forEach
.
call
(
tokens
,
(
el
)
=>
{
[].
forEach
.
call
(
tokens
,
(
el
)
=>
{
el
.
parentNode
.
removeChild
(
el
);
el
.
parentNode
.
removeChild
(
el
);
});
});
}
updateTokens
()
{
this
.
removeTokens
();
this
.
loadSearchParamsFromURL
();
this
.
loadSearchParamsFromURL
();
...
...
app/assets/javascripts/boards/models/list.js
浏览文件 @
4e7d4b4d
/* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */
/* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */
/* global ListIssue */
/* global ListIssue */
/* global ListLabel */
/* global ListLabel */
import
queryData
from
'
../utils/query_data
'
;
class
List
{
class
List
{
constructor
(
obj
)
{
constructor
(
obj
)
{
...
@@ -64,25 +65,7 @@ class List {
...
@@ -64,25 +65,7 @@ class List {
}
}
getIssues
(
emptyIssues
=
true
)
{
getIssues
(
emptyIssues
=
true
)
{
const
data
=
gl
.
issueBoards
.
BoardsStore
.
filter
.
path
.
split
(
'
&
'
).
reduce
((
data
,
filterParam
)
=>
{
const
data
=
queryData
(
gl
.
issueBoards
.
BoardsStore
.
filter
.
path
,
{
page
:
this
.
page
});
if
(
filterParam
===
''
)
return
data
;
const
paramSplit
=
filterParam
.
split
(
'
=
'
);
const
paramKeyNormalized
=
paramSplit
[
0
].
replace
(
'
[]
'
,
''
);
const
isArray
=
paramSplit
[
0
].
indexOf
(
'
[]
'
);
const
value
=
decodeURIComponent
(
paramSplit
[
1
]).
replace
(
/
\+
/g
,
'
'
);
if
(
isArray
!==
-
1
)
{
if
(
!
data
[
paramKeyNormalized
])
{
data
[
paramKeyNormalized
]
=
[];
}
data
[
paramKeyNormalized
].
push
(
value
);
}
else
{
data
[
paramKeyNormalized
]
=
value
;
}
return
data
;
},
{
page
:
this
.
page
});
if
(
this
.
label
&&
data
.
label_name
)
{
if
(
this
.
label
&&
data
.
label_name
)
{
data
.
label_name
=
data
.
label_name
.
filter
(
label
=>
label
!==
this
.
label
.
title
);
data
.
label_name
=
data
.
label_name
.
filter
(
label
=>
label
!==
this
.
label
.
title
);
...
...
app/assets/javascripts/boards/stores/modal_store.js
浏览文件 @
4e7d4b4d
...
@@ -17,17 +17,9 @@
...
@@ -17,17 +17,9 @@
loadingNewPage
:
false
,
loadingNewPage
:
false
,
page
:
1
,
page
:
1
,
perPage
:
50
,
perPage
:
50
,
};
filter
:
{
path
:
''
,
this
.
setDefaultFilter
();
},
}
setDefaultFilter
()
{
this
.
store
.
filter
=
{
author_id
:
''
,
assignee_id
:
''
,
milestone_title
:
''
,
label_name
:
[],
};
};
}
}
...
...
app/assets/javascripts/boards/utils/query_data.js
0 → 100644
浏览文件 @
4e7d4b4d
export
default
(
path
,
extraData
)
=>
path
.
split
(
'
&
'
).
reduce
((
dataParam
,
filterParam
)
=>
{
if
(
filterParam
===
''
)
return
dataParam
;
const
data
=
dataParam
;
const
paramSplit
=
filterParam
.
split
(
'
=
'
);
const
paramKeyNormalized
=
paramSplit
[
0
].
replace
(
'
[]
'
,
''
);
const
isArray
=
paramSplit
[
0
].
indexOf
(
'
[]
'
);
const
value
=
decodeURIComponent
(
paramSplit
[
1
]).
replace
(
/
\+
/g
,
'
'
);
if
(
isArray
!==
-
1
)
{
if
(
!
data
[
paramKeyNormalized
])
{
data
[
paramKeyNormalized
]
=
[];
}
data
[
paramKeyNormalized
].
push
(
value
);
}
else
{
data
[
paramKeyNormalized
]
=
value
;
}
return
data
;
},
extraData
);
app/assets/javascripts/filtered_search/container.js
0 → 100644
浏览文件 @
4e7d4b4d
/* eslint-disable class-methods-use-this */
let
container
=
document
;
class
FilteredSearchContainerClass
{
set
container
(
containerParam
)
{
container
=
containerParam
;
}
get
container
()
{
return
container
;
}
}
export
default
new
FilteredSearchContainerClass
();
app/assets/javascripts/filtered_search/dropdown_hint.js
浏览文件 @
4e7d4b4d
...
@@ -45,7 +45,7 @@ require('./filtered_search_dropdown');
...
@@ -45,7 +45,7 @@ require('./filtered_search_dropdown');
gl
.
FilteredSearchVisualTokens
.
addSearchVisualToken
(
searchTerms
.
join
(
'
'
));
gl
.
FilteredSearchVisualTokens
.
addSearchVisualToken
(
searchTerms
.
join
(
'
'
));
}
}
gl
.
FilteredSearchDropdownManager
.
addWordToInput
(
token
.
replace
(
'
:
'
,
''
));
gl
.
FilteredSearchDropdownManager
.
addWordToInput
(
token
.
replace
(
'
:
'
,
''
)
,
''
,
false
,
this
.
container
);
}
}
this
.
dismissDropdown
();
this
.
dismissDropdown
();
this
.
dispatchInputEvent
();
this
.
dispatchInputEvent
();
...
...
app/assets/javascripts/filtered_search/dropdown_utils.js
浏览文件 @
4e7d4b4d
import
FilteredSearchContainer
from
'
./container
'
;
(()
=>
{
(()
=>
{
class
DropdownUtils
{
class
DropdownUtils
{
static
getEscapedText
(
text
)
{
static
getEscapedText
(
text
)
{
...
@@ -85,7 +87,8 @@
...
@@ -85,7 +87,8 @@
// Determines the full search query (visual tokens + input)
// Determines the full search query (visual tokens + input)
static
getSearchQuery
(
untilInput
=
false
)
{
static
getSearchQuery
(
untilInput
=
false
)
{
const
tokens
=
[].
slice
.
call
(
document
.
querySelectorAll
(
'
.tokens-container li
'
));
const
container
=
FilteredSearchContainer
.
container
;
const
tokens
=
[].
slice
.
call
(
container
.
querySelectorAll
(
'
.tokens-container li
'
));
const
values
=
[];
const
values
=
[];
if
(
untilInput
)
{
if
(
untilInput
)
{
...
...
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
浏览文件 @
4e7d4b4d
/* global DropLab */
/* global DropLab */
import
FilteredSearchContainer
from
'
./container
'
;
(()
=>
{
(()
=>
{
class
FilteredSearchDropdownManager
{
class
FilteredSearchDropdownManager
{
constructor
(
baseEndpoint
=
''
,
page
)
{
constructor
(
baseEndpoint
=
''
,
page
)
{
this
.
container
=
FilteredSearchContainer
.
container
;
this
.
baseEndpoint
=
baseEndpoint
.
replace
(
/
\/
$/
,
''
);
this
.
baseEndpoint
=
baseEndpoint
.
replace
(
/
\/
$/
,
''
);
this
.
tokenizer
=
gl
.
FilteredSearchTokenizer
;
this
.
tokenizer
=
gl
.
FilteredSearchTokenizer
;
this
.
filteredSearchTokenKeys
=
gl
.
FilteredSearchTokenKeys
;
this
.
filteredSearchTokenKeys
=
gl
.
FilteredSearchTokenKeys
;
this
.
filteredSearchInput
=
document
.
querySelector
(
'
.filtered-search
'
);
this
.
filteredSearchInput
=
this
.
container
.
querySelector
(
'
.filtered-search
'
);
this
.
page
=
page
;
this
.
page
=
page
;
this
.
setupMapping
();
this
.
setupMapping
();
...
@@ -31,35 +33,35 @@
...
@@ -31,35 +33,35 @@
author
:
{
author
:
{
reference
:
null
,
reference
:
null
,
gl
:
'
DropdownUser
'
,
gl
:
'
DropdownUser
'
,
element
:
document
.
querySelector
(
'
#js-dropdown-author
'
),
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-author
'
),
},
},
assignee
:
{
assignee
:
{
reference
:
null
,
reference
:
null
,
gl
:
'
DropdownUser
'
,
gl
:
'
DropdownUser
'
,
element
:
document
.
querySelector
(
'
#js-dropdown-assignee
'
),
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-assignee
'
),
},
},
milestone
:
{
milestone
:
{
reference
:
null
,
reference
:
null
,
gl
:
'
DropdownNonUser
'
,
gl
:
'
DropdownNonUser
'
,
extraArguments
:
[
`
${
this
.
baseEndpoint
}
/milestones.json`
,
'
%
'
],
extraArguments
:
[
`
${
this
.
baseEndpoint
}
/milestones.json`
,
'
%
'
],
element
:
document
.
querySelector
(
'
#js-dropdown-milestone
'
),
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-milestone
'
),
},
},
label
:
{
label
:
{
reference
:
null
,
reference
:
null
,
gl
:
'
DropdownNonUser
'
,
gl
:
'
DropdownNonUser
'
,
extraArguments
:
[
`
${
this
.
baseEndpoint
}
/labels.json`
,
'
~
'
],
extraArguments
:
[
`
${
this
.
baseEndpoint
}
/labels.json`
,
'
~
'
],
element
:
document
.
querySelector
(
'
#js-dropdown-label
'
),
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-label
'
),
},
},
hint
:
{
hint
:
{
reference
:
null
,
reference
:
null
,
gl
:
'
DropdownHint
'
,
gl
:
'
DropdownHint
'
,
element
:
document
.
querySelector
(
'
#js-dropdown-hint
'
),
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-hint
'
),
},
},
};
};
}
}
static
addWordToInput
(
tokenName
,
tokenValue
=
''
,
clicked
=
false
)
{
static
addWordToInput
(
tokenName
,
tokenValue
=
''
,
clicked
=
false
)
{
const
input
=
document
.
querySelector
(
'
.filtered-search
'
);
const
input
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.filtered-search
'
);
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
tokenName
,
tokenValue
);
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
tokenName
,
tokenValue
);
input
.
value
=
''
;
input
.
value
=
''
;
...
@@ -75,13 +77,13 @@
...
@@ -75,13 +77,13 @@
updateDropdownOffset
(
key
)
{
updateDropdownOffset
(
key
)
{
// Always align dropdown with the input field
// Always align dropdown with the input field
let
offset
=
this
.
filteredSearchInput
.
getBoundingClientRect
().
left
-
document
.
querySelector
(
'
.scroll-container
'
).
getBoundingClientRect
().
left
;
let
offset
=
this
.
filteredSearchInput
.
getBoundingClientRect
().
left
-
this
.
container
.
querySelector
(
'
.scroll-container
'
).
getBoundingClientRect
().
left
;
const
maxInputWidth
=
240
;
const
maxInputWidth
=
240
;
const
currentDropdownWidth
=
this
.
mapping
[
key
].
element
.
clientWidth
||
maxInputWidth
;
const
currentDropdownWidth
=
this
.
mapping
[
key
].
element
.
clientWidth
||
maxInputWidth
;
// Make sure offset never exceeds the input container
// Make sure offset never exceeds the input container
const
offsetMaxWidth
=
document
.
querySelector
(
'
.scroll-container
'
).
clientWidth
-
currentDropdownWidth
;
const
offsetMaxWidth
=
this
.
container
.
querySelector
(
'
.scroll-container
'
).
clientWidth
-
currentDropdownWidth
;
if
(
offsetMaxWidth
<
offset
)
{
if
(
offsetMaxWidth
<
offset
)
{
offset
=
offsetMaxWidth
;
offset
=
offsetMaxWidth
;
}
}
...
...
app/assets/javascripts/filtered_search/filtered_search_manager.js
浏览文件 @
4e7d4b4d
import
FilteredSearchContainer
from
'
./container
'
;
(()
=>
{
(()
=>
{
class
FilteredSearchManager
{
class
FilteredSearchManager
{
constructor
(
page
)
{
constructor
(
page
)
{
this
.
filteredSearchInput
=
document
.
querySelector
(
'
.filtered-search
'
);
this
.
container
=
FilteredSearchContainer
.
container
;
this
.
clearSearchButton
=
document
.
querySelector
(
'
.clear-search
'
);
this
.
filteredSearchInput
=
this
.
container
.
querySelector
(
'
.filtered-search
'
);
this
.
tokensContainer
=
document
.
querySelector
(
'
.tokens-container
'
);
this
.
clearSearchButton
=
this
.
container
.
querySelector
(
'
.clear-search
'
);
this
.
tokensContainer
=
this
.
container
.
querySelector
(
'
.tokens-container
'
);
this
.
filteredSearchTokenKeys
=
gl
.
FilteredSearchTokenKeys
;
this
.
filteredSearchTokenKeys
=
gl
.
FilteredSearchTokenKeys
;
if
(
this
.
filteredSearchInput
)
{
if
(
this
.
filteredSearchInput
)
{
...
@@ -132,7 +135,7 @@
...
@@ -132,7 +135,7 @@
}
}
unselectEditTokens
(
e
)
{
unselectEditTokens
(
e
)
{
const
inputContainer
=
document
.
querySelector
(
'
.filtered-search-input-container
'
);
const
inputContainer
=
this
.
container
.
querySelector
(
'
.filtered-search-input-container
'
);
const
isElementInFilteredSearch
=
inputContainer
&&
inputContainer
.
contains
(
e
.
target
);
const
isElementInFilteredSearch
=
inputContainer
&&
inputContainer
.
contains
(
e
.
target
);
const
isElementInFilterDropdown
=
e
.
target
.
closest
(
'
.filter-dropdown
'
)
!==
null
;
const
isElementInFilterDropdown
=
e
.
target
.
closest
(
'
.filter-dropdown
'
)
!==
null
;
const
isElementTokensContainer
=
e
.
target
.
classList
.
contains
(
'
tokens-container
'
);
const
isElementTokensContainer
=
e
.
target
.
classList
.
contains
(
'
tokens-container
'
);
...
...
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
浏览文件 @
4e7d4b4d
import
FilteredSearchContainer
from
'
./container
'
;
class
FilteredSearchVisualTokens
{
class
FilteredSearchVisualTokens
{
static
getLastVisualTokenBeforeInput
()
{
static
getLastVisualTokenBeforeInput
()
{
const
inputLi
=
document
.
querySelector
(
'
.input-token
'
);
const
inputLi
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.input-token
'
);
const
lastVisualToken
=
inputLi
&&
inputLi
.
previousElementSibling
;
const
lastVisualToken
=
inputLi
&&
inputLi
.
previousElementSibling
;
return
{
return
{
...
@@ -10,7 +12,7 @@ class FilteredSearchVisualTokens {
...
@@ -10,7 +12,7 @@ class FilteredSearchVisualTokens {
}
}
static
unselectTokens
()
{
static
unselectTokens
()
{
const
otherTokens
=
document
.
querySelectorAll
(
'
.js-visual-token .selectable.selected
'
);
const
otherTokens
=
FilteredSearchContainer
.
container
.
querySelectorAll
(
'
.js-visual-token .selectable.selected
'
);
[].
forEach
.
call
(
otherTokens
,
t
=>
t
.
classList
.
remove
(
'
selected
'
));
[].
forEach
.
call
(
otherTokens
,
t
=>
t
.
classList
.
remove
(
'
selected
'
));
}
}
...
@@ -24,7 +26,7 @@ class FilteredSearchVisualTokens {
...
@@ -24,7 +26,7 @@ class FilteredSearchVisualTokens {
}
}
static
removeSelectedToken
()
{
static
removeSelectedToken
()
{
const
selected
=
document
.
querySelector
(
'
.js-visual-token .selected
'
);
const
selected
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.js-visual-token .selected
'
);
if
(
selected
)
{
if
(
selected
)
{
const
li
=
selected
.
closest
(
'
.js-visual-token
'
);
const
li
=
selected
.
closest
(
'
.js-visual-token
'
);
...
@@ -54,8 +56,8 @@ class FilteredSearchVisualTokens {
...
@@ -54,8 +56,8 @@ class FilteredSearchVisualTokens {
}
}
li
.
querySelector
(
'
.name
'
).
innerText
=
name
;
li
.
querySelector
(
'
.name
'
).
innerText
=
name
;
const
tokensContainer
=
document
.
querySelector
(
'
.tokens-container
'
);
const
tokensContainer
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.tokens-container
'
);
const
input
=
document
.
querySelector
(
'
.filtered-search
'
);
const
input
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.filtered-search
'
);
tokensContainer
.
insertBefore
(
li
,
input
.
parentElement
);
tokensContainer
.
insertBefore
(
li
,
input
.
parentElement
);
}
}
...
@@ -77,14 +79,14 @@ class FilteredSearchVisualTokens {
...
@@ -77,14 +79,14 @@ class FilteredSearchVisualTokens {
const
addVisualTokenElement
=
FilteredSearchVisualTokens
.
addVisualTokenElement
;
const
addVisualTokenElement
=
FilteredSearchVisualTokens
.
addVisualTokenElement
;
if
(
isLastVisualTokenValid
)
{
if
(
isLastVisualTokenValid
)
{
addVisualTokenElement
(
tokenName
,
tokenValue
);
addVisualTokenElement
(
tokenName
,
tokenValue
,
false
);
}
else
{
}
else
{
const
previousTokenName
=
lastVisualToken
.
querySelector
(
'
.name
'
).
innerText
;
const
previousTokenName
=
lastVisualToken
.
querySelector
(
'
.name
'
).
innerText
;
const
tokensContainer
=
document
.
querySelector
(
'
.tokens-container
'
);
const
tokensContainer
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.tokens-container
'
);
tokensContainer
.
removeChild
(
lastVisualToken
);
tokensContainer
.
removeChild
(
lastVisualToken
);
const
value
=
tokenValue
||
tokenName
;
const
value
=
tokenValue
||
tokenName
;
addVisualTokenElement
(
previousTokenName
,
value
);
addVisualTokenElement
(
previousTokenName
,
value
,
false
);
}
}
}
}
...
@@ -129,7 +131,7 @@ class FilteredSearchVisualTokens {
...
@@ -129,7 +131,7 @@ class FilteredSearchVisualTokens {
}
}
static
tokenizeInput
()
{
static
tokenizeInput
()
{
const
input
=
document
.
querySelector
(
'
.filtered-search
'
);
const
input
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.filtered-search
'
);
const
{
isLastVisualTokenValid
}
=
const
{
isLastVisualTokenValid
}
=
gl
.
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
gl
.
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
...
@@ -145,7 +147,7 @@ class FilteredSearchVisualTokens {
...
@@ -145,7 +147,7 @@ class FilteredSearchVisualTokens {
}
}
static
editToken
(
token
)
{
static
editToken
(
token
)
{
const
input
=
document
.
querySelector
(
'
.filtered-search
'
);
const
input
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.filtered-search
'
);
FilteredSearchVisualTokens
.
tokenizeInput
();
FilteredSearchVisualTokens
.
tokenizeInput
();
...
@@ -174,9 +176,9 @@ class FilteredSearchVisualTokens {
...
@@ -174,9 +176,9 @@ class FilteredSearchVisualTokens {
}
}
static
moveInputToTheRight
()
{
static
moveInputToTheRight
()
{
const
input
=
document
.
querySelector
(
'
.filtered-search
'
);
const
input
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.filtered-search
'
);
const
inputLi
=
input
.
parentElement
;
const
inputLi
=
input
.
parentElement
;
const
tokenContainer
=
document
.
querySelector
(
'
.tokens-container
'
);
const
tokenContainer
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.tokens-container
'
);
FilteredSearchVisualTokens
.
tokenizeInput
();
FilteredSearchVisualTokens
.
tokenizeInput
();
...
...
app/assets/stylesheets/pages/boards.scss
浏览文件 @
4e7d4b4d
...
@@ -420,12 +420,9 @@
...
@@ -420,12 +420,9 @@
display
:
-
webkit-flex
;
display
:
-
webkit-flex
;
display
:
flex
;
display
:
flex
;
.form-control
{
.issues-filters
{
margin-left
:
auto
;
-webkit-flex
:
1
;
flex
:
1
;
@media
(
min-width
:
$screen-sm-min
)
{
max-width
:
200px
;
}
}
}
}
}
...
...
app/views/projects/boards/_show.html.haml
浏览文件 @
4e7d4b4d
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
%script
#js-board-template
{
type:
"text/x-template"
}=
render
"projects/boards/components/board"
%script
#js-board-template
{
type:
"text/x-template"
}=
render
"projects/boards/components/board"
%script
#js-board-list-template
{
type:
"text/x-template"
}=
render
"projects/boards/components/board_list"
%script
#js-board-list-template
{
type:
"text/x-template"
}=
render
"projects/boards/components/board_list"
%script
#js-board-modal-filter
{
type:
"text/x-template"
}=
render
"shared/issuable/search_bar"
,
type: :boards_modal
=
render
"projects/issues/head"
=
render
"projects/issues/head"
...
...
app/views/shared/issuable/_search_bar.html.haml
浏览文件 @
4e7d4b4d
-
type
=
local_assigns
.
fetch
(
:type
)
-
type
=
local_assigns
.
fetch
(
:type
)
-
block_css_class
=
type
!=
:boards_modal
?
'row-content-block second-block'
:
''
.issues-filters
.issues-filters
.issues-details-filters.
row-content-block.second-block.filtered-search-block
.issues-details-filters.
filtered-search-block
{
class:
block_css_class
,
"v-pre"
=>
type
==
:boards_modal
}
=
form_tag
page_filter_path
(
without:
[
:assignee_id
,
:author_id
,
:milestone_title
,
:label_name
,
:search
]),
method: :get
,
class:
'filter-form js-filter-form'
do
=
form_tag
page_filter_path
(
without:
[
:assignee_id
,
:author_id
,
:milestone_title
,
:label_name
,
:search
]),
method: :get
,
class:
'filter-form js-filter-form'
do
-
if
params
[
:search
].
present?
-
if
params
[
:search
].
present?
=
hidden_field_tag
:search
,
params
[
:search
]
=
hidden_field_tag
:search
,
params
[
:search
]
...
@@ -14,7 +15,7 @@
...
@@ -14,7 +15,7 @@
.scroll-container
.scroll-container
%ul
.tokens-container.list-unstyled
%ul
.tokens-container.list-unstyled
%li
.input-token
%li
.input-token
%input
.form-control.filtered-search
{
placeholder:
'Search or filter results...'
,
data:
{
id:
'filtered-search'
,
'project-id'
=>
@project
.
id
,
'username-params'
=>
@users
.
to_json
(
only:
[
:id
,
:username
]),
'base-endpoint'
=>
namespace_project_path
(
@project
.
namespace
,
@project
)
}
}
%input
.form-control.filtered-search
{
placeholder:
'Search or filter results...'
,
data:
{
id:
"filtered-search-#{type.to_s}"
,
'project-id'
=>
@project
.
id
,
'username-params'
=>
@users
.
to_json
(
only:
[
:id
,
:username
]),
'base-endpoint'
=>
namespace_project_path
(
@project
.
namespace
,
@project
)
}
}
=
icon
(
'filter'
)
=
icon
(
'filter'
)
%button
.clear-search.hidden
{
type:
'button'
}
%button
.clear-search.hidden
{
type:
'button'
}
=
icon
(
'times'
)
=
icon
(
'times'
)
...
@@ -100,7 +101,7 @@
...
@@ -100,7 +101,7 @@
=
render
partial:
"shared/issuable/label_page_create"
=
render
partial:
"shared/issuable/label_page_create"
=
dropdown_loading
=
dropdown_loading
#js-add-issues-btn
.prepend-left-10
#js-add-issues-btn
.prepend-left-10
-
els
e
-
els
if
type
!=
:boards_modal
=
render
'shared/sort_dropdown'
=
render
'shared/sort_dropdown'
-
if
@bulk_edit
-
if
@bulk_edit
...
@@ -133,19 +134,20 @@
...
@@ -133,19 +134,20 @@
.filter-item.inline.update-issues-btn
.filter-item.inline.update-issues-btn
=
button_tag
"Update
#{
type
.
to_s
.
humanize
(
capitalize:
false
)
}
"
,
class:
"btn update_selected_issues btn-save"
=
button_tag
"Update
#{
type
.
to_s
.
humanize
(
capitalize:
false
)
}
"
,
class:
"btn update_selected_issues btn-save"
:javascript
-
unless
type
===
:boards_modal
new
UsersSelect
();
:javascript
new
LabelsSelect
();
new
UsersSelect
();
new
MilestoneSelect
();
new
LabelsSelect
();
new
IssueStatusSelect
();
new
MilestoneSelect
();
new
SubscriptionSelect
();
new
IssueStatusSelect
();
new
SubscriptionSelect
();
$
(
document
).
off
(
'
page:restore
'
).
on
(
'
page:restore
'
,
function
(
event
)
{
$
(
document
).
off
(
'
page:restore
'
).
on
(
'
page:restore
'
,
function
(
event
)
{
if
(
gl
.
FilteredSearchManager
)
{
if
(
gl
.
FilteredSearchManager
)
{
new
gl
.
FilteredSearchManager
();
new
gl
.
FilteredSearchManager
();
}
}
Issuable
.
init
();
Issuable
.
init
();
new
gl
.
IssuableBulkActions
({
new
gl
.
IssuableBulkActions
({
prefixId
:
'
issue_
'
,
prefixId
:
'
issue_
'
,
});
});
});
});
spec/features/boards/add_issues_modal_spec.rb
浏览文件 @
4e7d4b4d
...
@@ -107,6 +107,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
...
@@ -107,6 +107,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
it
'returns issues'
do
it
'returns issues'
do
page
.
within
(
'.add-issues-modal'
)
do
page
.
within
(
'.add-issues-modal'
)
do
find
(
'.form-control'
).
native
.
send_keys
(
issue
.
title
)
find
(
'.form-control'
).
native
.
send_keys
(
issue
.
title
)
find
(
'.form-control'
).
native
.
send_keys
(
:enter
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
end
end
...
@@ -115,6 +116,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
...
@@ -115,6 +116,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
it
'returns no issues'
do
it
'returns no issues'
do
page
.
within
(
'.add-issues-modal'
)
do
page
.
within
(
'.add-issues-modal'
)
do
find
(
'.form-control'
).
native
.
send_keys
(
'testing search'
)
find
(
'.form-control'
).
native
.
send_keys
(
'testing search'
)
find
(
'.form-control'
).
native
.
send_keys
(
:enter
)
expect
(
page
).
not_to
have_selector
(
'.card'
)
expect
(
page
).
not_to
have_selector
(
'.card'
)
expect
(
page
).
not_to
have_content
(
"You haven't added any issues to your project yet"
)
expect
(
page
).
not_to
have_content
(
"You haven't added any issues to your project yet"
)
...
...
spec/features/boards/modal_filter_spec.rb
浏览文件 @
4e7d4b4d
require
'rails_helper'
require
'rails_helper'
describe
'Issue Boards add issue modal filtering'
,
:feature
,
:js
do
describe
'Issue Boards add issue modal filtering'
,
:feature
,
:js
do
include
WaitForAjax
include
WaitForVueResource
include
WaitForVueResource
let
(
:project
)
{
create
(
:empty_project
,
:public
)
}
let
(
:project
)
{
create
(
:empty_project
,
:public
)
}
...
@@ -23,6 +22,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
...
@@ -23,6 +22,7 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
page
.
within
(
'.add-issues-modal'
)
do
page
.
within
(
'.add-issues-modal'
)
do
find
(
'.form-control'
).
native
.
send_keys
(
'testing empty state'
)
find
(
'.form-control'
).
native
.
send_keys
(
'testing empty state'
)
find
(
'.form-control'
).
native
.
send_keys
(
:enter
)
wait_for_vue_resource
wait_for_vue_resource
...
@@ -33,13 +33,11 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
...
@@ -33,13 +33,11 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
it
'restores filters when closing'
do
it
'restores filters when closing'
do
visit_board
visit_board
page
.
within
(
'.add-issues-modal'
)
do
set_filter
(
'milestone'
)
click_button
'Milestone'
click_filter_link
(
'Upcoming'
)
submit_filter
wait_for_ajax
click_link
'Upcoming'
page
.
within
(
'.add-issues-modal'
)
do
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
0
)
...
@@ -56,39 +54,44 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
...
@@ -56,39 +54,44 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
end
end
end
end
context
'author'
do
it
'resotres filters after clicking clear button'
do
let!
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user2
)
}
visit_board
before
do
project
.
team
<<
[
user2
,
:developer
]
visit_board
set_filter
(
'milestone'
)
end
click_filter_link
(
'Upcoming'
)
submit_filter
it
'filters by any author'
do
page
.
within
(
'.add-issues-modal'
)
do
page
.
within
(
'.add-issues-modal'
)
do
wait_for_vue_resource
click_button
'Author'
wait_for_ajax
expect
(
page
).
to
have_selector
(
'.card'
,
count:
0
)
click_link
'Any Author'
find
(
'.clear-search'
).
click
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.card'
,
count:
2
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
end
end
end
end
it
'filters by selected user'
do
context
'author'
do
page
.
within
(
'.add-issues-modal'
)
do
let!
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user2
)
}
click_button
'Author'
before
do
project
.
team
<<
[
user2
,
:developer
]
wait_for_ajax
visit_board
end
click_link
user2
.
name
it
'filters by selected user'
do
set_filter
(
'author'
)
click_filter_link
(
user2
.
name
)
submit_filter
page
.
within
(
'.add-issues-modal'
)
do
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.js-visual-token'
,
text:
user2
.
username
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
end
end
end
end
...
@@ -103,46 +106,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
...
@@ -103,46 +106,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
visit_board
visit_board
end
end
it
'filters by any assignee'
do
page
.
within
(
'.add-issues-modal'
)
do
click_button
'Assignee'
wait_for_ajax
click_link
'Any Assignee'
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.card'
,
count:
2
)
end
end
it
'filters by unassigned'
do
it
'filters by unassigned'
do
page
.
within
(
'.add-issues-modal'
)
do
set_filter
(
'assignee'
)
click_button
'Assignee'
click_filter_link
(
'No Assignee'
)
submit_filter
wait_for_ajax
click_link
'Unassigned'
page
.
within
(
'.add-issues-modal'
)
do
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.js-visual-token'
,
text:
'none'
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
end
end
end
end
it
'filters by selected user'
do
it
'filters by selected user'
do
page
.
within
(
'.add-issues-modal'
)
do
set_filter
(
'assignee'
)
click_button
'Assignee'
click_filter_link
(
user2
.
name
)
submit_filter
wait_for_ajax
page
.
within
'.dropdown-menu-user'
do
click_link
user2
.
name
end
page
.
within
(
'.add-issues-modal'
)
do
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.js-visual-token'
,
text:
user2
.
username
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
end
end
end
end
...
@@ -156,44 +141,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
...
@@ -156,44 +141,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
visit_board
visit_board
end
end
it
'filters by any milestone'
do
page
.
within
(
'.add-issues-modal'
)
do
click_button
'Milestone'
wait_for_ajax
click_link
'Any Milestone'
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.card'
,
count:
2
)
end
end
it
'filters by upcoming milestone'
do
it
'filters by upcoming milestone'
do
page
.
within
(
'.add-issues-modal'
)
do
set_filter
(
'milestone'
)
click_button
'Milestone'
click_filter_link
(
'Upcoming'
)
submit_filter
wait_for_ajax
click_link
'Upcoming'
page
.
within
(
'.add-issues-modal'
)
do
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.js-visual-token'
,
text:
'upcoming'
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
0
)
end
end
end
end
it
'filters by selected milestone'
do
it
'filters by selected milestone'
do
page
.
within
(
'.add-issues-modal'
)
do
set_filter
(
'milestone'
)
click_button
'Milestone'
click_filter_link
(
milestone
.
name
)
submit_filter
wait_for_ajax
click_link
milestone
.
name
page
.
within
(
'.add-issues-modal'
)
do
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.js-visual-token'
,
text:
milestone
.
name
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
end
end
end
end
...
@@ -207,44 +176,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
...
@@ -207,44 +176,28 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
visit_board
visit_board
end
end
it
'filters by any label'
do
page
.
within
(
'.add-issues-modal'
)
do
click_button
'Label'
wait_for_ajax
click_link
'Any Label'
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.card'
,
count:
2
)
end
end
it
'filters by no label'
do
it
'filters by no label'
do
page
.
within
(
'.add-issues-modal'
)
do
set_filter
(
'label'
)
click_button
'Label'
click_filter_link
(
'No Label'
)
submit_filter
wait_for_ajax
click_link
'No Label'
page
.
within
(
'.add-issues-modal'
)
do
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.js-visual-token'
,
text:
'none'
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
end
end
end
end
it
'filters by label'
do
it
'filters by label'
do
page
.
within
(
'.add-issues-modal'
)
do
set_filter
(
'label'
)
click_button
'Label'
click_filter_link
(
label
.
title
)
submit_filter
wait_for_ajax
click_link
label
.
title
page
.
within
(
'.add-issues-modal'
)
do
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.js-visual-token'
,
text:
label
.
title
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
end
end
end
end
...
@@ -256,4 +209,20 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
...
@@ -256,4 +209,20 @@ describe 'Issue Boards add issue modal filtering', :feature, :js do
click_button
(
'Add issues'
)
click_button
(
'Add issues'
)
end
end
def
set_filter
(
type
,
text
=
''
)
find
(
'.add-issues-modal .filtered-search'
).
native
.
send_keys
(
"
#{
type
}
:
#{
text
}
"
)
end
def
submit_filter
find
(
'.add-issues-modal .filtered-search'
).
native
.
send_keys
(
:enter
)
end
def
click_filter_link
(
link_text
)
page
.
within
(
'.add-issues-modal .filtered-search-input-container'
)
do
expect
(
page
).
to
have_button
(
link_text
)
click_button
(
link_text
)
end
end
end
end
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录