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
98d83965
Commit
98d83965
authored
Apr 12, 2017
by
Clement Ho
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[skip ci] refactor
parent
e43d79b0
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
186 additions
and
155 deletions
+186
-155
app/assets/javascripts/sidebar_assignees/components/expanded/multiple_assignees.js
...debar_assignees/components/expanded/multiple_assignees.js
+12
-7
app/assets/javascripts/sidebar_assignees/components/expanded/no_assignee.js
...ipts/sidebar_assignees/components/expanded/no_assignee.js
+3
-7
app/assets/javascripts/sidebar_assignees/components/expanded/single_assignee.js
.../sidebar_assignees/components/expanded/single_assignee.js
+7
-6
app/assets/javascripts/sidebar_assignees/event_hub.js
app/assets/javascripts/sidebar_assignees/event_hub.js
+3
-0
app/assets/javascripts/sidebar_assignees/index.js
app/assets/javascripts/sidebar_assignees/index.js
+2
-80
app/assets/javascripts/sidebar_assignees/sidebar_assignees_options.js
...avascripts/sidebar_assignees/sidebar_assignees_options.js
+107
-0
app/assets/javascripts/sidebar_assignees/stores/sidebar_assignees_store.js
...ripts/sidebar_assignees/stores/sidebar_assignees_store.js
+33
-34
app/assets/javascripts/users_select.js
app/assets/javascripts/users_select.js
+6
-5
app/controllers/projects/issues_controller.rb
app/controllers/projects/issues_controller.rb
+1
-1
app/views/shared/issuable/_sidebar.html.haml
app/views/shared/issuable/_sidebar.html.haml
+12
-15
No files found.
app/assets/javascripts/sidebar_assignees/components/expanded/multiple_assignees.js
View file @
98d83965
...
...
@@ -2,21 +2,26 @@ export default {
name
:
'
MultipleAssignees
'
,
data
()
{
return
{
defaultRenderCount
:
5
,
showLess
:
true
,
};
},
props
:
{
store
:
{
type
:
Object
,
rootPath
:
{
type
:
String
,
required
:
true
,
},
users
:
{
type
:
Array
,
required
:
true
,
},
},
computed
:
{
renderShowMoreSection
()
{
return
this
.
store
.
users
.
length
>
this
.
store
.
defaultRenderCount
;
return
this
.
users
.
length
>
this
.
defaultRenderCount
;
},
numberOfHiddenAssignees
()
{
return
this
.
store
.
users
.
length
-
this
.
store
.
defaultRenderCount
;
return
this
.
users
.
length
-
this
.
defaultRenderCount
;
},
isHiddenAssignees
()
{
return
this
.
numberOfHiddenAssignees
>
0
;
...
...
@@ -27,10 +32,10 @@ export default {
this
.
showLess
=
!
this
.
showLess
;
},
renderAssignee
(
index
)
{
return
!
this
.
showLess
||
(
index
<
this
.
store
.
defaultRenderCount
&&
this
.
showLess
);
return
!
this
.
showLess
||
(
index
<
this
.
defaultRenderCount
&&
this
.
showLess
);
},
assigneeUrl
(
username
)
{
return
`
${
this
.
store
.
rootPath
}${
username
}
`
;
return
`
${
this
.
rootPath
}${
username
}
`
;
},
assigneeAlt
(
name
)
{
return
`
${
name
}
's avatar`
;
...
...
@@ -40,7 +45,7 @@ export default {
<div class="hide-collapsed">
<div class="hide-collapsed">
<div class="user-list">
<div class="user-item" v-for="(user, index) in
store.
users"
<div class="user-item" v-for="(user, index) in users"
v-if="renderAssignee(index)" >
<a class="user-link has-tooltip"
data-placement="bottom"
...
...
app/assets/javascripts/sidebar_assignees/components/expanded/no_assignee.js
View file @
98d83965
import
eventHub
from
'
../../event_hub
'
;
export
default
{
name
:
'
NoAssignee
'
,
props
:
{
store
:
{
type
:
Object
,
required
:
true
,
},
},
methods
:
{
assignSelf
()
{
this
.
store
.
addCurrentUser
(
);
eventHub
.
$emit
(
'
addCurrentUser
'
);
},
},
template
:
`
...
...
app/assets/javascripts/sidebar_assignees/components/expanded/single_assignee.js
View file @
98d83965
export
default
{
name
:
'
SingleAssignee
'
,
props
:
{
store
:
{
type
:
Object
,
rootPath
:
{
type
:
String
,
required
:
true
,
},
user
:
{
type
:
Object
,
required
:
true
,
}
},
computed
:
{
user
()
{
return
this
.
store
.
users
[
0
];
},
userUrl
()
{
return
`
${
this
.
store
.
rootPath
}${
this
.
user
.
username
}
`
;
return
`
${
this
.
rootPath
}${
this
.
user
.
username
}
`
;
},
username
()
{
return
`@
${
this
.
user
.
username
}
`
;
...
...
app/assets/javascripts/sidebar_assignees/event_hub.js
0 → 100644
View file @
98d83965
import
Vue
from
'
vue
'
;
export
default
new
Vue
();
app/assets/javascripts/sidebar_assignees/index.js
View file @
98d83965
import
Vue
from
'
vue
'
;
import
AssigneeTitle
from
'
./components/assignee_title
'
;
import
NoAssignee
from
'
./components/expanded/no_assignee
'
;
import
SingleAssignee
from
'
./components/expanded/single_assignee
'
;
import
MultipleAssignees
from
'
./components/expanded/multiple_assignees
'
;
import
CollapsedAssignees
from
'
./components/collapsed/assignees
'
;
import
SidebarAssigneesService
from
'
./services/sidebar_assignees_service
'
;
import
SidebarAssigneesStore
from
'
./stores/sidebar_assignees_store
'
;
const
sidebarAssigneesOptions
=
()
=>
({
el
:
'
#js-vue-sidebar-assignees
'
,
name
:
'
SidebarAssignees
'
,
data
()
{
const
selector
=
this
.
$options
.
el
;
const
element
=
document
.
querySelector
(
selector
);
// Get data from data attributes passed from haml
const
rootPath
=
element
.
dataset
.
rootPath
;
const
path
=
element
.
dataset
.
path
;
const
field
=
element
.
dataset
.
field
;
const
editable
=
element
.
hasAttribute
(
'
data-editable
'
);
const
currentUserId
=
parseInt
(
element
.
dataset
.
userId
,
10
);
const
service
=
new
SidebarAssigneesService
(
path
,
field
);
const
store
=
new
SidebarAssigneesStore
({
currentUserId
,
service
,
rootPath
,
editable
,
});
return
{
store
,
};
},
computed
:
{
numberOfAssignees
()
{
return
this
.
store
.
users
.
length
;
},
componentName
()
{
switch
(
this
.
numberOfAssignees
)
{
case
0
:
return
'
no-assignee
'
;
case
1
:
return
'
single-assignee
'
;
default
:
return
'
multiple-assignees
'
;
}
},
hideComponent
()
{
return
!
this
.
store
.
saved
;
},
},
components
:
{
'
no-assignee
'
:
NoAssignee
,
'
single-assignee
'
:
SingleAssignee
,
'
multiple-assignees
'
:
MultipleAssignees
,
'
assignee-title
'
:
AssigneeTitle
,
'
collapsed-assignees
'
:
CollapsedAssignees
,
},
template
:
`
<div>
<assignee-title
:numberOfAssignees="store.users.length"
:loading="store.loading"
:editable="store.editable"
/>
<collapsed-assignees :users="store.users"/>
<component v-if="store.saved"
class="value"
:is="componentName"
:store="store"
/>
</div>
`
,
});
import
sidebarAssigneesOptions
from
'
./sidebar_assignees_options
'
;
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
// Expose this to window so that we can add assignees from glDropdown
window
.
gl
.
sidebarAssigneesOptions
=
new
Vue
(
sidebarAssigneesOptions
());
window
.
gl
.
sidebarAssigneesOptions
=
new
Vue
(
sidebarAssigneesOptions
);
});
app/assets/javascripts/sidebar_assignees/sidebar_assignees_options.js
0 → 100644
View file @
98d83965
import
Vue
from
'
vue
'
;
import
eventHub
from
'
./event_hub
'
;
import
AssigneeTitle
from
'
./components/assignee_title
'
;
import
NoAssignee
from
'
./components/expanded/no_assignee
'
;
import
SingleAssignee
from
'
./components/expanded/single_assignee
'
;
import
MultipleAssignees
from
'
./components/expanded/multiple_assignees
'
;
import
CollapsedAssignees
from
'
./components/collapsed/assignees
'
;
import
SidebarAssigneesService
from
'
./services/sidebar_assignees_service
'
;
import
SidebarAssigneesStore
from
'
./stores/sidebar_assignees_store
'
;
export
default
{
el
:
'
#js-vue-sidebar-assignees
'
,
name
:
'
SidebarAssignees
'
,
data
()
{
const
selector
=
this
.
$options
.
el
;
const
element
=
document
.
querySelector
(
selector
);
// Get data from data attributes passed from haml
const
rootPath
=
element
.
dataset
.
rootPath
;
const
path
=
element
.
dataset
.
path
;
const
field
=
element
.
dataset
.
field
;
const
editable
=
element
.
hasAttribute
(
'
data-editable
'
);
const
currentUser
=
{
id
:
parseInt
(
element
.
dataset
.
userId
,
10
),
name
:
element
.
dataset
.
userName
,
username
:
element
.
dataset
.
userUserName
,
avatarUrl
:
element
.
dataset
.
avatar_url
,
};
const
service
=
new
SidebarAssigneesService
(
path
,
field
);
const
store
=
new
SidebarAssigneesStore
({
currentUser
,
rootPath
,
editable
,
assignees
:
gl
.
sidebarAssigneesData
,
});
return
{
store
,
service
,
loading
:
false
,
};
},
computed
:
{
numberOfAssignees
()
{
return
this
.
store
.
users
.
length
;
},
},
created
()
{
eventHub
.
$on
(
'
addCurrentUser
'
,
this
.
addCurrentUser
);
eventHub
.
$on
(
'
addUser
'
,
this
.
store
.
addUser
.
bind
(
this
.
store
));
eventHub
.
$on
(
'
removeUser
'
,
this
.
store
.
removeUser
.
bind
(
this
.
store
));
eventHub
.
$on
(
'
removeAllUsers
'
,
this
.
store
.
removeAllUsers
.
bind
(
this
.
store
));
eventHub
.
$on
(
'
saveUsers
'
,
this
.
saveUsers
);
},
methods
:
{
addCurrentUser
()
{
this
.
store
.
addCurrentUser
();
this
.
saveUsers
();
},
saveUsers
()
{
this
.
loading
=
true
;
this
.
service
.
update
(
this
.
store
.
getUserIds
())
.
then
((
response
)
=>
{
this
.
loading
=
false
;
this
.
store
.
saveUsers
(
response
.
data
.
assignees
);
}).
catch
(()
=>
{
this
.
loading
=
false
;
return
new
Flash
(
'
An error occured while saving assignees
'
,
'
alert
'
);
});
},
},
components
:
{
'
no-assignee
'
:
NoAssignee
,
'
single-assignee
'
:
SingleAssignee
,
'
multiple-assignees
'
:
MultipleAssignees
,
'
assignee-title
'
:
AssigneeTitle
,
'
collapsed-assignees
'
:
CollapsedAssignees
,
},
template
:
`
<div>
<assignee-title
:numberOfAssignees="store.users.length"
:loading="loading"
:editable="store.editable"
/>
<collapsed-assignees :users="store.users"/>
<div class="value" v-if="!loading">
<no-assignee v-if="numberOfAssignees === 0" />
<single-assignee
v-else-if="numberOfAssignees === 1"
:rootPath="store.rootPath"
:user="store.users[0]"
/>
<multiple-assignees
v-else
:rootPath="store.rootPath"
:users="store.users"
/>
</div>
</div>
`
,
};
app/assets/javascripts/sidebar_assignees/stores/sidebar_assignees_store.js
View file @
98d83965
...
...
@@ -3,19 +3,19 @@ import '~/flash';
export
default
class
SidebarAssigneesStore
{
constructor
(
store
)
{
const
{
currentUser
Id
,
service
,
rootPath
,
editable
}
=
store
;
const
{
currentUser
,
assignees
,
rootPath
,
editable
}
=
store
;
this
.
currentUserId
=
currentUserId
;
this
.
service
=
service
;
this
.
currentUser
=
currentUser
;
this
.
rootPath
=
rootPath
;
this
.
users
=
[];
this
.
saved
=
true
;
this
.
loading
=
false
;
this
.
editable
=
editable
;
this
.
defaultRenderCount
=
5
;
assignees
.
forEach
(
a
=>
this
.
addUser
(
this
.
destructUser
(
a
)));
}
addUser
(
user
,
saved
=
false
)
{
addUser
(
user
)
{
const
{
id
,
name
,
username
,
avatarUrl
}
=
user
;
this
.
users
.
push
({
...
...
@@ -24,48 +24,47 @@ export default class SidebarAssigneesStore {
username
,
avatarUrl
,
});
// !saved means that this user was added to UI but not service
this
.
saved
=
saved
;
console
.
log
(
`addUser()`
);
console
.
log
(
user
);
}
addCurrentUser
()
{
this
.
addUser
({
id
:
this
.
currentUserId
,
});
this
.
saveUsers
();
this
.
addUser
(
this
.
currentUser
);
}
removeUser
(
id
)
{
this
.
saved
=
false
;
console
.
log
(
`removeUser()`
);
console
.
log
(
id
);
this
.
users
=
this
.
users
.
filter
(
u
=>
u
.
id
!==
id
);
}
saveUsers
()
{
removeAllUsers
()
{
this
.
users
=
[];
}
getUserIds
()
{
console
.
log
(
`getUserIds`
);
const
ids
=
this
.
users
.
map
(
u
=>
u
.
id
);
// If there are no ids, that means we have to unassign (which is id = 0)
const
payload
=
ids
.
length
>
0
?
ids
:
[
0
];
this
.
loading
=
true
;
this
.
service
.
update
(
payload
)
.
then
((
response
)
=>
{
const
data
=
response
.
data
;
const
assignees
=
data
.
assignees
;
if
(
ids
.
length
>
0
&&
ids
[
0
]
==
undefined
)
{
debugger
}
// If there are no ids, that means we have to unassign (which is id = 0)
return
ids
.
length
>
0
?
ids
:
[
0
];
}
this
.
users
=
[];
destructUser
(
u
)
{
return
{
id
:
u
.
id
,
name
:
u
.
name
,
username
:
u
.
username
,
avatarUrl
:
u
.
avatar_url
,
};
}
assignees
.
forEach
(
a
=>
this
.
addUser
({
id
:
a
.
id
,
name
:
a
.
name
,
username
:
a
.
username
,
avatarUrl
:
a
.
avatar_url
,
},
true
));
saveUsers
(
assignees
)
{
this
.
users
=
[];
this
.
saved
=
true
;
this
.
loading
=
false
;
}).
catch
(()
=>
{
this
.
loading
=
false
;
return
new
Flash
(
'
An error occured while saving assignees
'
,
'
alert
'
);
});
assignees
.
forEach
(
a
=>
this
.
addUser
(
this
.
destructUser
(
a
)));
}
}
app/assets/javascripts/users_select.js
View file @
98d83965
...
...
@@ -3,6 +3,7 @@
/* global ListUser */
import
Vue
from
'
vue
'
;
import
eventHub
from
'
./sidebar_assignees/event_hub
'
;
(
function
()
{
var
bind
=
function
(
fn
,
me
)
{
return
function
()
{
return
fn
.
apply
(
me
,
arguments
);
};
},
...
...
@@ -250,7 +251,8 @@ import Vue from 'vue';
defaultLabel
:
defaultLabel
,
hidden
:
function
(
e
)
{
if
(
$dropdown
.
hasClass
(
'
js-multiselect
'
))
{
gl
.
sidebarAssigneesOptions
.
store
.
saveUsers
();
// gl.sidebarAssigneesOptions.store.saveUsers();
eventHub
.
$emit
(
'
saveUsers
'
);
}
$selectbox
.
hide
();
...
...
@@ -274,12 +276,12 @@ import Vue from 'vue';
// Unassigned selected
previouslySelected
.
each
((
index
,
element
)
=>
{
const
id
=
parseInt
(
element
.
value
,
10
);
gl
.
sidebarAssigneesOptions
.
store
.
removeUser
(
id
);
element
.
remove
();
});
eventHub
.
$emit
(
'
removeAllUsers
'
);
}
else
if
(
isActive
)
{
// user selected
gl
.
sidebarAssigneesOptions
.
store
.
addUser
(
{
eventHub
.
$emit
(
'
addUser
'
,
{
id
:
user
.
id
,
name
:
user
.
name
,
username
:
user
.
username
,
...
...
@@ -292,7 +294,6 @@ import Vue from 'vue';
if
(
unassignedSelected
)
{
unassignedSelected
.
remove
();
gl
.
sidebarAssigneesOptions
.
store
.
removeUser
(
unassignedSelected
);
}
}
else
{
if
(
previouslySelected
.
length
===
0
)
{
...
...
@@ -301,7 +302,7 @@ import Vue from 'vue';
}
// User unselected
gl
.
sidebarAssigneesOptions
.
store
.
removeUser
(
user
.
id
);
eventHub
.
$emit
(
'
removeUser
'
,
user
.
id
);
}
}
...
...
app/controllers/projects/issues_controller.rb
View file @
98d83965
...
...
@@ -157,7 +157,7 @@ class Projects::IssuesController < Projects::ApplicationController
if
@issue
.
valid?
render
json:
@issue
.
to_json
(
methods:
[
:task_status
,
:task_status_short
],
include:
{
milestone:
{},
assignees:
{
only:
[
:name
,
:username
],
methods:
[
:avatar_url
]
},
assignees:
{
only:
[
:
id
,
:
name
,
:username
],
methods:
[
:avatar_url
]
},
labels:
{
methods: :text_color
}
})
else
render
json:
{
errors:
@issue
.
errors
.
full_messages
},
status: :unprocessable_entity
...
...
app/views/shared/issuable/_sidebar.html.haml
View file @
98d83965
...
...
@@ -24,14 +24,24 @@
=
form_for
[
@project
.
namespace
.
becomes
(
Namespace
),
@project
,
issuable
],
remote:
true
,
format: :json
,
html:
{
class:
'issuable-context-form inline-update js-issuable-update'
}
do
|
f
|
.block.assignee
-
if
issuable
.
instance_of?
(
Issue
)
#js-vue-sidebar-assignees
{
data:
{
path:
issuable_json_path
(
issuable
),
field:
"#{issuable.to_ability_name}[assignee_ids]"
,
'editable'
=>
can_edit_issuable
?
true
:
false
,
user:
{
id:
current_user
.
id
},
root:
{
path:
root_path
}
}
}
#js-vue-sidebar-assignees
{
data:
{
path:
issuable_json_path
(
issuable
),
field:
"#{issuable.to_ability_name}[assignee_ids]"
,
'editable'
=>
can_edit_issuable
?
true
:
false
,
user:
{
id:
current_user
.
id
},
name:
current_user
.
name
,
username:
current_user
.
username
,
avatar_url:
current_user
.
avatar_url
,
root:
{
path:
root_path
}
}
}
.title.hide-collapsed
Assignee
=
icon
(
'spinner spin'
,
class:
'block-loading'
,
'aria-hidden'
:
'true'
)
-
if
can_edit_issuable
=
link_to
'Edit'
,
'#'
,
class:
'edit-link pull-right'
-
content_for
:page_specific_javascripts
do
=
page_specific_javascript_bundle_tag
(
'vue_sidebar_assignees'
)
=
page_specific_javascript_bundle_tag
(
'sidebar_assignees'
)
:javascript
gl
.
sidebarAssigneesData
=
[];
-
issuable
.
assignees
.
each
do
|
assignee
|
:javascript
gl
.
sidebarAssigneesData
.
push
({
id
:
#{
assignee
.
id
}
,
name
:
"
#{
assignee
.
name
}
"
,
username
:
"
#{
assignee
.
username
}
"
,
avatar_url
:
"
#{
assignee
.
avatar_url
}
"
})
-
else
.sidebar-collapsed-icon.sidebar-collapsed-user
{
data:
{
toggle:
"tooltip"
,
placement:
"left"
,
container:
"body"
},
title:
(
issuable
.
assignee
.
name
if
issuable
.
assignee
)
}
-
if
issuable
.
assignee
...
...
@@ -220,19 +230,6 @@
=
project_ref
=
clipboard_button
(
clipboard_text:
project_ref
,
title:
"Copy reference to clipboard"
,
placement:
"left"
)
-
if
issuable
.
instance_of?
(
Issue
)
-
issuable
.
assignees
.
each
do
|
assignee
|
:javascript
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
gl
.
sidebarAssigneesOptions
.
store
.
addUser
({
id
:
parseInt
(
"
#{
assignee
.
id
}
"
,
10
),
name
:
"
#{
assignee
.
name
}
"
,
username
:
"
#{
assignee
.
username
}
"
,
avatarUrl
:
"
#{
assignee
.
avatar_url
}
"
},
true
);
});
:javascript
gl
.
IssuableResource
=
new
gl
.
SubbableResource
(
'
#{
issuable_json_path
(
issuable
)
}
'
);
new
gl
.
IssuableTimeTracking
(
"
#{
escape_javascript
(
serialize_issuable
(
issuable
))
}
"
);
...
...
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