Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
cb0b32d7
Commit
cb0b32d7
authored
Mar 10, 2017
by
Phil Hughes
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Port of issue boards filter bar in modal window to EE
parent
623932ec
Changes
21
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
211 additions
and
460 deletions
+211
-460
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
+10
-3
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
No files found.
app/assets/javascripts/boards/components/modal/filters.js
View file @
cb0b32d7
/* 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
deleted
100644 → 0
View file @
623932ec
/* 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
deleted
100644 → 0
View file @
623932ec
/* 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
deleted
100644 → 0
View file @
623932ec
/* 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
View file @
cb0b32d7
/* 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
View file @
cb0b32d7
/* 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
View file @
cb0b32d7
/* 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
,
can
t
Edit
=
[])
{
constructor
(
store
,
updateUrl
=
false
,
canEdit
=
[])
{
super
(
'
boards
'
);
super
(
'
boards
'
);
this
.
store
=
store
;
this
.
store
=
store
;
...
@@ -19,13 +22,17 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
...
@@ -19,13 +22,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
View file @
cb0b32d7
/* 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
View file @
cb0b32d7
...
@@ -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
View file @
cb0b32d7
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
View file @
cb0b32d7
/* 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
View file @
cb0b32d7
...
@@ -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
View file @
cb0b32d7
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
View file @
cb0b32d7
/* 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
;
if
(
this
.
page
===
'
issues
'
||
this
.
page
===
'
boards
'
)
{
if
(
this
.
page
===
'
issues
'
||
this
.
page
===
'
boards
'
)
{
...
@@ -35,29 +37,29 @@
...
@@ -35,29 +37,29 @@
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
'
),
},
},
};
};
...
@@ -71,7 +73,7 @@
...
@@ -71,7 +73,7 @@
}
}
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
=
''
;
...
@@ -87,13 +89,13 @@
...
@@ -87,13 +89,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
View file @
cb0b32d7
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
(
page
===
'
issues
'
||
page
===
'
boards
'
)
{
if
(
page
===
'
issues
'
||
page
===
'
boards
'
)
{
...
@@ -137,7 +140,7 @@
...
@@ -137,7 +140,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
View file @
cb0b32d7
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
View file @
cb0b32d7
...
@@ -449,12 +449,9 @@
...
@@ -449,12 +449,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
View file @
cb0b32d7
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,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
View file @
cb0b32d7
-
type
=
local_assigns
.
fetch
(
:type
)
-
type
=
local_assigns
.
fetch
(
:type
)
-
board
=
local_assigns
.
fetch
(
:board
,
nil
)
-
board
=
local_assigns
.
fetch
(
:board
,
nil
)
-
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
}
-
if
type
==
:boards
&&
board
-
if
type
==
:boards
&&
board
#js-multiple-boards-switcher
.inline.boards-switcher
{
"v-cloak"
=>
true
}
#js-multiple-boards-switcher
.inline.boards-switcher
{
"v-cloak"
=>
true
}
=
render
"projects/boards/switcher"
,
board:
board
=
render
"projects/boards/switcher"
,
board:
board
...
@@ -18,7 +19,7 @@
...
@@ -18,7 +19,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'
)
...
@@ -120,7 +121,7 @@
...
@@ -120,7 +121,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
...
@@ -153,19 +154,20 @@
...
@@ -153,19 +154,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
View file @
cb0b32d7
...
@@ -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
View file @
cb0b32d7
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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment