[#369] project collaborators page: in progress
This commit is contained in:
parent
6dc75e264d
commit
2a5ec70a6b
2
Gemfile
2
Gemfile
|
@ -47,7 +47,7 @@ gem 'wikicloth'
|
|||
gem 'newrelic_rpm'
|
||||
gem 'whenever', '~> 0.9.0', require: false
|
||||
|
||||
gem 'jbuilder', '~> 2.1'
|
||||
gem 'jbuilder', '~> 2.2'
|
||||
gem 'rails3-jquery-autocomplete'
|
||||
gem 'will_paginate', '~> 3.0'
|
||||
gem 'meta-tags', '~> 2.0', require: 'meta_tags'
|
||||
|
|
|
@ -213,7 +213,7 @@ GEM
|
|||
inherited_resources (1.4.1)
|
||||
has_scope (~> 0.6.0.rc)
|
||||
responders (~> 1.0.0.rc)
|
||||
jbuilder (2.1.3)
|
||||
jbuilder (2.2.4)
|
||||
activesupport (>= 3.0.0, < 5)
|
||||
multi_json (~> 1.2)
|
||||
jquery-migrate-rails (1.2.1)
|
||||
|
@ -563,7 +563,7 @@ DEPENDENCIES
|
|||
haml-rails (~> 0.5)
|
||||
highline (~> 1.6.20)
|
||||
hirb
|
||||
jbuilder (~> 2.1)
|
||||
jbuilder (~> 2.2)
|
||||
jquery-migrate-rails
|
||||
jquery-rails (~> 2.3)
|
||||
js-routes
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
CollaboratorsController = (dataservice, Collaborator, $http) ->
|
||||
vm = this
|
||||
|
||||
vm.new_role = 'reader'
|
||||
|
||||
vm.getCollaborators = (val) ->
|
||||
return [] if val.length <= 2
|
||||
Collaborator.find(vm.name_with_owner, val)
|
||||
|
||||
vm.selectCollaborator = (item, model, label) ->
|
||||
vm.selected_new_collaborator = item
|
||||
false
|
||||
|
||||
vm.addCollaborator = ->
|
||||
promise = Collaborator.add(vm.name_with_owner,
|
||||
vm.selected_new_collaborator,
|
||||
vm.new_role,
|
||||
vm.project_id)
|
||||
promise.success (data) ->
|
||||
vm.collaborators.push data
|
||||
|
||||
vm.selected_new_collaborator = null
|
||||
false
|
||||
|
||||
init = (dataservice) ->
|
||||
vm.name_with_owner = dataservice.name_with_owner
|
||||
vm.project_id = dataservice.project_id
|
||||
|
||||
vm.collaborators = dataservice.collaborators
|
||||
|
||||
init(dataservice)
|
||||
return true
|
||||
|
||||
|
||||
angular
|
||||
.module("RosaABF")
|
||||
.controller "CollaboratorsController", CollaboratorsController
|
||||
|
||||
CollaboratorsController.$inject = ['CollaboratorsInitializer', 'Collaborator', '$http']
|
|
@ -1,27 +0,0 @@
|
|||
RosaABF.factory('ApiCollaborator', ['$resource', function($resource) {
|
||||
|
||||
var CollaboratorResource = $resource(
|
||||
'/:owner/:project/collaborators/:id?format=json',
|
||||
{
|
||||
owner: '@project.owner_uname',
|
||||
project: '@project.name',
|
||||
id: '@id'
|
||||
},
|
||||
{
|
||||
update: {
|
||||
method: 'PUT',
|
||||
isArray : false
|
||||
},
|
||||
find: {
|
||||
url: '/:owner/:project/collaborators/find',
|
||||
format: 'json',
|
||||
method: 'GET',
|
||||
isArray : true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
resource : CollaboratorResource
|
||||
}
|
||||
}]);
|
|
@ -0,0 +1,34 @@
|
|||
collaboratorService = ($http) ->
|
||||
{
|
||||
find: (name_with_owner, val) ->
|
||||
path = Routes.find_project_collaborators_path(
|
||||
{
|
||||
name_with_owner: name_with_owner,
|
||||
term: val
|
||||
}
|
||||
)
|
||||
|
||||
$http.get(path).then (response) ->
|
||||
response.data
|
||||
|
||||
add: (name_with_owner, selected, role, project_id) ->
|
||||
path = Routes.project_collaborators_path(
|
||||
{
|
||||
name_with_owner: name_with_owner,
|
||||
collaborator: {
|
||||
actor_id: selected.actor_id
|
||||
actor_type: selected.actor_type
|
||||
role: role
|
||||
project_id: project_id
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
$http.post(path)
|
||||
}
|
||||
|
||||
angular
|
||||
.module("RosaABF")
|
||||
.factory "Collaborator", collaboratorService
|
||||
|
||||
collaboratorService.$inject = ['$http']
|
|
@ -1,65 +0,0 @@
|
|||
RosaABF.controller('CollaboratorsController', ['$scope', 'ApiCollaborator', function($scope, ApiCollaborator) {
|
||||
|
||||
$scope.def_role = "<%=Relation::ROLES.first%>";
|
||||
|
||||
$scope.popup = $('#add_collaborator_form .users-search-popup');
|
||||
$scope.owner = $('#owner_name').val();
|
||||
$scope.project = $('#project_name').val();
|
||||
$scope.resource = ApiCollaborator.resource;
|
||||
$scope.collaborators = [];
|
||||
|
||||
$scope.new_collaborators = [];
|
||||
|
||||
$scope.initNewCollaborator = function(c) {
|
||||
if (c) {
|
||||
c.term = c.actor_name;
|
||||
c.collaborator.role = $scope.def_role;
|
||||
} else {
|
||||
c = {collaborator: {role: $scope.def_role}};
|
||||
}
|
||||
$scope.new_collaborator = c;
|
||||
}
|
||||
$scope.initNewCollaborator();
|
||||
|
||||
$scope.getCollaborators = function() {
|
||||
$scope.collaborators = $scope.resource.query({owner: $scope.owner, project: $scope.project});
|
||||
}
|
||||
$scope.getCollaborators();
|
||||
|
||||
$scope.update = function(collaborator) {
|
||||
collaborator.$update();
|
||||
}
|
||||
|
||||
$scope.deleteCollaborators = function() {
|
||||
var collaborators = [];
|
||||
_.each($scope.collaborators, function(collaborator){
|
||||
if(collaborator.removed) {
|
||||
collaborator.$delete();
|
||||
} else {
|
||||
collaborators.push(collaborator);
|
||||
}
|
||||
$scope.collaborators = collaborators;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.search = function() {
|
||||
if ($scope.new_collaborator.term.length > 2) {
|
||||
$scope.new_collaborators = $scope.resource.find(
|
||||
{owner: $scope.owner, project: $scope.project, term: $scope.new_collaborator.term});
|
||||
$scope.popup.show();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.select = function(c) {
|
||||
$scope.initNewCollaborator(c);
|
||||
$scope.popup.hide();
|
||||
}
|
||||
|
||||
$scope.add = function() {
|
||||
$scope.new_collaborator.$save(function() {
|
||||
$scope.collaborators.push($scope.new_collaborator);
|
||||
$scope.initNewCollaborator();
|
||||
});
|
||||
}
|
||||
|
||||
}]);
|
|
@ -17,24 +17,22 @@ class Projects::CollaboratorsController < Projects::BaseController
|
|||
users = User.not_member_of(@project)
|
||||
groups = Group.not_member_of(@project)
|
||||
if params[:term].present?
|
||||
users = users.search(params[:term])
|
||||
groups = groups.search(params[:term])
|
||||
users = users.search(params[:term]).first(5)
|
||||
groups = groups.search(params[:term]).first(5)
|
||||
end
|
||||
@collaborators = (users | groups).map{|act| Collaborator.new(actor: act, project: @project)}
|
||||
respond_with @collaborators do |format|
|
||||
format.json { render 'index' }
|
||||
end
|
||||
respond_with @collaborators
|
||||
end
|
||||
|
||||
def create
|
||||
@collaborator = Collaborator.new(params[:collaborator])
|
||||
@collaborator.project = @project
|
||||
if @collaborator.save
|
||||
respond_with @collaborator do |format|
|
||||
respond_to do |format|
|
||||
if @collaborator.save
|
||||
format.json { render partial: 'collaborator', locals: {collaborator: @collaborator} }
|
||||
else
|
||||
format.json { render text: 'error', status: 422 }
|
||||
end
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -87,6 +87,10 @@ class Collaborator
|
|||
end
|
||||
end
|
||||
|
||||
def actor_uname
|
||||
@actor.uname
|
||||
end
|
||||
|
||||
def project_id
|
||||
@project.try(:id)
|
||||
end
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
json.(collaborator, :id, :actor_name)
|
||||
json.collaborator do # attr_accessible for AngularJS
|
||||
json.(collaborator, :role, :actor_id, :actor_type, :project_id)
|
||||
end
|
||||
json.project do
|
||||
json.(collaborator.project, :name, :owner_uname)
|
||||
end
|
||||
json.avatar avatar_url(collaborator.actor)
|
||||
json.actor_path participant_path(collaborator.actor)
|
||||
json.(collaborator, :id, :actor_name, :actor_type, :actor_id, :role)
|
||||
|
||||
json.avatar avatar_url(collaborator.actor)
|
||||
json.path participant_path(collaborator.actor)
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
json.array!(collaborators) do |collaborator|
|
||||
json.partial! 'projects/collaborators/collaborator', collaborator: collaborator
|
||||
json.partial! 'collaborator.json', collaborator: collaborator
|
||||
end
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<script>
|
||||
angular.module('RosaABF').service('CollaboratorsInitializer', function(){
|
||||
return {
|
||||
name_with_owner: '<%= @project.name_with_owner %>',
|
||||
project_id: <%= @project.id %>,
|
||||
collaborators: <%= render('collaborators.json', collaborators: @collaborators).html_safe %>
|
||||
};
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,66 @@
|
|||
table.table.table-striped
|
||||
thead
|
||||
tr
|
||||
th
|
||||
th
|
||||
= t('layout.collaborators.members')
|
||||
th.buttons.text-center colspan=3
|
||||
= t('layout.collaborators.roles')
|
||||
th.buttons
|
||||
= t('layout.remove')
|
||||
tbody
|
||||
tr ng-repeat = 'member in membersCtrl.collaborators'
|
||||
td
|
||||
= check_box_tag 'members[]', 'true', false,
|
||||
'ng-model' => 'member.check_delete',
|
||||
'ng-value' => 'member.check_delete'
|
||||
td
|
||||
span
|
||||
img ng-src = '{{ member.avatar_path }}' size = '30x30'
|
||||
|
|
||||
a[ ng-href = 'member.path' ] {{ member.actor_name }}
|
||||
- Relation::ROLES.each do |role|
|
||||
td
|
||||
input[
|
||||
type = 'radio'
|
||||
ng-model = 'member.role'
|
||||
value = role ]
|
||||
|
|
||||
= t("layout.collaborators.role_names.#{ role }")
|
||||
td
|
||||
a[ ng-click = 'membersCtrl.updateCollaborator(member)'
|
||||
ng-confirm-click = t('layout.confirm') ]
|
||||
|
||||
span.glyphicon.glyphicon-ok
|
||||
|
|
||||
|
||||
a[ ng-click = 'membersCtrl.removeCollaborator(member)'
|
||||
ng-confirm-click = t('layout.confirm') ]
|
||||
span.glyphicon.glyphicon-remove
|
||||
|
|
||||
|
||||
|
||||
= submit_tag t('layout.delete'), class: 'btn btn-danger',
|
||||
'ng-click' => 'membersCtrl.RemoveCollaborators()'
|
||||
|
||||
hr
|
||||
|
||||
= simple_form_for :user, url: '',
|
||||
html: { class: 'form-inline' },
|
||||
wrapper: :inline_form do |f|
|
||||
|
||||
=> f.input :uname,
|
||||
input_html: {'ng-model' => 'membersCtrl.selected_new_collaborator.actor_uname',
|
||||
'typeahead' => 'member.actor_uname for member in membersCtrl.getCollaborators($viewValue)',
|
||||
'typeahead-on-select' => 'membersCtrl.selectCollaborator($item, $model, $label)' }
|
||||
|
||||
=> f.input :role,
|
||||
collection: options_for_collaborators_roles_select,
|
||||
input_html: { name: :role, 'ng-model' => 'membersCtrl.new_role' },
|
||||
include_blank: false
|
||||
|
||||
= f.button :submit, t('layout.add'), 'ng-click' => 'membersCtrl.addCollaborator()',
|
||||
'ng-disabled' => '!membersCtrl.selected_new_collaborator'
|
||||
|
||||
- content_for :additional_scripts do
|
||||
= render 'init_service.js.erb'
|
|
@ -0,0 +1,3 @@
|
|||
json.array!(@collaborators) do |collaborator|
|
||||
json.(collaborator, :actor_uname, :actor_id, :actor_type)
|
||||
end
|
|
@ -1,74 +0,0 @@
|
|||
-set_meta_tags title: [title_object(@project), t('layout.projects.members')]
|
||||
= render 'sidebar'
|
||||
= render 'submenu'
|
||||
|
||||
%a{name: 'users'}
|
||||
%h3= t("layout.users.list_header")
|
||||
|
||||
#collaborators{'ng-controller' => 'CollaboratorsController'}
|
||||
= hidden_field_tag :owner_name, @project.try(:owner).try(:uname)
|
||||
= hidden_field_tag :project_name, @project.try(:name)
|
||||
|
||||
#add_collaborator_form
|
||||
.admin-search.withimage
|
||||
.img
|
||||
%img{alt: 'avatar', 'ng-src' => '{{new_collaborator.avatar}}', 'ng-show' => 'new_collaborator.avatar'}
|
||||
= text_field_tag :collaborator_name, nil, 'ng-model' => 'new_collaborator.term', 'ng-keyup' => 'search()'
|
||||
|
||||
.admin-role
|
||||
.lineForm
|
||||
= select_tag 'role', options_for_collaborators_roles_select, 'ng-model' => 'new_collaborator.collaborator.role'
|
||||
.admin-add
|
||||
%a.button{rel: 'nofollow', href: '', 'ng-click' => 'add()'}
|
||||
= t('layout.add')
|
||||
.both
|
||||
|
||||
.users-search-popup
|
||||
.header
|
||||
.title= t('layout.issues.search_user')
|
||||
%span.icon-remove-circle
|
||||
.list
|
||||
.people{'ng-repeat' => 'c in new_collaborators', 'ng-click' => 'select(c)'}
|
||||
.avatar
|
||||
%img{width: '16px', 'ng-src' => '{{c.avatar}}', alt: 'avatar'}
|
||||
.name {{c.actor_name}}
|
||||
.both
|
||||
|
||||
.nothing{'ng-hide' => 'new_collaborators.length > 0'}= t('layout.issues.nothing_to_show')
|
||||
.both
|
||||
.both
|
||||
|
||||
%table.tablesorter{cellpadding: "0", cellspacing: "0"}
|
||||
%thead
|
||||
%tr
|
||||
%th.centered
|
||||
%span.delete{'ng-click' => 'deleteCollaborators()'}
|
||||
%th
|
||||
= t("layout.collaborators.members")
|
||||
%th{colspan: "3"}
|
||||
= t("layout.collaborators.roles")
|
||||
%tr.search
|
||||
%th{colspan: "5"}
|
||||
%input{type: "text", placeholder: "#{t('layout.filter_by_name')}", 'ng-model' => 'query.actor_name'}
|
||||
|
||||
%tbody
|
||||
%tr{'ng-repeat' => 'c in collaborators | filter:query'}
|
||||
%td
|
||||
%input{type: 'checkbox', 'ng-model' => 'c.removed' }
|
||||
%td
|
||||
.img
|
||||
%img{'ng-src' => '{{c.avatar}}', alt: 'avatar' }
|
||||
.forimg
|
||||
%a{'ng-href' => '{{c.actor_path}}'} {{c.actor_name}}
|
||||
|
||||
- Relation::ROLES.each do |role|
|
||||
%td
|
||||
.radio
|
||||
%input{type: 'radio', 'ng-model' => 'c.collaborator.role', value: role, 'ng-click' => 'update(c)'}
|
||||
.forradio
|
||||
%label= t("layout.collaborators.role_names.#{role}")
|
||||
|
||||
|
||||
%br
|
||||
|
||||
.both
|
|
@ -0,0 +1,10 @@
|
|||
-set_meta_tags title: [title_object(@project), t('layout.projects.members')]
|
||||
= render 'submenu'
|
||||
|
||||
.container
|
||||
.row
|
||||
.col-md-offset-2.col-md-8= render 'settings_menu'
|
||||
.col-md-9.col-md-offset-2[ ng-controller = 'CollaboratorsController as membersCtrl'
|
||||
ng-cloak = 'true' ]
|
||||
|
||||
= render 'members_table'
|
|
@ -1 +1 @@
|
|||
json.partial! 'collaborators', collaborators: @collaborators
|
||||
json.partial! 'collaborators.json', collaborators: @collaborators
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
= t("layout.remove")
|
||||
tbody
|
||||
- if update_roles_path
|
||||
- actors = editable_object.actors.where(actor_id: members.map(&:id), actor_type: 'User').to_a
|
||||
- actors ||= editable_object.actors
|
||||
- members.each do |user|
|
||||
tr
|
||||
- if can? :remove_members, editable_object
|
||||
|
@ -63,8 +63,9 @@
|
|||
wrapper: :inline_form do |f|
|
||||
|
||||
= hidden_field_tag 'member_id', nil, id: 'member_id_field'
|
||||
- autocomplete_path ||= autocomplete_user_uname_autocompletes_path
|
||||
= f.input :uname,
|
||||
input_html: { 'data-ajax' => autocomplete_user_uname_autocompletes_path,
|
||||
input_html: { 'data-ajax' => autocomplete_path,
|
||||
'data-id' => '#member_id_field',
|
||||
class: 'typeahead' }
|
||||
|
||||
|
|
Loading…
Reference in New Issue