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
c4c9abeb
Commit
c4c9abeb
authored
Jun 08, 2018
by
Dennis Tang
Committed by
Mike Greiling
Jun 08, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Nonnegative integer weights in issuable sidebar
parent
8e323420
Changes
24
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
543 additions
and
335 deletions
+543
-335
app/assets/javascripts/droplab/plugins/custom_number.js
app/assets/javascripts/droplab/plugins/custom_number.js
+62
-0
app/assets/javascripts/filtered_search/dropdown_weight.js
app/assets/javascripts/filtered_search/dropdown_weight.js
+37
-0
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
...ripts/filtered_search/filtered_search_dropdown_manager.js
+15
-10
app/assets/stylesheets/pages/issuable.scss
app/assets/stylesheets/pages/issuable.scss
+9
-0
app/views/shared/issuable/_search_bar.html.haml
app/views/shared/issuable/_search_bar.html.haml
+7
-8
doc/workflow/issue_weight.md
doc/workflow/issue_weight.md
+4
-2
doc/workflow/issue_weight/issue.png
doc/workflow/issue_weight/issue.png
+0
-0
ee/app/assets/javascripts/filtered_search/filtered_search_token_keys_issues.js
...ipts/filtered_search/filtered_search_token_keys_issues.js
+5
-5
ee/app/assets/javascripts/sidebar/components/weight/sidebar_weight.vue
.../javascripts/sidebar/components/weight/sidebar_weight.vue
+27
-29
ee/app/assets/javascripts/sidebar/components/weight/weight.vue
...p/assets/javascripts/sidebar/components/weight/weight.vue
+163
-197
ee/app/models/ee/issue.rb
ee/app/models/ee/issue.rb
+5
-3
ee/app/services/ee/quick_actions/interpret_service.rb
ee/app/services/ee/quick_actions/interpret_service.rb
+2
-2
ee/changelogs/unreleased/3969-nonnegative-integer-weights.yml
...hangelogs/unreleased/3969-nonnegative-integer-weights.yml
+5
-0
ee/spec/features/boards/scoped_issue_board_spec.rb
ee/spec/features/boards/scoped_issue_board_spec.rb
+3
-3
ee/spec/features/boards/sidebar_spec.rb
ee/spec/features/boards/sidebar_spec.rb
+2
-3
ee/spec/features/issues/filtered_search/dropdown_weight_spec.rb
...c/features/issues/filtered_search/dropdown_weight_spec.rb
+4
-4
ee/spec/features/issues/issue_sidebar_spec.rb
ee/spec/features/issues/issue_sidebar_spec.rb
+54
-0
ee/spec/models/issue_spec.rb
ee/spec/models/issue_spec.rb
+23
-0
spec/features/issues/issue_sidebar_spec.rb
spec/features/issues/issue_sidebar_spec.rb
+0
-29
spec/features/issues_spec.rb
spec/features/issues_spec.rb
+1
-1
spec/javascripts/filtered_search/filtered_search_token_keys_issues_spec.js
...filtered_search/filtered_search_token_keys_issues_spec.js
+1
-1
spec/javascripts/sidebar/ee_mock_data.js
spec/javascripts/sidebar/ee_mock_data.js
+14
-8
spec/javascripts/sidebar/ee_sidebar_store_spec.js
spec/javascripts/sidebar/ee_sidebar_store_spec.js
+2
-2
spec/javascripts/sidebar/weight_spec.js
spec/javascripts/sidebar/weight_spec.js
+98
-28
No files found.
app/assets/javascripts/droplab/plugins/custom_number.js
0 → 100644
View file @
c4c9abeb
const
CustomNumber
=
{
keydown
(
e
)
{
if
(
this
.
destroyed
)
return
;
const
{
list
}
=
e
.
detail
.
hook
;
const
{
value
}
=
e
.
detail
.
hook
.
trigger
;
const
parsedValue
=
Number
(
value
);
const
config
=
e
.
detail
.
hook
.
config
.
CustomNumber
;
const
{
defaultOptions
}
=
config
;
const
isValidNumber
=
!
Number
.
isNaN
(
parsedValue
)
&&
value
!==
''
;
const
customOption
=
[{
id
:
parsedValue
,
title
:
parsedValue
}];
const
defaultDropdownOptions
=
defaultOptions
.
map
(
o
=>
({
id
:
o
,
title
:
o
}));
list
.
setData
(
isValidNumber
?
customOption
:
defaultDropdownOptions
);
list
.
currentIndex
=
0
;
},
debounceKeydown
(
e
)
{
if
(
[
13
,
// enter
16
,
// shift
17
,
// ctrl
18
,
// alt
20
,
// caps lock
37
,
// left arrow
38
,
// up arrow
39
,
// right arrow
40
,
// down arrow
91
,
// left window
92
,
// right window
93
,
// select
].
indexOf
(
e
.
detail
.
which
||
e
.
detail
.
keyCode
)
>
-
1
)
return
;
if
(
this
.
timeout
)
clearTimeout
(
this
.
timeout
);
this
.
timeout
=
setTimeout
(
this
.
keydown
.
bind
(
this
,
e
),
200
);
},
init
(
hook
)
{
this
.
hook
=
hook
;
this
.
destroyed
=
false
;
this
.
eventWrapper
=
{};
this
.
eventWrapper
.
debounceKeydown
=
this
.
debounceKeydown
.
bind
(
this
);
this
.
hook
.
trigger
.
addEventListener
(
'
keydown.dl
'
,
this
.
eventWrapper
.
debounceKeydown
);
this
.
hook
.
trigger
.
addEventListener
(
'
mousedown.dl
'
,
this
.
eventWrapper
.
debounceKeydown
);
},
destroy
()
{
if
(
this
.
timeout
)
clearTimeout
(
this
.
timeout
);
this
.
destroyed
=
true
;
this
.
hook
.
trigger
.
removeEventListener
(
'
keydown.dl
'
,
this
.
eventWrapper
.
debounceKeydown
);
this
.
hook
.
trigger
.
removeEventListener
(
'
mousedown.dl
'
,
this
.
eventWrapper
.
debounceKeydown
);
},
};
export
default
CustomNumber
;
app/assets/javascripts/filtered_search/dropdown_weight.js
0 → 100644
View file @
c4c9abeb
import
FilteredSearchDropdown
from
'
./filtered_search_dropdown
'
;
import
DropdownUtils
from
'
./dropdown_utils
'
;
import
CustomNumber
from
'
../droplab/plugins/custom_number
'
;
export
default
class
DropdownWeight
extends
FilteredSearchDropdown
{
constructor
(
options
=
{})
{
super
(
options
);
this
.
defaultOptions
=
Array
.
from
(
Array
(
21
).
keys
());
this
.
config
=
{
CustomNumber
:
{
defaultOptions
:
this
.
defaultOptions
,
},
};
}
itemClicked
(
e
)
{
super
.
itemClicked
(
e
,
selected
=>
{
const
title
=
selected
.
querySelector
(
'
.js-data-value
'
).
innerText
.
trim
();
return
`
${
DropdownUtils
.
getEscapedText
(
title
)}
`
;
});
}
renderContent
(
forceShowList
=
false
)
{
this
.
droplab
.
changeHookList
(
this
.
hookId
,
this
.
dropdown
,
[
CustomNumber
],
this
.
config
);
const
defaultDropdownOptions
=
this
.
defaultOptions
.
map
(
o
=>
({
id
:
o
,
title
:
o
}));
this
.
droplab
.
setData
(
defaultDropdownOptions
);
super
.
renderContent
(
forceShowList
);
}
init
()
{
this
.
droplab
.
addHook
(
this
.
input
,
this
.
dropdown
,
[
CustomNumber
],
this
.
config
).
init
();
}
}
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
View file @
c4c9abeb
...
...
@@ -7,6 +7,7 @@ import DropdownHint from './dropdown_hint';
import
DropdownEmoji
from
'
./dropdown_emoji
'
;
import
DropdownNonUser
from
'
./dropdown_non_user
'
;
import
DropdownUser
from
'
./dropdown_user
'
;
import
DropdownWeight
from
'
./dropdown_weight
'
;
import
FilteredSearchVisualTokens
from
'
./filtered_search_visual_tokens
'
;
export
default
class
FilteredSearchDropdownManager
{
...
...
@@ -92,12 +93,12 @@ export default class FilteredSearchDropdownManager {
},
weight
:
{
reference
:
null
,
gl
:
Dropdown
NonUser
,
gl
:
Dropdown
Weight
,
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-weight
'
),
},
};
supportedTokens
.
forEach
(
(
type
)
=>
{
supportedTokens
.
forEach
(
type
=>
{
if
(
availableMappings
[
type
])
{
allowedMappings
[
type
]
=
availableMappings
[
type
];
}
...
...
@@ -152,13 +153,16 @@ export default class FilteredSearchDropdownManager {
updateDropdownOffset
(
key
)
{
// Always align dropdown with the input field
let
offset
=
this
.
filteredSearchInput
.
getBoundingClientRect
().
left
-
this
.
container
.
querySelector
(
'
.scroll-container
'
).
getBoundingClientRect
().
left
;
let
offset
=
this
.
filteredSearchInput
.
getBoundingClientRect
().
left
-
this
.
container
.
querySelector
(
'
.scroll-container
'
).
getBoundingClientRect
().
left
;
const
maxInputWidth
=
240
;
const
currentDropdownWidth
=
this
.
mapping
[
key
].
element
.
clientWidth
||
maxInputWidth
;
// Make sure offset never exceeds the input container
const
offsetMaxWidth
=
this
.
container
.
querySelector
(
'
.scroll-container
'
).
clientWidth
-
currentDropdownWidth
;
const
offsetMaxWidth
=
this
.
container
.
querySelector
(
'
.scroll-container
'
).
clientWidth
-
currentDropdownWidth
;
if
(
offsetMaxWidth
<
offset
)
{
offset
=
offsetMaxWidth
;
}
...
...
@@ -184,8 +188,7 @@ export default class FilteredSearchDropdownManager {
const
glArguments
=
Object
.
assign
({},
defaultArguments
,
extraArguments
);
// Passing glArguments to `new glClass(<arguments>)`
mappingKey
.
reference
=
new
(
Function
.
prototype
.
bind
.
apply
(
glClass
,
[
null
,
glArguments
]))();
mappingKey
.
reference
=
new
(
Function
.
prototype
.
bind
.
apply
(
glClass
,
[
null
,
glArguments
]))();
}
if
(
firstLoad
)
{
...
...
@@ -212,8 +215,8 @@ export default class FilteredSearchDropdownManager {
}
const
match
=
this
.
filteredSearchTokenKeys
.
searchByKey
(
dropdownName
.
toLowerCase
());
const
shouldOpenFilterDropdown
=
match
&&
this
.
currentDropdown
!==
match
.
key
&&
this
.
mapping
[
match
.
key
];
const
shouldOpenFilterDropdown
=
match
&&
this
.
currentDropdown
!==
match
.
key
&&
this
.
mapping
[
match
.
key
];
const
shouldOpenHintDropdown
=
!
match
&&
this
.
currentDropdown
!==
'
hint
'
;
if
(
shouldOpenFilterDropdown
||
shouldOpenHintDropdown
)
{
...
...
@@ -224,8 +227,10 @@ export default class FilteredSearchDropdownManager {
setDropdown
()
{
const
query
=
DropdownUtils
.
getSearchQuery
(
true
);
const
{
lastToken
,
searchToken
}
=
this
.
tokenizer
.
processTokens
(
query
,
this
.
filteredSearchTokenKeys
.
getKeys
());
const
{
lastToken
,
searchToken
}
=
this
.
tokenizer
.
processTokens
(
query
,
this
.
filteredSearchTokenKeys
.
getKeys
(),
);
if
(
this
.
currentDropdown
)
{
this
.
updateCurrentDropdownOffset
();
...
...
app/assets/stylesheets/pages/issuable.scss
View file @
c4c9abeb
...
...
@@ -215,6 +215,15 @@
}
}
}
&
.weight
{
.gl-field-error
{
margin-top
:
$gl-padding-8
;
margin-left
:
-6px
;
display
:
flex
;
align-items
:
center
;
}
}
}
.block-first
{
...
...
app/views/shared/issuable/_search_bar.html.haml
View file @
c4c9abeb
...
...
@@ -107,17 +107,16 @@
-
if
type
==
:issues
||
type
==
:boards
||
type
==
:boards_modal
#js-dropdown-weight
.filtered-search-input-dropdown-menu.dropdown-menu
%ul
{
'data-dropdown'
=>
true
}
%li
.filter-dropdown-item
{
'data-value'
=>
'
n
one'
}
%li
.filter-dropdown-item
{
'data-value'
=>
'
N
one'
}
%button
.btn.btn-link
No
Weight
%li
.filter-dropdown-item
{
'data-value'
=>
'
a
ny'
}
No
ne
%li
.filter-dropdown-item
{
'data-value'
=>
'
A
ny'
}
%button
.btn.btn-link
Any
Weight
Any
%li
.divider.droplab-item-ignore
%ul
.filter-dropdown
{
'data-dropdown'
=>
true
}
-
Issue
.
weight_filter_options
.
each
do
|
weight
|
%li
.filter-dropdown-item
{
'data-value'
=>
"#{weight}"
}
%button
.btn.btn-link
=
weight
%ul
.filter-dropdown
{
data:
{
dropdown:
true
,
dynamic:
true
}
}
%li
.filter-dropdown-item
{
data:
{
value:
'
{{
id
}}
'
}
}
%button
.btn.btn-link
{{title}}
%button
.clear-search.hidden
{
type:
'button'
}
=
icon
(
'times'
)
...
...
doc/workflow/issue_weight.md
View file @
c4c9abeb
# Issue Weight **[STARTER]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/76)
in
[
GitLab Starter
](
https://about.gitlab.com/products/
)
8.3.
>
in [GitLab Starter](https://about.gitlab.com/products/) 8.3.
When you have a lot of issues, it can be hard to get an overview.
By adding a weight to each issue, you can get a better idea of how much time,
value or complexity a given issue has or will cost.
You can set the weight of an issue during its creation, by simply changing the
value in the dropdown menu. You can set it to a numeric value from 1 to 9.
value in the dropdown menu. You can set it to a non-negative integer
value from 0, 1, 2, and so on. You can remove weight from an issue
as well.
This value will appear on the right sidebar of an individual issue, as well as
in the issues page next to a distinctive balance scale icon.
...
...
doc/workflow/issue_weight/issue.png
View replaced file @
8e323420
View file @
c4c9abeb
158 KB
|
W:
|
H:
238 KB
|
W:
|
H:
2-up
Swipe
Onion skin
ee/app/assets/javascripts/filtered_search/filtered_search_token_keys_issues.js
View file @
c4c9abeb
...
...
@@ -6,17 +6,17 @@ const weightTokenKey = {
param
:
''
,
symbol
:
''
,
icon
:
'
balance-scale
'
,
tag
:
'
weight
'
,
tag
:
'
number
'
,
};
const
weightConditions
=
[{
url
:
'
weight=No
+Weight
'
,
url
:
'
weight=No
ne
'
,
tokenKey
:
'
weight
'
,
value
:
'
n
one
'
,
value
:
'
N
one
'
,
},
{
url
:
'
weight=Any
+Weight
'
,
url
:
'
weight=Any
'
,
tokenKey
:
'
weight
'
,
value
:
'
a
ny
'
,
value
:
'
A
ny
'
,
}];
const
alternativeTokenKeys
=
[{
...
...
ee/app/assets/javascripts/sidebar/components/weight/sidebar_weight.vue
View file @
c4c9abeb
<
script
>
import
Flash
from
'
~/flash
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
weightComponent
from
'
./weight.vue
'
;
import
Flash
from
'
~/flash
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
weightComponent
from
'
./weight.vue
'
;
export
default
{
components
:
{
weight
:
weightComponent
,
},
props
:
{
mediator
:
{
required
:
true
,
type
:
Object
,
validator
(
mediatorObject
)
{
return
mediatorObject
.
updateWeight
&&
mediatorObject
.
store
;
},
export
default
{
components
:
{
weight
:
weightComponent
,
},
props
:
{
mediator
:
{
required
:
true
,
type
:
Object
,
validator
(
mediatorObject
)
{
return
mediatorObject
.
updateWeight
&&
mediatorObject
.
store
;
},
},
},
created
()
{
eventHub
.
$on
(
'
updateWeight
'
,
this
.
onUpdateWeight
);
},
created
()
{
eventHub
.
$on
(
'
updateWeight
'
,
this
.
onUpdateWeight
);
},
beforeDestroy
()
{
eventHub
.
$off
(
'
updateWeight
'
,
this
.
onUpdateWeight
);
},
beforeDestroy
()
{
eventHub
.
$off
(
'
updateWeight
'
,
this
.
onUpdateWeight
);
},
methods
:
{
onUpdateWeight
(
newWeight
)
{
this
.
mediator
.
updateWeight
(
newWeight
)
.
catch
(()
=>
{
Flash
(
'
Error occurred while updating the issue weight
'
);
});
},
methods
:
{
onUpdateWeight
(
newWeight
)
{
this
.
mediator
.
updateWeight
(
newWeight
).
catch
(()
=>
{
Flash
(
'
Error occurred while updating the issue weight
'
);
});
},
};
},
};
</
script
>
<
template
>
...
...
@@ -41,7 +40,6 @@
:fetching=
"mediator.store.isFetching.weight"
:loading=
"mediator.store.isLoading.weight"
:weight=
"mediator.store.weight"
:weight-options=
"mediator.store.weightOptions"
:weight-none-value=
"mediator.store.weightNoneValue"
:editable=
"mediator.store.editable"
/>
...
...
ee/app/assets/javascripts/sidebar/components/weight/weight.vue
View file @
c4c9abeb
This diff is collapsed.
Click to expand it.
ee/app/models/ee/issue.rb
View file @
c4c9abeb
...
...
@@ -4,13 +4,15 @@ module EE
extend
::
Gitlab
::
Utils
::
Override
prepended
do
WEIGHT_RANGE
=
1
..
9
WEIGHT_RANGE
=
0
..
20
WEIGHT_ALL
=
'Everything'
.
freeze
WEIGHT_ANY
=
'Any
Weight
'
.
freeze
WEIGHT_NONE
=
'No
Weight
'
.
freeze
WEIGHT_ANY
=
'Any'
.
freeze
WEIGHT_NONE
=
'No
ne
'
.
freeze
scope
:order_weight_desc
,
->
{
reorder
::
Gitlab
::
Database
.
nulls_last_order
(
'weight'
,
'DESC'
)
}
scope
:order_weight_asc
,
->
{
reorder
::
Gitlab
::
Database
.
nulls_last_order
(
'weight'
)
}
validates
:weight
,
allow_nil:
true
,
numericality:
{
greater_than_or_equal_to:
0
}
end
# override
...
...
ee/app/services/ee/quick_actions/interpret_service.rb
View file @
c4c9abeb
...
...
@@ -21,13 +21,13 @@ module EE
explanation
do
|
weight
|
"Sets weight to
#{
weight
}
."
if
weight
end
params
::
Issue
::
WEIGHT_RANGE
.
to_s
.
squeeze
(
'.'
).
tr
(
'.'
,
'-'
)
params
"0, 1, 2, …"
condition
do
issuable
.
supports_weight?
&&
current_user
.
can?
(
:"admin_
#{
issuable
.
to_ability_name
}
"
,
issuable
)
end
parse_params
do
|
weight
|
weight
.
to_i
if
::
Issue
.
weight_filter_options
.
include?
(
weight
.
to_i
)
weight
.
to_i
if
weight
.
to_i
>
0
end
command
:weight
do
|
weight
|
@updates
[
:weight
]
=
weight
if
weight
...
...
ee/changelogs/unreleased/3969-nonnegative-integer-weights.yml
0 → 100644
View file @
c4c9abeb
---
title
:
Add support for non-negative integer weight values in issuable sidebar
merge_request
:
author
:
type
:
changed
ee/spec/features/boards/scoped_issue_board_spec.rb
View file @
c4c9abeb
...
...
@@ -170,8 +170,8 @@ describe 'Scoped issue boards', :js do
end
end
it
'creates board filtering by "Any
weight"
'
do
create_board_weight
(
'Any
Weight
'
)
it
'creates board filtering by "Any
" weight
'
do
create_board_weight
(
'Any'
)
expect
(
page
).
to
have_selector
(
'.board-card'
,
count:
4
)
end
...
...
@@ -356,7 +356,7 @@ describe 'Scoped issue boards', :js do
end
it
'sets board to Any weight'
do
update_board_weight
(
'Any
Weight
'
)
update_board_weight
(
'Any'
)
expect
(
page
).
to
have_selector
(
'.board-card'
,
count:
4
)
end
...
...
ee/spec/features/boards/sidebar_spec.rb
View file @
c4c9abeb
...
...
@@ -186,7 +186,7 @@ describe 'Issue Boards', :js do
page
.
within
'.weight'
do
click_link
'Edit'
click_link
'1'
find
(
'.block.weight input'
).
send_keys
1
,
:enter
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'1'
...
...
@@ -212,8 +212,7 @@ describe 'Issue Boards', :js do
wait_for_requests
page
.
within
'.weight'
do
click_link
'Edit'
click_link
'No Weight'
click_link
'remove weight'
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'None'
...
...
ee/spec/features/issues/filtered_search/dropdown_weight_spec.rb
View file @
c4c9abeb
...
...
@@ -17,7 +17,7 @@ describe 'Dropdown weight', :js do
end
def
click_weight
(
text
)
find
(
'#js-dropdown-weight .filter-dropdown .filter-dropdown-item'
,
text:
text
).
click
find
(
'#js-dropdown-weight .filter-dropdown .filter-dropdown-item'
,
text:
text
,
exact_text:
true
).
click
end
def
click_static_weight
(
text
)
...
...
@@ -49,7 +49,7 @@ describe 'Dropdown weight', :js do
it
'should load all the weights when opened'
do
send_keys_to_filtered_search
(
'weight:'
)
expect
(
page
.
all
(
'#js-dropdown-weight .filter-dropdown .filter-dropdown-item'
).
size
).
to
eq
(
9
)
expect
(
page
.
all
(
'#js-dropdown-weight .filter-dropdown .filter-dropdown-item'
).
size
).
to
eq
(
21
)
end
end
...
...
@@ -83,10 +83,10 @@ describe 'Dropdown weight', :js do
end
it
'fills in `no weight`'
do
click_static_weight
(
'No
Weight
'
)
click_static_weight
(
'No
ne
'
)
expect
(
page
).
to
have_css
(
js_dropdown_weight
,
visible:
false
)
expect_tokens
([{
name:
'Weight'
,
value:
'
n
one'
}])
expect_tokens
([{
name:
'Weight'
,
value:
'
N
one'
}])
expect_filtered_search_input_empty
end
end
...
...
ee/spec/features/issues/issue_sidebar_spec.rb
0 → 100644
View file @
c4c9abeb
require
'rails_helper'
feature
'Issue Sidebar'
do
include
MobileHelpers
let
(
:group
)
{
create
(
:group
,
:nested
)
}
let
(
:project
)
{
create
(
:project
,
:public
,
namespace:
group
)
}
let!
(
:user
)
{
create
(
:user
)}
let!
(
:label
)
{
create
(
:label
,
project:
project
,
title:
'bug'
)
}
let
(
:issue
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
label
])
}
before
do
sign_in
(
user
)
end
context
'updating weight'
,
:js
do
before
do
project
.
add_master
(
user
)
visit_issue
(
project
,
issue
)
end
it
'updates weight in sidebar to 1'
do
page
.
within
'.weight'
do
click_link
'Edit'
find
(
'input'
).
send_keys
1
,
:enter
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'1'
end
end
end
it
'updates weight in sidebar to no weight'
do
page
.
within
'.weight'
do
click_link
'Edit'
find
(
'input'
).
send_keys
1
,
:enter
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'1'
end
click_link
'remove weight'
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'None'
end
end
end
end
def
visit_issue
(
project
,
issue
)
visit
project_issue_path
(
project
,
issue
)
end
end
ee/spec/models/issue_spec.rb
View file @
c4c9abeb
...
...
@@ -4,6 +4,29 @@ describe Issue do
using
RSpec
::
Parameterized
::
TableSyntax
include
ExternalAuthorizationServiceHelpers
describe
'validations'
do
subject
{
build
(
:issue
)
}
describe
'weight'
do
it
'is not valid when negative number'
do
subject
.
weight
=
-
1
expect
(
subject
).
not_to
be_valid
expect
(
subject
.
errors
[
:weight
]).
not_to
be_empty
end
it
'is valid when non-negative'
do
subject
.
weight
=
0
expect
(
subject
).
to
be_valid
subject
.
weight
=
1
expect
(
subject
).
to
be_valid
end
end
end
describe
'#allows_multiple_assignees?'
do
it
'does not allow multiple assignees without license'
do
stub_licensed_features
(
multiple_issue_assignees:
false
)
...
...
spec/features/issues/issue_sidebar_spec.rb
View file @
c4c9abeb
...
...
@@ -225,35 +225,6 @@ feature 'Issue Sidebar' do
end
end
context
'updating weight'
,
:js
do
before
do
project
.
add_master
(
user
)
visit_issue
(
project
,
issue
)
end
it
'updates weight in sidebar to 1'
do
page
.
within
'.weight'
do
click_link
'Edit'
click_link
'1'
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'1'
end
end
end
it
'updates weight in sidebar to no weight'
do
page
.
within
'.weight'
do
click_link
'Edit'
click_link
'No Weight'
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'None'
end
end
end
end
def
visit_issue
(
project
,
issue
)
visit
project_issue_path
(
project
,
issue
)
end
...
...
spec/features/issues_spec.rb
View file @
c4c9abeb
...
...
@@ -546,7 +546,7 @@ describe 'Issues' do
expect
(
page
).
to
have_content
"None"
click_link
'Edit'
find
(
'.
dropdown-content a'
,
text:
'1'
).
click
find
(
'.
block.weight input'
).
send_keys
1
,
:enter
page
.
within
(
'.value'
)
do
expect
(
page
).
to
have_content
"1"
...
...
spec/javascripts/filtered_search/filtered_search_token_keys_issues_spec.js
View file @
c4c9abeb
...
...
@@ -7,7 +7,7 @@ describe('Filtered Search Token Keys (Issues EE)', () => {
param
:
''
,
symbol
:
''
,
icon
:
'
balance-scale
'
,
tag
:
'
weight
'
,
tag
:
'
number
'
,
};
describe
(
'
get
'
,
()
=>
{
...
...
spec/javascripts/sidebar/ee_mock_data.js
View file @
c4c9abeb
...
...
@@ -9,7 +9,8 @@ RESPONSE_MAP.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'] =
username
:
'
user0
'
,
id
:
22
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80
\
u0026d=identicon
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/user0
'
,
},
{
...
...
@@ -17,7 +18,8 @@ RESPONSE_MAP.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'] =
username
:
'
tajuana
'
,
id
:
18
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80
\
u0026d=identicon
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/tajuana
'
,
},
{
...
...
@@ -25,7 +27,8 @@ RESPONSE_MAP.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'] =
username
:
'
michaele.will
'
,
id
:
16
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80
\
u0026d=identicon
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/michaele.will
'
,
},
],
...
...
@@ -37,7 +40,8 @@ RESPONSE_MAP.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'] =
username
:
'
user0
'
,
id
:
22
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80
\
u0026d=identicon
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/user0
'
,
},
{
...
...
@@ -45,7 +49,8 @@ RESPONSE_MAP.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'] =
username
:
'
tajuana
'
,
id
:
18
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80
\
u0026d=identicon
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/tajuana
'
,
},
{
...
...
@@ -53,7 +58,8 @@ RESPONSE_MAP.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'] =
username
:
'
michaele.will
'
,
id
:
16
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80
\
u0026d=identicon
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/michaele.will
'
,
},
],
...
...
@@ -67,8 +73,8 @@ export default {
...
CEMockData
,
mediator
:
{
...
CEMockData
.
mediator
,
weightOptions
:
[
'
No
Weight
'
,
0
,
1
,
3
],
weightNoneValue
:
'
No
Weight
'
,
weightOptions
:
[
'
No
ne
'
,
0
,
1
,
3
],
weightNoneValue
:
'
No
ne
'
,
},
responseMap
:
RESPONSE_MAP
,
};
spec/javascripts/sidebar/ee_sidebar_store_spec.js
View file @
c4c9abeb
...
...
@@ -6,8 +6,8 @@ describe('EE Sidebar store', () => {
beforeEach
(()
=>
{
store
=
new
SidebarStore
({
weight
:
null
,
weightOptions
:
[
'
No
Weight
'
,
0
,
1
,
3
],
weightNoneValue
:
'
No
Weight
'
,
weightOptions
:
[
'
No
ne
'
,
0
,
1
,
3
],
weightNoneValue
:
'
No
ne
'
,
});
});
...
...
spec/javascripts/sidebar/weight_spec.js
View file @
c4c9abeb
import
Vue
from
'
vue
'
;
import
weight
from
'
ee/sidebar/components/weight/weight.vue
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
{
ENTER_KEY_CODE
}
from
'
~/lib/utils/keycodes
'
;
import
mountComponent
from
'
spec/helpers/vue_mount_component_helper
'
;
import
getSetTimeoutPromise
from
'
spec/helpers/set_timeout_promise_helper
'
;
const
DEFAULT_PROPS
=
{
weightOptions
:
[
'
No Weight
'
,
1
,
2
,
3
],
weightNoneValue
:
'
No Weight
'
,
weightNoneValue
:
'
None
'
,
};
describe
(
'
Weight
'
,
function
()
{
describe
(
'
Weight
'
,
function
()
{
let
vm
;
let
Weight
;
...
...
@@ -51,9 +50,12 @@ describe('Weight', function () {
weight
:
WEIGHT
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-collapsed-weight-label
'
).
textContent
.
trim
()).
toEqual
(
`
${
WEIGHT
}
`
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-weight-label
'
).
textContent
.
trim
()).
toEqual
(
`
${
WEIGHT
}
`
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-dropdown-toggle-text
'
).
textContent
.
trim
()).
toEqual
(
`
${
WEIGHT
}
`
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-collapsed-weight-label
'
).
textContent
.
trim
()).
toEqual
(
`
${
WEIGHT
}
`
,
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-weight-label-value
'
).
textContent
.
trim
()).
toEqual
(
`
${
WEIGHT
}
`
,
);
});
it
(
'
shows weight no-value
'
,
()
=>
{
...
...
@@ -64,20 +66,23 @@ describe('Weight', function () {
weight
:
WEIGHT
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-collapsed-weight-label
'
).
textContent
.
trim
()).
toEqual
(
'
No
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-weight-label
'
).
textContent
.
trim
()).
toEqual
(
'
None
'
);
// Put a placeholder in the dropdown toggle
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-dropdown-toggle-text
'
).
textContent
.
trim
()).
toEqual
(
'
Weight
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-collapsed-weight-label
'
).
textContent
.
trim
()).
toEqual
(
'
None
'
,
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-weight-label .no-value
'
).
textContent
.
trim
()).
toEqual
(
'
None
'
,
);
});
it
(
'
adds `collapse-after-update` class when clicking the collapsed block
'
,
(
done
)
=>
{
it
(
'
adds `collapse-after-update` class when clicking the collapsed block
'
,
done
=>
{
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
});
vm
.
$el
.
querySelector
(
'
.js-weight-collapsed-block
'
).
click
();
vm
.
$nextTick
()
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'
collapse-after-update
'
)).
toEqual
(
true
);
})
...
...
@@ -85,26 +90,28 @@ describe('Weight', function () {
.
catch
(
done
.
fail
);
});
it
(
'
shows dropdown on "Edit" link click
'
,
(
done
)
=>
{
it
(
'
shows dropdown on "Edit" link click
'
,
done
=>
{
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
editable
:
true
,
});
expect
(
vm
.
shouldShow
Dropdown
).
toEqual
(
false
);
expect
(
vm
.
shouldShow
EditField
).
toEqual
(
false
);
vm
.
$el
.
querySelector
(
'
.js-weight-edit-link
'
).
click
();
vm
.
$nextTick
()
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
vm
.
shouldShow
Dropdown
).
toEqual
(
true
);
expect
(
vm
.
shouldShow
EditField
).
toEqual
(
true
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
emits event on
dropdown item click
'
,
(
done
)
=>
{
it
(
'
emits event on
input submission
'
,
done
=>
{
const
ID
=
123
;
const
expectedWeightValue
=
'
3
'
;
spyOn
(
eventHub
,
'
$emit
'
);
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
...
...
@@ -114,15 +121,78 @@ describe('Weight', function () {
vm
.
$el
.
querySelector
(
'
.js-weight-edit-link
'
).
click
();
vm
.
$nextTick
()
.
then
(()
=>
getSetTimeoutPromise
())
.
then
(()
=>
{
vm
.
$el
.
querySelector
(
'
.js-weight-dropdown-content li:nth-child(2) a
'
).
click
();
})
.
then
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
updateWeight
'
,
DEFAULT_PROPS
.
weightOptions
[
1
],
ID
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
vm
.
$nextTick
(()
=>
{
const
event
=
new
CustomEvent
(
'
keydown
'
);
event
.
keyCode
=
ENTER_KEY_CODE
;
vm
.
$refs
.
editableField
.
click
();
vm
.
$refs
.
editableField
.
value
=
expectedWeightValue
;
vm
.
$refs
.
editableField
.
dispatchEvent
(
event
);
expect
(
vm
.
hasValidInput
).
toBe
(
true
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
updateWeight
'
,
expectedWeightValue
,
ID
);
done
();
});
});
it
(
'
emits event on remove weight link click
'
,
done
=>
{
const
ID
=
123
;
spyOn
(
eventHub
,
'
$emit
'
);
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
editable
:
true
,
weight
:
3
,
id
:
ID
,
});
vm
.
$el
.
querySelector
(
'
.js-weight-remove-link
'
).
click
();
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
hasValidInput
).
toBe
(
true
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
updateWeight
'
,
''
,
ID
);
done
();
});
});
it
(
'
triggers error on invalid string value
'
,
done
=>
{
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
editable
:
true
,
});
vm
.
$el
.
querySelector
(
'
.js-weight-edit-link
'
).
click
();
vm
.
$nextTick
(()
=>
{
const
event
=
new
CustomEvent
(
'
keydown
'
);
event
.
keyCode
=
ENTER_KEY_CODE
;
vm
.
$refs
.
editableField
.
click
();
vm
.
$refs
.
editableField
.
value
=
'
potato
'
;
vm
.
$refs
.
editableField
.
dispatchEvent
(
event
);
expect
(
vm
.
hasValidInput
).
toBe
(
false
);
done
();
});
});
it
(
'
triggers error on invalid negative integer value
'
,
done
=>
{
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
editable
:
true
,
});
vm
.
$el
.
querySelector
(
'
.js-weight-edit-link
'
).
click
();
vm
.
$nextTick
(()
=>
{
const
event
=
new
CustomEvent
(
'
keydown
'
);
event
.
keyCode
=
ENTER_KEY_CODE
;
vm
.
$refs
.
editableField
.
click
();
vm
.
$refs
.
editableField
.
value
=
-
9001
;
vm
.
$refs
.
editableField
.
dispatchEvent
(
event
);
expect
(
vm
.
hasValidInput
).
toBe
(
false
);
done
();
});
});
});
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