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
0162c514
Commit
0162c514
authored
May 22, 2017
by
Eric Eastwood
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix locked milestone in boards being remove-able
Fix
https://gitlab.com/gitlab-org/gitlab-ee/issues/2433
parent
211679e2
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
102 additions
and
47 deletions
+102
-47
app/assets/javascripts/boards/boards_bundle.js
app/assets/javascripts/boards/boards_bundle.js
+1
-0
app/assets/javascripts/boards/components/modal/filters.js
app/assets/javascripts/boards/components/modal/filters.js
+1
-0
app/assets/javascripts/boards/filtered_search_boards.js
app/assets/javascripts/boards/filtered_search_boards.js
+1
-3
app/assets/javascripts/dispatcher.js
app/assets/javascripts/dispatcher.js
+4
-1
app/assets/javascripts/filtered_search/filtered_search_manager.js
...ts/javascripts/filtered_search/filtered_search_manager.js
+39
-17
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
...ascripts/filtered_search/filtered_search_visual_tokens.js
+16
-9
app/assets/stylesheets/framework/filters.scss
app/assets/stylesheets/framework/filters.scss
+17
-16
app/views/shared/issuable/_search_bar.html.haml
app/views/shared/issuable/_search_bar.html.haml
+2
-1
spec/features/boards/boards_spec.rb
spec/features/boards/boards_spec.rb
+18
-0
spec/javascripts/filtered_search/filtered_search_manager_spec.js
...vascripts/filtered_search/filtered_search_manager_spec.js
+3
-0
No files found.
app/assets/javascripts/boards/boards_bundle.js
View file @
0162c514
...
...
@@ -87,6 +87,7 @@ $(() => {
Store
.
rootPath
=
this
.
endpoint
;
this
.
filterManager
=
new
FilteredSearchBoards
(
Store
.
filter
,
true
,
[(
this
.
milestoneTitle
?
'
milestone
'
:
null
)]);
this
.
filterManager
.
setup
();
// Listen for updateTokens event
eventHub
.
$on
(
'
updateTokens
'
,
this
.
updateTokens
);
...
...
app/assets/javascripts/boards/components/modal/filters.js
View file @
0162c514
...
...
@@ -13,6 +13,7 @@ export default {
FilteredSearchContainer
.
container
=
this
.
$el
;
this
.
filteredSearch
=
new
FilteredSearchBoards
(
this
.
store
);
this
.
filteredSearch
.
setup
();
this
.
filteredSearch
.
removeTokens
();
this
.
filteredSearch
.
handleInputPlaceholder
();
this
.
filteredSearch
.
toggleClearSearchButton
();
...
...
app/assets/javascripts/boards/filtered_search_boards.js
View file @
0162c514
...
...
@@ -42,9 +42,7 @@ export default class FilteredSearchBoards extends gl.FilteredSearchManager {
this
.
filteredSearchInput
.
dispatchEvent
(
new
Event
(
'
input
'
));
}
canEdit
(
token
)
{
const
tokenName
=
token
.
querySelector
(
'
.name
'
).
textContent
.
trim
();
canEdit
(
tokenName
)
{
return
this
.
cantEdit
.
indexOf
(
tokenName
)
===
-
1
;
}
}
app/assets/javascripts/dispatcher.js
View file @
0162c514
...
...
@@ -130,7 +130,10 @@ import ApproversSelect from './approvers_select';
case
'
projects:merge_requests:index
'
:
case
'
projects:issues:index
'
:
if
(
gl
.
FilteredSearchManager
)
{
new
gl
.
FilteredSearchManager
(
page
===
'
projects:issues:index
'
?
'
issues
'
:
'
merge_requests
'
);
const
filteredSearchManager
=
new
gl
.
FilteredSearchManager
(
page
===
'
projects:issues:index
'
?
'
issues
'
:
'
merge_requests
'
,
);
filteredSearchManager
.
setup
();
}
Issuable
.
init
();
new
gl
.
IssuableBulkActions
({
...
...
app/assets/javascripts/filtered_search/filtered_search_manager.js
View file @
0162c514
...
...
@@ -6,6 +6,7 @@ import eventHub from './event_hub';
class
FilteredSearchManager
{
constructor
(
page
)
{
this
.
page
=
page
;
this
.
container
=
FilteredSearchContainer
.
container
;
this
.
filteredSearchInput
=
this
.
container
.
querySelector
(
'
.filtered-search
'
);
this
.
filteredSearchInputForm
=
this
.
filteredSearchInput
.
form
;
...
...
@@ -13,7 +14,7 @@ class FilteredSearchManager {
this
.
tokensContainer
=
this
.
container
.
querySelector
(
'
.tokens-container
'
);
this
.
filteredSearchTokenKeys
=
gl
.
FilteredSearchTokenKeys
;
if
(
page
===
'
issues
'
||
page
===
'
boards
'
)
{
if
(
this
.
page
===
'
issues
'
||
this
.
page
===
'
boards
'
)
{
this
.
filteredSearchTokenKeys
=
gl
.
FilteredSearchTokenKeysIssuesEE
;
}
...
...
@@ -21,16 +22,18 @@ class FilteredSearchManager {
isLocalStorageAvailable
:
RecentSearchesService
.
isAvailable
(),
allowedKeys
:
this
.
filteredSearchTokenKeys
.
getKeys
(),
});
const
searchHistoryDropdownElement
=
document
.
querySelector
(
'
.js-filtered-search-history-dropdown
'
);
const
projectPath
=
searchHistoryDropdownElement
?
searchHistoryDropdownElement
.
dataset
.
projectFullPath
:
'
project
'
;
this
.
searchHistoryDropdownElement
=
document
.
querySelector
(
'
.js-filtered-search-history-dropdown
'
);
const
projectPath
=
this
.
searchHistoryDropdownElement
?
this
.
searchHistoryDropdownElement
.
dataset
.
projectFullPath
:
'
project
'
;
let
recentSearchesPagePrefix
=
'
issue-recent-searches
'
;
if
(
page
===
'
merge_requests
'
)
{
if
(
this
.
page
===
'
merge_requests
'
)
{
recentSearchesPagePrefix
=
'
merge-request-recent-searches
'
;
}
const
recentSearchesKey
=
`
${
projectPath
}
-
${
recentSearchesPagePrefix
}
`
;
this
.
recentSearchesService
=
new
RecentSearchesService
(
recentSearchesKey
);
}
setup
()
{
// Fetch recent searches from localStorage
this
.
fetchingRecentSearchesPromise
=
this
.
recentSearchesService
.
fetch
()
.
catch
((
error
)
=>
{
...
...
@@ -51,12 +54,12 @@ class FilteredSearchManager {
if
(
this
.
filteredSearchInput
)
{
this
.
tokenizer
=
gl
.
FilteredSearchTokenizer
;
this
.
dropdownManager
=
new
gl
.
FilteredSearchDropdownManager
(
this
.
filteredSearchInput
.
getAttribute
(
'
data-base-endpoint
'
)
||
''
,
this
.
tokenizer
,
page
);
this
.
dropdownManager
=
new
gl
.
FilteredSearchDropdownManager
(
this
.
filteredSearchInput
.
getAttribute
(
'
data-base-endpoint
'
)
||
''
,
this
.
tokenizer
,
this
.
page
);
this
.
recentSearchesRoot
=
new
RecentSearchesRoot
(
this
.
recentSearchesStore
,
this
.
recentSearchesService
,
searchHistoryDropdownElement
,
this
.
searchHistoryDropdownElement
,
);
this
.
recentSearchesRoot
.
init
();
...
...
@@ -145,9 +148,9 @@ class FilteredSearchManager {
if
(
e
.
keyCode
===
8
||
e
.
keyCode
===
46
)
{
const
{
lastVisualToken
}
=
gl
.
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
if
(
this
.
filteredSearchInput
.
value
===
''
&&
lastVisualToken
)
{
if
(
this
.
canEdit
&&
!
this
.
canEdit
(
lastVisualToken
))
return
;
const
sanitizedTokenName
=
lastVisualToken
&&
lastVisualToken
.
querySelector
(
'
.name
'
).
textContent
.
trim
();
const
canEdit
=
sanitizedTokenName
&&
this
.
canEdit
&&
this
.
canEdit
(
sanitizedTokenName
)
;
if
(
this
.
filteredSearchInput
.
value
===
''
&&
lastVisualToken
&&
canEdit
)
{
this
.
filteredSearchInput
.
value
=
gl
.
FilteredSearchVisualTokens
.
getLastTokenPartial
();
gl
.
FilteredSearchVisualTokens
.
removeLastTokenPartial
();
}
...
...
@@ -246,10 +249,10 @@ class FilteredSearchManager {
editToken
(
e
)
{
const
token
=
e
.
target
.
closest
(
'
.js-visual-token
'
);
const
sanitizedTokenName
=
token
.
querySelector
(
'
.name
'
).
textContent
.
trim
();
const
canEdit
=
this
.
canEdit
&&
this
.
canEdit
(
sanitizedTokenName
);
if
(
this
.
canEdit
&&
!
this
.
canEdit
(
token
))
return
;
if
(
token
)
{
if
(
token
&&
canEdit
)
{
gl
.
FilteredSearchVisualTokens
.
editToken
(
token
);
this
.
tokenChange
();
}
...
...
@@ -399,7 +402,12 @@ class FilteredSearchManager {
if
(
condition
)
{
hasFilteredSearch
=
true
;
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
condition
.
tokenKey
,
condition
.
value
);
const
canEdit
=
this
.
canEdit
&&
this
.
canEdit
(
condition
.
tokenKey
);
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
condition
.
tokenKey
,
condition
.
value
,
canEdit
,
);
}
else
{
// Sanitize value since URL converts spaces into +
// Replace before decode so that we know what was originally + versus the encoded +
...
...
@@ -418,18 +426,27 @@ class FilteredSearchManager {
}
hasFilteredSearch
=
true
;
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
sanitizedKey
,
`
${
symbol
}${
quotationsToUse
}${
sanitizedValue
}${
quotationsToUse
}
`
);
const
canEdit
=
this
.
canEdit
&&
this
.
canEdit
(
sanitizedKey
);
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
sanitizedKey
,
`
${
symbol
}${
quotationsToUse
}${
sanitizedValue
}${
quotationsToUse
}
`
,
canEdit
,
);
}
else
if
(
!
match
&&
keyParam
===
'
assignee_id
'
)
{
const
id
=
parseInt
(
value
,
10
);
if
(
usernameParams
[
id
])
{
hasFilteredSearch
=
true
;
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
'
assignee
'
,
`@
${
usernameParams
[
id
]}
`
);
const
tokenName
=
'
assignee
'
;
const
canEdit
=
this
.
canEdit
&&
this
.
canEdit
(
tokenName
);
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
tokenName
,
`@
${
usernameParams
[
id
]}
`
,
canEdit
);
}
}
else
if
(
!
match
&&
keyParam
===
'
author_id
'
)
{
const
id
=
parseInt
(
value
,
10
);
if
(
usernameParams
[
id
])
{
hasFilteredSearch
=
true
;
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
'
author
'
,
`@
${
usernameParams
[
id
]}
`
);
const
tokenName
=
'
author
'
;
const
canEdit
=
this
.
canEdit
&&
this
.
canEdit
(
tokenName
);
gl
.
FilteredSearchVisualTokens
.
addFilterVisualToken
(
tokenName
,
`@
${
usernameParams
[
id
]}
`
,
canEdit
);
}
}
else
if
(
!
match
&&
keyParam
===
'
search
'
)
{
hasFilteredSearch
=
true
;
...
...
@@ -524,6 +541,11 @@ class FilteredSearchManager {
this
.
filteredSearchInput
.
dispatchEvent
(
new
CustomEvent
(
'
input
'
));
this
.
search
();
}
// eslint-disable-next-line class-methods-use-this
canEdit
()
{
return
true
;
}
}
window
.
gl
=
window
.
gl
||
{};
...
...
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
View file @
0162c514
...
...
@@ -36,15 +36,22 @@ class FilteredSearchVisualTokens {
}
}
static
createVisualTokenElementHTML
()
{
static
createVisualTokenElementHTML
(
canEdit
=
true
)
{
let
removeTokenMarkup
=
''
;
if
(
canEdit
)
{
removeTokenMarkup
=
`
<div class="remove-token" role="button">
<i class="fa fa-close"></i>
</div>
`
;
}
return
`
<div class="selectable" role="button">
<div class="name"></div>
<div class="value-container">
<div class="value"></div>
<div class="remove-token" role="button">
<i class="fa fa-close"></i>
</div>
${
removeTokenMarkup
}
</div>
</div>
`
;
...
...
@@ -84,13 +91,13 @@ class FilteredSearchVisualTokens {
}
}
static
addVisualTokenElement
(
name
,
value
,
isSearchTerm
)
{
static
addVisualTokenElement
(
name
,
value
,
isSearchTerm
,
canEdit
)
{
const
li
=
document
.
createElement
(
'
li
'
);
li
.
classList
.
add
(
'
js-visual-token
'
);
li
.
classList
.
add
(
isSearchTerm
?
'
filtered-search-term
'
:
'
filtered-search-token
'
);
if
(
value
)
{
li
.
innerHTML
=
FilteredSearchVisualTokens
.
createVisualTokenElementHTML
();
li
.
innerHTML
=
FilteredSearchVisualTokens
.
createVisualTokenElementHTML
(
canEdit
);
FilteredSearchVisualTokens
.
renderVisualTokenValue
(
li
,
name
,
value
);
}
else
{
li
.
innerHTML
=
'
<div class="name"></div>
'
;
...
...
@@ -114,20 +121,20 @@ class FilteredSearchVisualTokens {
}
}
static
addFilterVisualToken
(
tokenName
,
tokenValue
)
{
static
addFilterVisualToken
(
tokenName
,
tokenValue
,
canEdit
)
{
const
{
lastVisualToken
,
isLastVisualTokenValid
}
=
FilteredSearchVisualTokens
.
getLastVisualTokenBeforeInput
();
const
addVisualTokenElement
=
FilteredSearchVisualTokens
.
addVisualTokenElement
;
if
(
isLastVisualTokenValid
)
{
addVisualTokenElement
(
tokenName
,
tokenValue
,
false
);
addVisualTokenElement
(
tokenName
,
tokenValue
,
false
,
canEdit
);
}
else
{
const
previousTokenName
=
lastVisualToken
.
querySelector
(
'
.name
'
).
innerText
;
const
tokensContainer
=
FilteredSearchContainer
.
container
.
querySelector
(
'
.tokens-container
'
);
tokensContainer
.
removeChild
(
lastVisualToken
);
const
value
=
tokenValue
||
tokenName
;
addVisualTokenElement
(
previousTokenName
,
value
,
false
);
addVisualTokenElement
(
previousTokenName
,
value
,
false
,
canEdit
);
}
}
...
...
app/assets/stylesheets/framework/filters.scss
View file @
0162c514
...
...
@@ -104,6 +104,22 @@
padding
:
2px
7px
;
}
.name
{
background-color
:
$filter-name-resting-color
;
color
:
$filter-name-text-color
;
border-radius
:
2px
0
0
2px
;
margin-right
:
1px
;
text-transform
:
capitalize
;
}
.value-container
{
background-color
:
$white-normal
;
color
:
$filter-value-text-color
;
border-radius
:
0
2px
2px
0
;
margin-right
:
5px
;
padding-right
:
8px
;
}
.value
{
padding-right
:
0
;
}
...
...
@@ -111,7 +127,7 @@
.remove-token
{
display
:
inline-block
;
padding-left
:
4px
;
padding-right
:
8px
;
padding-right
:
0
;
.fa-close
{
color
:
$gl-text-color-secondary
;
...
...
@@ -132,21 +148,6 @@
}
}
.name
{
background-color
:
$filter-name-resting-color
;
color
:
$filter-name-text-color
;
border-radius
:
2px
0
0
2px
;
margin-right
:
1px
;
text-transform
:
capitalize
;
}
.value-container
{
background-color
:
$white-normal
;
color
:
$filter-value-text-color
;
border-radius
:
0
2px
2px
0
;
margin-right
:
5px
;
}
.selected
{
.name
{
background-color
:
$filter-name-selected-color
;
...
...
app/views/shared/issuable/_search_bar.html.haml
View file @
0162c514
...
...
@@ -177,7 +177,8 @@
$
(
document
).
off
(
'
page:restore
'
).
on
(
'
page:restore
'
,
function
(
event
)
{
if
(
gl
.
FilteredSearchManager
)
{
new
gl
.
FilteredSearchManager
();
const
filteredSearchManager
=
new
gl
.
FilteredSearchManager
();
filteredSearchManager
.
setup
();
}
Issuable
.
init
();
new
gl
.
IssuableBulkActions
({
...
...
spec/features/boards/boards_spec.rb
View file @
0162c514
...
...
@@ -4,7 +4,9 @@ describe 'Issue Boards', feature: true, js: true do
include
DragTo
let
(
:project
)
{
create
(
:empty_project
,
:public
)
}
let
(
:milestone
)
{
create
(
:milestone
,
title:
"v2.2"
,
project:
project
)
}
let!
(
:board
)
{
create
(
:board
,
project:
project
)
}
let!
(
:board_with_milestone
)
{
create
(
:board
,
project:
project
,
milestone:
milestone
)
}
let
(
:user
)
{
create
(
:user
)
}
let!
(
:user2
)
{
create
(
:user
)
}
...
...
@@ -509,6 +511,22 @@ describe 'Issue Boards', feature: true, js: true do
end
end
context
'locked milestone'
do
before
do
visit
namespace_project_board_path
(
project
.
namespace
,
project
,
board_with_milestone
)
wait_for_requests
end
it
'should not have remove button'
do
expect
(
page
).
to
have_selector
(
'.js-visual-token .remove-token'
,
count:
0
)
end
it
'should not be able to be backspaced'
do
find
(
'.input-token .filtered-search'
).
native
.
send_key
(
:backspace
)
expect
(
page
).
to
have_selector
(
'.js-visual-token'
,
count:
1
)
end
end
context
'keyboard shortcuts'
do
before
do
visit
namespace_project_boards_path
(
project
.
namespace
,
project
)
...
...
spec/javascripts/filtered_search/filtered_search_manager_spec.js
View file @
0162c514
...
...
@@ -57,6 +57,7 @@ describe('Filtered Search Manager', () => {
input
=
document
.
querySelector
(
'
.filtered-search
'
);
tokensContainer
=
document
.
querySelector
(
'
.tokens-container
'
);
manager
=
new
gl
.
FilteredSearchManager
();
manager
.
setup
();
});
afterEach
(()
=>
{
...
...
@@ -72,6 +73,7 @@ describe('Filtered Search Manager', () => {
spyOn
(
recentSearchesStoreSrc
,
'
default
'
);
filteredSearchManager
=
new
gl
.
FilteredSearchManager
();
filteredSearchManager
.
setup
();
return
filteredSearchManager
;
});
...
...
@@ -89,6 +91,7 @@ describe('Filtered Search Manager', () => {
spyOn
(
window
,
'
Flash
'
);
filteredSearchManager
=
new
gl
.
FilteredSearchManager
();
filteredSearchManager
.
setup
();
expect
(
window
.
Flash
).
not
.
toHaveBeenCalled
();
});
...
...
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