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
0
Merge Requests
0
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
Jérome Perrin
gitlab-ce
Commits
7187395e
Commit
7187395e
authored
Aug 30, 2017
by
Hiroyuki Sato
Committed by
Phil Hughes
Aug 30, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add filter by my reaction
parent
df8ca5aa
Changes
26
Show whitespace changes
Inline
Side-by-side
Showing
26 changed files
with
693 additions
and
138 deletions
+693
-138
app/assets/javascripts/droplab/drop_down.js
app/assets/javascripts/droplab/drop_down.js
+7
-0
app/assets/javascripts/filtered_search/dropdown_emoji.js
app/assets/javascripts/filtered_search/dropdown_emoji.js
+82
-0
app/assets/javascripts/filtered_search/dropdown_hint.js
app/assets/javascripts/filtered_search/dropdown_hint.js
+1
-1
app/assets/javascripts/filtered_search/filtered_search_bundle.js
...ets/javascripts/filtered_search/filtered_search_bundle.js
+1
-0
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
...ripts/filtered_search/filtered_search_dropdown_manager.js
+5
-0
app/assets/javascripts/filtered_search/filtered_search_manager.js
...ts/javascripts/filtered_search/filtered_search_manager.js
+11
-3
app/assets/javascripts/filtered_search/filtered_search_token_keys.js
...javascripts/filtered_search/filtered_search_token_keys.js
+20
-0
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
...ascripts/filtered_search/filtered_search_visual_tokens.js
+21
-0
app/assets/stylesheets/framework/filters.scss
app/assets/stylesheets/framework/filters.scss
+13
-1
app/controllers/autocomplete_controller.rb
app/controllers/autocomplete_controller.rb
+17
-1
app/finders/issuable_finder.rb
app/finders/issuable_finder.rb
+10
-0
app/models/concerns/awardable.rb
app/models/concerns/awardable.rb
+15
-0
app/views/shared/issuable/_search_bar.html.haml
app/views/shared/issuable/_search_bar.html.haml
+7
-0
changelogs/unreleased/add-filter-by-my-reaction.yml
changelogs/unreleased/add-filter-by-my-reaction.yml
+4
-0
config/routes.rb
config/routes.rb
+1
-0
spec/controllers/autocomplete_controller_spec.rb
spec/controllers/autocomplete_controller_spec.rb
+38
-0
spec/features/issues/filtered_search/dropdown_assignee_spec.rb
...features/issues/filtered_search/dropdown_assignee_spec.rb
+6
-0
spec/features/issues/filtered_search/dropdown_emoji_spec.rb
spec/features/issues/filtered_search/dropdown_emoji_spec.rb
+182
-0
spec/features/issues/filtered_search/dropdown_hint_spec.rb
spec/features/issues/filtered_search/dropdown_hint_spec.rb
+166
-122
spec/features/issues/filtered_search/dropdown_label_spec.rb
spec/features/issues/filtered_search/dropdown_label_spec.rb
+6
-0
spec/features/issues/filtered_search/dropdown_milestone_spec.rb
...eatures/issues/filtered_search/dropdown_milestone_spec.rb
+6
-0
spec/features/issues/filtered_search/search_bar_spec.rb
spec/features/issues/filtered_search/search_bar_spec.rb
+1
-1
spec/finders/issues_finder_spec.rb
spec/finders/issues_finder_spec.rb
+35
-0
spec/javascripts/droplab/drop_down_spec.js
spec/javascripts/droplab/drop_down_spec.js
+13
-2
spec/models/concerns/awardable_spec.rb
spec/models/concerns/awardable_spec.rb
+15
-7
spec/support/filtered_search_helpers.rb
spec/support/filtered_search_helpers.rb
+10
-0
No files found.
app/assets/javascripts/droplab/drop_down.js
View file @
7187395e
...
...
@@ -85,6 +85,13 @@ class DropDown {
const
renderableList
=
this
.
list
.
querySelector
(
'
ul[data-dynamic]
'
)
||
this
.
list
;
renderableList
.
innerHTML
=
children
.
join
(
''
);
const
listEvent
=
new
CustomEvent
(
'
render.dl
'
,
{
detail
:
{
list
:
this
,
},
});
this
.
list
.
dispatchEvent
(
listEvent
);
}
renderChildren
(
data
)
{
...
...
app/assets/javascripts/filtered_search/dropdown_emoji.js
0 → 100644
View file @
7187395e
/* global Flash */
import
Ajax
from
'
~/droplab/plugins/ajax
'
;
import
Filter
from
'
~/droplab/plugins/filter
'
;
import
'
./filtered_search_dropdown
'
;
class
DropdownEmoji
extends
gl
.
FilteredSearchDropdown
{
constructor
(
options
=
{})
{
super
(
options
);
this
.
config
=
{
Ajax
:
{
endpoint
:
`
${
gon
.
relative_url_root
||
''
}
/autocomplete/award_emojis`
,
method
:
'
setData
'
,
loadingTemplate
:
this
.
loadingTemplate
,
onError
()
{
/* eslint-disable no-new */
new
Flash
(
'
An error occured fetching the dropdown data.
'
);
/* eslint-enable no-new */
},
},
Filter
:
{
template
:
'
name
'
,
},
};
import
(
/* webpackChunkName: 'emoji' */
'
~/emoji
'
)
.
then
(({
glEmojiTag
})
=>
{
this
.
glEmojiTag
=
glEmojiTag
;
})
.
catch
(()
=>
{
/* ignore error and leave emoji name in the search bar */
});
this
.
unbindEvents
();
this
.
bindEvents
();
}
bindEvents
()
{
super
.
bindEvents
();
this
.
listRenderedWrapper
=
this
.
listRendered
.
bind
(
this
);
this
.
dropdown
.
addEventListener
(
'
render.dl
'
,
this
.
listRenderedWrapper
);
}
unbindEvents
()
{
this
.
dropdown
.
removeEventListener
(
'
render.dl
'
,
this
.
listRenderedWrapper
);
super
.
unbindEvents
();
}
listRendered
()
{
this
.
replaceEmojiElement
();
}
itemClicked
(
e
)
{
super
.
itemClicked
(
e
,
(
selected
)
=>
{
const
name
=
selected
.
querySelector
(
'
.js-data-value
'
).
innerText
.
trim
();
return
gl
.
DropdownUtils
.
getEscapedText
(
name
);
});
}
renderContent
(
forceShowList
=
false
)
{
this
.
droplab
.
changeHookList
(
this
.
hookId
,
this
.
dropdown
,
[
Ajax
,
Filter
],
this
.
config
);
super
.
renderContent
(
forceShowList
);
}
replaceEmojiElement
()
{
if
(
!
this
.
glEmojiTag
)
return
;
// Replace empty gl-emoji tag to real content
const
dropdownItems
=
[...
this
.
dropdown
.
querySelectorAll
(
'
.filter-dropdown-item
'
)];
dropdownItems
.
forEach
((
dropdownItem
)
=>
{
const
name
=
dropdownItem
.
querySelector
(
'
.js-data-value
'
).
innerText
;
const
emojiTag
=
this
.
glEmojiTag
(
name
);
const
emojiElement
=
dropdownItem
.
querySelector
(
'
gl-emoji
'
);
emojiElement
.
outerHTML
=
emojiTag
;
});
}
init
()
{
this
.
droplab
.
addHook
(
this
.
input
,
this
.
dropdown
,
[
Ajax
,
Filter
],
this
.
config
).
init
();
}
}
window
.
gl
=
window
.
gl
||
{};
gl
.
DropdownEmoji
=
DropdownEmoji
;
app/assets/javascripts/filtered_search/dropdown_hint.js
View file @
7187395e
...
...
@@ -61,7 +61,7 @@ class DropdownHint extends gl.FilteredSearchDropdown {
.
map
(
tokenKey
=>
({
icon
:
`fa-
${
tokenKey
.
icon
}
`
,
hint
:
tokenKey
.
key
,
tag
:
`<
${
tokenKey
.
symbol
}${
tokenKey
.
key
}
>`
,
tag
:
`<
${
tokenKey
.
tag
}
>`
,
type
:
tokenKey
.
type
,
}));
...
...
app/assets/javascripts/filtered_search/filtered_search_bundle.js
View file @
7187395e
import
'
./dropdown_emoji
'
;
import
'
./dropdown_hint
'
;
import
'
./dropdown_non_user
'
;
import
'
./dropdown_user
'
;
...
...
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
View file @
7187395e
...
...
@@ -58,6 +58,11 @@ class FilteredSearchDropdownManager {
},
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-label
'
),
},
'
my-reaction
'
:
{
reference
:
null
,
gl
:
'
DropdownEmoji
'
,
element
:
this
.
container
.
querySelector
(
'
#js-dropdown-my-reaction
'
),
},
hint
:
{
reference
:
null
,
gl
:
'
DropdownHint
'
,
...
...
app/assets/javascripts/filtered_search/filtered_search_manager.js
View file @
7187395e
...
...
@@ -439,8 +439,13 @@ class FilteredSearchManager {
const
match
=
this
.
filteredSearchTokenKeys
.
searchByKeyParam
(
keyParam
);
if
(
match
)
{
const
indexOf
=
keyParam
.
indexOf
(
'
_
'
);
const
sanitizedKey
=
indexOf
!==
-
1
?
keyParam
.
slice
(
0
,
keyParam
.
indexOf
(
'
_
'
))
:
keyParam
;
// Use lastIndexOf because the token key is allowed to contain underscore
// e.g. 'my_reaction' is the token key of 'my_reaction_emoji'
const
lastIndexOf
=
keyParam
.
lastIndexOf
(
'
_
'
);
let
sanitizedKey
=
lastIndexOf
!==
-
1
?
keyParam
.
slice
(
0
,
lastIndexOf
)
:
keyParam
;
// Replace underscore with hyphen in the sanitizedkey.
// e.g. 'my_reaction' => 'my-reaction'
sanitizedKey
=
sanitizedKey
.
replace
(
'
_
'
,
'
-
'
);
const
symbol
=
match
.
symbol
;
let
quotationsToUse
=
''
;
...
...
@@ -515,7 +520,10 @@ class FilteredSearchManager {
const
condition
=
this
.
filteredSearchTokenKeys
.
searchByConditionKeyValue
(
token
.
key
,
token
.
value
.
toLowerCase
());
const
{
param
}
=
this
.
filteredSearchTokenKeys
.
searchByKey
(
token
.
key
)
||
{};
const
keyParam
=
param
?
`
${
token
.
key
}
_
${
param
}
`
:
token
.
key
;
// Replace hyphen with underscore to use as request parameter
// e.g. 'my-reaction' => 'my_reaction'
const
underscoredKey
=
token
.
key
.
replace
(
'
-
'
,
'
_
'
);
const
keyParam
=
param
?
`
${
underscoredKey
}
_
${
param
}
`
:
underscoredKey
;
let
tokenPath
=
''
;
if
(
condition
)
{
...
...
app/assets/javascripts/filtered_search/filtered_search_token_keys.js
View file @
7187395e
...
...
@@ -4,26 +4,42 @@ const tokenKeys = [{
param
:
'
username
'
,
symbol
:
'
@
'
,
icon
:
'
pencil
'
,
tag
:
'
@author
'
,
},
{
key
:
'
assignee
'
,
type
:
'
string
'
,
param
:
'
username
'
,
symbol
:
'
@
'
,
icon
:
'
user
'
,
tag
:
'
@assignee
'
,
},
{
key
:
'
milestone
'
,
type
:
'
string
'
,
param
:
'
title
'
,
symbol
:
'
%
'
,
icon
:
'
clock-o
'
,
tag
:
'
%milestone
'
,
},
{
key
:
'
label
'
,
type
:
'
array
'
,
param
:
'
name[]
'
,
symbol
:
'
~
'
,
icon
:
'
tag
'
,
tag
:
'
~label
'
,
}];
if
(
gon
.
current_user_id
)
{
// Appending tokenkeys only logged-in
tokenKeys
.
push
({
key
:
'
my-reaction
'
,
type
:
'
string
'
,
param
:
'
emoji
'
,
symbol
:
''
,
icon
:
'
thumbs-up
'
,
tag
:
'
emoji
'
,
});
}
const
alternativeTokenKeys
=
[{
key
:
'
label
'
,
type
:
'
string
'
,
...
...
@@ -84,6 +100,10 @@ class FilteredSearchTokenKeys {
return
tokenKeysWithAlternative
.
find
((
tokenKey
)
=>
{
let
tokenKeyParam
=
tokenKey
.
key
;
// Replace hyphen with underscore to compare keyParam with tokenKeyParam
// e.g. 'my-reaction' => 'my_reaction'
tokenKeyParam
=
tokenKeyParam
.
replace
(
'
-
'
,
'
_
'
);
if
(
tokenKey
.
param
)
{
tokenKeyParam
+=
`_
${
tokenKey
.
param
}
`
;
}
...
...
app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
View file @
7187395e
...
...
@@ -132,6 +132,23 @@ class FilteredSearchVisualTokens {
.
catch
(()
=>
{
});
}
static
updateEmojiTokenAppearance
(
tokenValueContainer
,
tokenValueElement
,
tokenValue
)
{
const
container
=
tokenValueContainer
;
const
element
=
tokenValueElement
;
return
import
(
/* webpackChunkName: 'emoji' */
'
../emoji
'
)
.
then
((
Emoji
)
=>
{
if
(
!
Emoji
.
isEmojiNameValid
(
tokenValue
))
{
return
;
}
container
.
dataset
.
originalValue
=
tokenValue
;
element
.
innerHTML
=
Emoji
.
glEmojiTag
(
tokenValue
);
})
// ignore error and leave emoji name in the search bar
.
catch
(()
=>
{
});
}
static
renderVisualTokenValue
(
parentElement
,
tokenName
,
tokenValue
)
{
const
tokenValueContainer
=
parentElement
.
querySelector
(
'
.value-container
'
);
const
tokenValueElement
=
tokenValueContainer
.
querySelector
(
'
.value
'
);
...
...
@@ -144,6 +161,10 @@ class FilteredSearchVisualTokens {
FilteredSearchVisualTokens
.
updateUserTokenAppearance
(
tokenValueContainer
,
tokenValueElement
,
tokenValue
,
);
}
else
if
(
tokenType
===
'
my-reaction
'
)
{
FilteredSearchVisualTokens
.
updateEmojiTokenAppearance
(
tokenValueContainer
,
tokenValueElement
,
tokenValue
,
);
}
}
...
...
app/assets/stylesheets/framework/filters.scss
View file @
7187395e
...
...
@@ -225,6 +225,18 @@
color
:
$common-gray-dark
;
}
gl-emoji
{
display
:
inline-block
;
font-family
:
inherit
;
font-size
:
inherit
;
vertical-align
:
inherit
;
img
{
height
:
18px
;
width
:
18px
;
}
}
.form-control
{
position
:
relative
;
min-width
:
200px
;
...
...
@@ -277,7 +289,7 @@
}
.filtered-search-input-dropdown-menu
{
max-height
:
2
25
px
;
max-height
:
2
60
px
;
max-width
:
280px
;
overflow
:
auto
;
...
...
app/controllers/autocomplete_controller.rb
View file @
7187395e
class
AutocompleteController
<
ApplicationController
skip_before_action
:authenticate_user!
,
only:
[
:users
]
AWARD_EMOJI_MAX
=
100
skip_before_action
:authenticate_user!
,
only:
[
:users
,
:award_emojis
]
before_action
:load_project
,
only:
[
:users
]
before_action
:find_users
,
only:
[
:users
]
...
...
@@ -48,6 +50,20 @@ class AutocompleteController < ApplicationController
render
json:
projects
.
to_json
(
only:
[
:id
,
:name_with_namespace
],
methods: :name_with_namespace
)
end
def
award_emojis
emoji_with_count
=
AwardEmoji
.
limit
(
AWARD_EMOJI_MAX
)
.
where
(
user:
current_user
)
.
group
(
:name
)
.
order
(
count: :desc
,
name: :asc
)
.
count
# Transform from hash to array to guarantee json order
# e.g. { 'thumbsup' => 2, 'thumbsdown' = 1 }
# => [{ name: 'thumbsup' }, { name: 'thumbsdown' }]
render
json:
emoji_with_count
.
map
{
|
k
,
v
|
{
name:
k
}
}
end
private
def
find_users
...
...
app/finders/issuable_finder.rb
View file @
7187395e
...
...
@@ -18,6 +18,7 @@
# sort: string
# non_archived: boolean
# iids: integer[]
# my_reaction_emoji: string
#
class
IssuableFinder
include
CreatedAtFilter
...
...
@@ -46,6 +47,7 @@ class IssuableFinder
items
=
by_iids
(
items
)
items
=
by_milestone
(
items
)
items
=
by_label
(
items
)
items
=
by_my_reaction_emoji
(
items
)
# Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
items
=
by_project
(
items
)
...
...
@@ -371,6 +373,14 @@ class IssuableFinder
items
end
def
by_my_reaction_emoji
(
items
)
if
params
[
:my_reaction_emoji
].
present?
&&
current_user
items
=
items
.
awarded
(
current_user
,
params
[
:my_reaction_emoji
])
end
items
end
def
by_due_date
(
items
)
if
due_date?
if
filter_by_no_due_date?
...
...
app/models/concerns/awardable.rb
View file @
7187395e
...
...
@@ -11,6 +11,21 @@ module Awardable
end
module
ClassMethods
def
awarded
(
user
,
name
)
sql
=
<<~
EOL
EXISTS (
SELECT TRUE
FROM award_emoji
WHERE user_id = :user_id AND
name = :name AND
awardable_type = :awardable_type AND
awardable_id =
#{
self
.
arel_table
.
name
}
.id
)
EOL
where
(
sql
,
user_id:
user
.
id
,
name:
name
,
awardable_type:
self
.
name
)
end
def
order_upvotes_desc
order_votes_desc
(
AwardEmoji
::
UPVOTE_NAME
)
end
...
...
app/views/shared/issuable/_search_bar.html.haml
View file @
7187395e
...
...
@@ -93,6 +93,13 @@
%span
.dropdown-label-box
{
style:
'
background:
{{
color
}}
'
}
%span
.label-title.js-data-value
{{title}}
#js-dropdown-my-reaction
.filtered-search-input-dropdown-menu.dropdown-menu
%ul
.filter-dropdown
{
data:
{
dynamic:
true
,
dropdown:
true
}
}
%li
.filter-dropdown-item
%button
.btn.btn-link
%gl-emoji
%span
.js-data-value.prepend-left-10
{{name}}
%button
.clear-search.hidden
{
type:
'button'
}
=
icon
(
'times'
)
.filter-dropdown-container
...
...
changelogs/unreleased/add-filter-by-my-reaction.yml
0 → 100644
View file @
7187395e
---
title
:
Add my reaction filter to search bar
merge_request
:
12962
author
:
Hiroyuki Sato
config/routes.rb
View file @
7187395e
...
...
@@ -27,6 +27,7 @@ Rails.application.routes.draw do
get
'/autocomplete/users'
=>
'autocomplete#users'
get
'/autocomplete/users/:id'
=>
'autocomplete#user'
get
'/autocomplete/projects'
=>
'autocomplete#projects'
get
'/autocomplete/award_emojis'
=>
'autocomplete#award_emojis'
# Search
get
'search'
=>
'search#show'
...
...
spec/controllers/autocomplete_controller_spec.rb
View file @
7187395e
...
...
@@ -339,4 +339,42 @@ describe AutocompleteController do
end
end
end
context
'GET award_emojis'
do
let
(
:user2
)
{
create
(
:user
)
}
let!
(
:award_emoji1
)
{
create_list
(
:award_emoji
,
2
,
user:
user
,
name:
'thumbsup'
)
}
let!
(
:award_emoji2
)
{
create_list
(
:award_emoji
,
1
,
user:
user
,
name:
'thumbsdown'
)
}
let!
(
:award_emoji3
)
{
create_list
(
:award_emoji
,
3
,
user:
user
,
name:
'star'
)
}
let!
(
:award_emoji4
)
{
create_list
(
:award_emoji
,
1
,
user:
user
,
name:
'tea'
)
}
context
'unauthorized user'
do
it
'returns empty json'
do
get
:award_emojis
expect
(
json_response
).
to
be_empty
end
end
context
'sign in as user without award emoji'
do
it
'returns empty json'
do
sign_in
(
user2
)
get
:award_emojis
expect
(
json_response
).
to
be_empty
end
end
context
'sign in as user with award emoji'
do
it
'returns json sorted by name count'
do
sign_in
(
user
)
get
:award_emojis
expect
(
json_response
.
count
).
to
eq
4
expect
(
json_response
[
0
]).
to
match
(
'name'
=>
'star'
)
expect
(
json_response
[
1
]).
to
match
(
'name'
=>
'thumbsup'
)
expect
(
json_response
[
2
]).
to
match
(
'name'
=>
'tea'
)
expect
(
json_response
[
3
]).
to
match
(
'name'
=>
'thumbsdown'
)
end
end
end
end
spec/features/issues/filtered_search/dropdown_assignee_spec.rb
View file @
7187395e
...
...
@@ -204,6 +204,12 @@ describe 'Dropdown assignee', :js do
expect
(
page
).
to
have_css
(
js_dropdown_assignee
,
visible:
true
)
end
it
'opens assignee dropdown with existing my-reaction'
do
filtered_search
.
set
(
'my-reaction:star assignee:'
)
expect
(
page
).
to
have_css
(
js_dropdown_assignee
,
visible:
true
)
end
end
describe
'caching requests'
do
...
...
spec/features/issues/filtered_search/dropdown_emoji_spec.rb
0 → 100644
View file @
7187395e
require
'rails_helper'
describe
'Dropdown emoji'
,
js:
true
do
include
FilteredSearchHelpers
let!
(
:project
)
{
create
(
:project
,
:public
)
}
let!
(
:user
)
{
create
(
:user
,
name:
'administrator'
,
username:
'root'
)
}
let!
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let!
(
:award_emoji_star
)
{
create
(
:award_emoji
,
name:
'star'
,
user:
user
,
awardable:
issue
)
}
let
(
:filtered_search
)
{
find
(
'.filtered-search'
)
}
let
(
:js_dropdown_emoji
)
{
'#js-dropdown-my-reaction'
}
def
send_keys_to_filtered_search
(
input
)
input
.
split
(
""
).
each
do
|
i
|
filtered_search
.
send_keys
(
i
)
end
sleep
0.5
wait_for_requests
end
def
dropdown_emoji_size
page
.
all
(
'#js-dropdown-my-reaction .filter-dropdown .filter-dropdown-item'
).
size
end
def
click_emoji
(
text
)
find
(
'#js-dropdown-my-reaction .filter-dropdown .filter-dropdown-item'
,
text:
text
).
click
end
before
do
project
.
team
<<
[
user
,
:master
]
create_list
(
:award_emoji
,
2
,
user:
user
,
name:
'thumbsup'
)
create_list
(
:award_emoji
,
1
,
user:
user
,
name:
'thumbsdown'
)
create_list
(
:award_emoji
,
3
,
user:
user
,
name:
'star'
)
create_list
(
:award_emoji
,
1
,
user:
user
,
name:
'tea'
)
end
context
'when user not logged in'
do
before
do
visit
project_issues_path
(
project
)
end
describe
'behavior'
do
it
'does not open when the search bar has my-reaction:'
do
filtered_search
.
set
(
'my-reaction:'
)
expect
(
page
).
not_to
have_css
(
js_dropdown_emoji
)
end
end
end
context
'when user loggged in'
do
before
do
sign_in
(
user
)
visit
project_issues_path
(
project
)
end
describe
'behavior'
do
it
'opens when the search bar has my-reaction:'
do
filtered_search
.
set
(
'my-reaction:'
)
expect
(
page
).
to
have_css
(
js_dropdown_emoji
,
visible:
true
)
end
it
'closes when the search bar is unfocused'
do
find
(
'body'
).
click
()
expect
(
page
).
to
have_css
(
js_dropdown_emoji
,
visible:
false
)
end
it
'should show loading indicator when opened'
do
filtered_search
.
set
(
'my-reaction:'
)
expect
(
page
).
to
have_css
(
'#js-dropdown-my-reaction .filter-dropdown-loading'
,
visible:
true
)
end
it
'should hide loading indicator when loaded'
do
send_keys_to_filtered_search
(
'my-reaction:'
)
expect
(
page
).
not_to
have_css
(
'#js-dropdown-my-reaction .filter-dropdown-loading'
)
end
it
'should load all the emojis when opened'
do
send_keys_to_filtered_search
(
'my-reaction:'
)
expect
(
dropdown_emoji_size
).
to
eq
(
4
)
end
it
'shows the most populated emoji at top of dropdown'
do
send_keys_to_filtered_search
(
'my-reaction:'
)
expect
(
first
(
'#js-dropdown-my-reaction li'
)).
to
have_content
(
award_emoji_star
.
name
)
end
end
describe
'filtering'
do
before
do
filtered_search
.
set
(
'my-reaction'
)
send_keys_to_filtered_search
(
':'
)
end
it
'filters by name'
do
send_keys_to_filtered_search
(
'up'
)
expect
(
dropdown_emoji_size
).
to
eq
(
1
)
end
it
'filters by case insensitive name'
do
send_keys_to_filtered_search
(
'Up'
)
expect
(
dropdown_emoji_size
).
to
eq
(
1
)
end
end
describe
'selecting from dropdown'
do
before
do
filtered_search
.
set
(
'my-reaction'
)
send_keys_to_filtered_search
(
':'
)
end
it
'fills in the my-reaction name'
do
click_emoji
(
'thumbsup'
)
wait_for_requests
expect
(
page
).
to
have_css
(
js_dropdown_emoji
,
visible:
false
)
expect_tokens
([
emoji_token
(
'thumbsup'
)])
expect_filtered_search_input_empty
end
end
describe
'input has existing content'
do
it
'opens my-reaction dropdown with existing search term'
do
filtered_search
.
set
(
'searchTerm my-reaction:'
)
expect
(
page
).
to
have_css
(
js_dropdown_emoji
,
visible:
true
)
end
it
'opens my-reaction dropdown with existing assignee'
do
filtered_search
.
set
(
'assignee:@user my-reaction:'
)
expect
(
page
).
to
have_css
(
js_dropdown_emoji
,
visible:
true
)
end
it
'opens my-reaction dropdown with existing label'
do
filtered_search
.
set
(
'label:~bug my-reaction:'
)
expect
(
page
).
to
have_css
(
js_dropdown_emoji
,
visible:
true
)
end
it
'opens my-reaction dropdown with existing milestone'
do
filtered_search
.
set
(
'milestone:%v1.0 my-reaction:'
)
expect
(
page
).
to
have_css
(
js_dropdown_emoji
,
visible:
true
)
end
it
'opens my-reaction dropdown with existing my-reaction'
do
filtered_search
.
set
(
'my-reaction:star my-reaction:'
)
expect
(
page
).
to
have_css
(
js_dropdown_emoji
,
visible:
true
)
end
end
describe
'caching requests'
do
it
'caches requests after the first load'
do
filtered_search
.
set
(
'my-reaction'
)
send_keys_to_filtered_search
(
':'
)
initial_size
=
dropdown_emoji_size
expect
(
initial_size
).
to
be
>
0
create_list
(
:award_emoji
,
1
,
user:
user
,
name:
'smile'
)
find
(
'.filtered-search-box .clear-search'
).
click
filtered_search
.
set
(
'my-reaction'
)
send_keys_to_filtered_search
(
':'
)
expect
(
dropdown_emoji_size
).
to
eq
(
initial_size
)
end
end
end
end
spec/features/issues/filtered_search/dropdown_hint_spec.rb
View file @
7187395e
...
...
@@ -3,7 +3,7 @@ require 'rails_helper'
describe
'Dropdown hint'
,
:js
do
include
FilteredSearchHelpers
let!
(
:project
)
{
create
(
:project
)
}
let!
(
:project
)
{
create
(
:project
,
:public
)
}
let!
(
:user
)
{
create
(
:user
)
}
let
(
:filtered_search
)
{
find
(
'.filtered-search'
)
}
let
(
:js_dropdown_hint
)
{
'#js-dropdown-hint'
}
...
...
@@ -14,8 +14,23 @@ describe 'Dropdown hint', :js do
before
do
project
.
team
<<
[
user
,
:master
]
sign_in
(
user
)
create
(
:issue
,
project:
project
)
end
context
'when user not logged in'
do
before
do
visit
project_issues_path
(
project
)
end
it
'does not exist my-reaction dropdown item'
do
expect
(
page
).
to
have_css
(
js_dropdown_hint
,
visible:
false
)
expect
(
page
).
not_to
have_content
(
'my-reaction'
)
end
end
context
'when user logged in'
do
before
do
sign_in
(
user
)
visit
project_issues_path
(
project
)
end
...
...
@@ -50,7 +65,7 @@ describe 'Dropdown hint', :js do
it
'filters with text'
do
filtered_search
.
set
(
'a'
)
expect
(
find
(
js_dropdown_hint
)).
to
have_selector
(
'.filter-dropdown .filter-dropdown-item'
,
count:
3
)
expect
(
find
(
js_dropdown_hint
)).
to
have_selector
(
'.filter-dropdown .filter-dropdown-item'
,
count:
4
)
end
end
...
...
@@ -94,6 +109,15 @@ describe 'Dropdown hint', :js do
expect_tokens
([{
name:
'label'
}])
expect_filtered_search_input_empty
end
it
'opens the emoji dropdown when you click on my-reaction'
do
click_hint
(
'my-reaction'
)
expect
(
page
).
to
have_css
(
js_dropdown_hint
,
visible:
false
)
expect
(
page
).
to
have_css
(
'#js-dropdown-my-reaction'
,
visible:
true
)
expect_tokens
([{
name:
'my-reaction'
}])
expect_filtered_search_input_empty
end
end
describe
'selecting from dropdown with some input'
do
...
...
@@ -136,6 +160,16 @@ describe 'Dropdown hint', :js do
expect_tokens
([{
name:
'label'
}])
expect_filtered_search_input_empty
end
it
'opens the emoji dropdown when you click on my-reaction'
do
filtered_search
.
set
(
'my'
)
click_hint
(
'my-reaction'
)
expect
(
page
).
to
have_css
(
js_dropdown_hint
,
visible:
false
)
expect
(
page
).
to
have_css
(
'#js-dropdown-my-reaction'
,
visible:
true
)
expect_tokens
([{
name:
'my-reaction'
}])
expect_filtered_search_input_empty
end
end
describe
'reselecting from dropdown'
do
...
...
@@ -174,5 +208,15 @@ describe 'Dropdown hint', :js do
expect_tokens
([{
name:
'label'
}])
expect_filtered_search_input_empty
end
it
'reuses existing emoji text'
do
filtered_search
.
send_keys
(
'my-reaction:'
)
filtered_search
.
send_keys
(
:backspace
)
click_hint
(
'my-reaction'
)
expect_tokens
([{
name:
'my-reaction'
}])
expect_filtered_search_input_empty
end
end
end
end
spec/features/issues/filtered_search/dropdown_label_spec.rb
View file @
7187395e
...
...
@@ -270,6 +270,12 @@ describe 'Dropdown label', js: true do
expect
(
page
).
to
have_css
(
js_dropdown_label
)
end
it
'opens label dropdown with existing my-reaction'
do
filtered_search
.
set
(
'my-reaction:star label:'
)
expect
(
page
).
to
have_css
(
js_dropdown_label
)
end
end
describe
'caching requests'
do
...
...
spec/features/issues/filtered_search/dropdown_milestone_spec.rb
View file @
7187395e
...
...
@@ -242,6 +242,12 @@ describe 'Dropdown milestone', :js do
expect
(
page
).
to
have_css
(
js_dropdown_milestone
,
visible:
true
)
end
it
'opens milestone dropdown with existing my-reaction'
do
filtered_search
.
set
(
'my-reaction:star milestone:'
)
expect
(
page
).
to
have_css
(
js_dropdown_milestone
,
visible:
true
)
end
end
describe
'caching requests'
do
...
...
spec/features/issues/filtered_search/search_bar_spec.rb
View file @
7187395e
...
...
@@ -100,7 +100,7 @@ describe 'Search bar', js: true do
find
(
'.filtered-search-box .clear-search'
).
click
filtered_search
.
click
expect
(
find
(
'#js-dropdown-hint'
)).
to
have_selector
(
'.filter-dropdown .filter-dropdown-item'
,
count:
4
)
expect
(
find
(
'#js-dropdown-hint'
)).
to
have_selector
(
'.filter-dropdown .filter-dropdown-item'
,
count:
5
)
expect
(
get_left_style
(
find
(
'#js-dropdown-hint'
)[
'style'
])).
to
eq
(
hint_offset
)
end
end
...
...
spec/finders/issues_finder_spec.rb
View file @
7187395e
...
...
@@ -10,6 +10,9 @@ describe IssuesFinder do
set
(
:issue1
)
{
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project1
,
milestone:
milestone
,
title:
'gitlab'
,
created_at:
1
.
week
.
ago
)
}
set
(
:issue2
)
{
create
(
:issue
,
author:
user
,
assignees:
[
user
],
project:
project2
,
description:
'gitlab'
)
}
set
(
:issue3
)
{
create
(
:issue
,
author:
user2
,
assignees:
[
user2
],
project:
project2
,
title:
'tanuki'
,
description:
'tanuki'
,
created_at:
1
.
week
.
from_now
)
}
set
(
:award_emoji1
)
{
create
(
:award_emoji
,
name:
'thumbsup'
,
user:
user
,
awardable:
issue1
)
}
set
(
:award_emoji2
)
{
create
(
:award_emoji
,
name:
'thumbsup'
,
user:
user2
,
awardable:
issue2
)
}
set
(
:award_emoji3
)
{
create
(
:award_emoji
,
name:
'thumbsdown'
,
user:
user
,
awardable:
issue3
)
}
describe
'#execute'
do
set
(
:closed_issue
)
{
create
(
:issue
,
author:
user2
,
assignees:
[
user2
],
project:
project2
,
state:
'closed'
)
}
...
...
@@ -26,6 +29,10 @@ describe IssuesFinder do
issue1
issue2
issue3
award_emoji1
award_emoji2
award_emoji3
end
context
'scope: all'
do
...
...
@@ -250,6 +257,34 @@ describe IssuesFinder do
end
end
context
'filtering by reaction name'
do
context
'user searches by "thumbsup" reaction'
do
let
(
:params
)
{
{
my_reaction_emoji:
'thumbsup'
}
}
it
'returns issues that the user thumbsup to'
do
expect
(
issues
).
to
contain_exactly
(
issue1
)
end
end
context
'user2 searches by "thumbsup" reaction'
do
let
(
:search_user
)
{
user2
}
let
(
:params
)
{
{
my_reaction_emoji:
'thumbsup'
}
}
it
'returns issues that the user2 thumbsup to'
do
expect
(
issues
).
to
contain_exactly
(
issue2
)
end
end
context
'user searches by "thumbsdown" reaction'
do
let
(
:params
)
{
{
my_reaction_emoji:
'thumbsdown'
}
}
it
'returns issues that the user thumbsdown to'
do
expect
(
issues
).
to
contain_exactly
(
issue3
)
end
end
end
context
'when the user is unauthorized'
do
let
(
:search_user
)
{
nil
}
...
...
spec/javascripts/droplab/drop_down_spec.js
View file @
7187395e
...
...
@@ -351,14 +351,17 @@ describe('DropDown', function () {
describe
(
'
render
'
,
function
()
{
beforeEach
(
function
()
{
this
.
list
=
{
querySelector
:
()
=>
{}
};
this
.
list
=
{
querySelector
:
()
=>
{}
,
dispatchEvent
:
()
=>
{}
};
this
.
dropdown
=
{
renderChildren
:
()
=>
{},
list
:
this
.
list
};
this
.
renderableList
=
{};
this
.
data
=
[
0
,
1
];
this
.
customEvent
=
{};
spyOn
(
this
.
dropdown
,
'
renderChildren
'
).
and
.
callFake
(
data
=>
data
);
spyOn
(
this
.
list
,
'
querySelector
'
).
and
.
returnValue
(
this
.
renderableList
);
spyOn
(
this
.
list
,
'
dispatchEvent
'
);
spyOn
(
this
.
data
,
'
map
'
).
and
.
callThrough
();
spyOn
(
window
,
'
CustomEvent
'
).
and
.
returnValue
(
this
.
customEvent
);
DropDown
.
prototype
.
render
.
call
(
this
.
dropdown
,
this
.
data
);
});
...
...
@@ -375,6 +378,14 @@ describe('DropDown', function () {
expect
(
this
.
renderableList
.
innerHTML
).
toBe
(
'
01
'
);
});
it
(
'
should call render.dl
'
,
function
()
{
expect
(
window
.
CustomEvent
).
toHaveBeenCalledWith
(
'
render.dl
'
,
jasmine
.
any
(
Object
));
});
it
(
'
should call dispatchEvent with the customEvent
'
,
function
()
{
expect
(
this
.
list
.
dispatchEvent
).
toHaveBeenCalledWith
(
this
.
customEvent
);
});
describe
(
'
if no data argument is passed
'
,
function
()
{
beforeEach
(
function
()
{
this
.
data
.
map
.
calls
.
reset
();
...
...
@@ -394,7 +405,7 @@ describe('DropDown', function () {
describe
(
'
if no dynamic list is present
'
,
function
()
{
beforeEach
(
function
()
{
this
.
list
=
{
querySelector
:
()
=>
{}
};
this
.
list
=
{
querySelector
:
()
=>
{}
,
dispatchEvent
:
()
=>
{}
};
this
.
dropdown
=
{
renderChildren
:
()
=>
{},
list
:
this
.
list
};
this
.
data
=
[
0
,
1
];
...
...
spec/models/concerns/awardable_spec.rb
View file @
7187395e
...
...
@@ -12,11 +12,9 @@ describe Awardable do
describe
"ClassMethods"
do
let!
(
:issue2
)
{
create
(
:issue
)
}
let!
(
:award_emoji2
)
{
create
(
:award_emoji
,
awardable:
issue2
)
}
before
do
create
(
:award_emoji
,
awardable:
issue2
)
end
describe
"orders"
do
it
"orders on upvotes"
do
expect
(
Issue
.
order_upvotes_desc
.
to_a
).
to
eq
[
issue2
,
issue
]
end
...
...
@@ -26,6 +24,16 @@ describe Awardable do
end
end
describe
".awarded"
do
it
"filters by user and emoji name"
do
expect
(
Issue
.
awarded
(
award_emoji
.
user
,
"thumbsup"
)).
to
be_empty
expect
(
Issue
.
awarded
(
award_emoji
.
user
,
"thumbsdown"
)).
to
eq
[
issue
]
expect
(
Issue
.
awarded
(
award_emoji2
.
user
,
"thumbsup"
)).
to
eq
[
issue2
]
expect
(
Issue
.
awarded
(
award_emoji2
.
user
,
"thumbsdown"
)).
to
be_empty
end
end
end
describe
"#upvotes"
do
it
"counts the number of upvotes"
do
expect
(
issue
.
upvotes
).
to
be
0
...
...
spec/support/filtered_search_helpers.rb
View file @
7187395e
...
...
@@ -58,11 +58,17 @@ module FilteredSearchHelpers
page
.
all
(
:css
,
'.tokens-container li .selectable'
).
each_with_index
do
|
el
,
index
|
token_name
=
tokens
[
index
][
:name
]
token_value
=
tokens
[
index
][
:value
]
token_emoji
=
tokens
[
index
][
:emoji_name
]
expect
(
el
.
find
(
'.name'
)).
to
have_content
(
token_name
)
if
token_value
expect
(
el
.
find
(
'.value'
)).
to
have_content
(
token_value
)
end
# gl-emoji content is blank when the emoji unicode is not supported
if
token_emoji
selector
=
%(gl-emoji[data-name="#{token_emoji}"])
expect
(
el
.
find
(
'.value'
)).
to
have_css
(
selector
)
end
end
end
end
...
...
@@ -89,6 +95,10 @@ module FilteredSearchHelpers
create_token
(
'Label'
,
label_name
,
symbol
)
end
def
emoji_token
(
emoji_name
=
nil
)
{
name:
'My-Reaction'
,
emoji_name:
emoji_name
}
end
def
default_placeholder
'Search or filter results...'
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