Merge branch 'members-ui' into 'master'
Project members UI ## What does this MR do? New UI for project members that includes groups. ## Screenshots (if relevant) ### Project members ![Screen_Shot_2016-09-02_at_15.13.27](/uploads/b9d4a634d44b7b7bbb6eddb10aee86bd/Screen_Shot_2016-09-02_at_15.13.27.png) ### Group members ![Screen_Shot_2016-09-02_at_15.13.36](/uploads/c15c173e68b2c0b49bcd06ca560269d3/Screen_Shot_2016-09-02_at_15.13.36.png) ## What are the relevant issue numbers? Part of #19868 Closes #21320 See merge request !6148
This commit is contained in:
commit
66855f6262
|
@ -140,12 +140,12 @@
|
|||
break;
|
||||
case 'groups:group_members:index':
|
||||
new gl.MemberExpirationDate();
|
||||
new GroupMembers();
|
||||
new gl.Members();
|
||||
new UsersSelect();
|
||||
break;
|
||||
case 'projects:project_members:index':
|
||||
new gl.MemberExpirationDate();
|
||||
new ProjectMembers();
|
||||
new gl.Members();
|
||||
new UsersSelect();
|
||||
break;
|
||||
case 'groups:new':
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
(function() {
|
||||
this.GroupMembers = (function() {
|
||||
function GroupMembers() {
|
||||
$('li.group_member').bind('ajax:success', function() {
|
||||
return $(this).fadeOut();
|
||||
});
|
||||
}
|
||||
|
||||
return GroupMembers;
|
||||
|
||||
})();
|
||||
|
||||
}).call(this);
|
|
@ -14,14 +14,18 @@
|
|||
inputs.datepicker({
|
||||
dateFormat: 'yy-mm-dd',
|
||||
minDate: 1,
|
||||
onSelect: toggleClearInput
|
||||
onSelect: function () {
|
||||
$(this).trigger('change');
|
||||
toggleClearInput.call(this);
|
||||
}
|
||||
});
|
||||
|
||||
inputs.next('.js-clear-input').on('click', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var input = $(this).closest('.clearable-input').find('.js-access-expiration-date');
|
||||
input.datepicker('setDate', null);
|
||||
input.datepicker('setDate', null)
|
||||
.trigger('change');
|
||||
toggleClearInput.call(input);
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
((w) => {
|
||||
w.gl = w.gl || {};
|
||||
|
||||
class Members {
|
||||
constructor() {
|
||||
this.addListeners();
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
$('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow);
|
||||
$('.js-member-update-control').off('change').on('change', this.formSubmit);
|
||||
$('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess);
|
||||
}
|
||||
|
||||
removeRow(e) {
|
||||
const $target = $(e.target);
|
||||
|
||||
if ($target.hasClass('btn-remove')) {
|
||||
$target.closest('.member')
|
||||
.fadeOut(function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
formSubmit() {
|
||||
$(this).closest('form').trigger("submit.rails").end().disable();
|
||||
}
|
||||
|
||||
formSuccess() {
|
||||
$(this).find('.js-member-update-control').enable();
|
||||
}
|
||||
}
|
||||
|
||||
gl.Members = Members;
|
||||
})(window);
|
|
@ -1,10 +0,0 @@
|
|||
(function() {
|
||||
this.ProjectMembers = (function() {
|
||||
function ProjectMembers() {
|
||||
$('li.project_member').bind('ajax:success', function() {
|
||||
return $(this).fadeOut();
|
||||
});
|
||||
}
|
||||
return ProjectMembers;
|
||||
})();
|
||||
}).call(this);
|
|
@ -125,7 +125,3 @@ label {
|
|||
border-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.help-block {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
|
@ -128,6 +128,10 @@ ul.content-list {
|
|||
color: $gl-dark-link-color;
|
||||
}
|
||||
|
||||
.member-group-link {
|
||||
color: $blue-normal;
|
||||
}
|
||||
|
||||
.description {
|
||||
p {
|
||||
@include str-truncated;
|
||||
|
@ -168,6 +172,14 @@ ul.content-list {
|
|||
}
|
||||
}
|
||||
|
||||
.member-controls {
|
||||
float: none;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
// When dragging a list item
|
||||
&.ui-sortable-helper {
|
||||
border-bottom: none;
|
||||
|
|
|
@ -13,6 +13,11 @@
|
|||
.dropdown-menu-toggle {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
margin-top: -2px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
background: none;
|
||||
|
||||
.select2-search-field input {
|
||||
padding: $gl-padding / 2;
|
||||
padding: 5px $gl-padding / 2;
|
||||
font-size: 13px;
|
||||
height: auto;
|
||||
font-family: inherit;
|
||||
|
@ -101,7 +101,7 @@
|
|||
}
|
||||
|
||||
.select2-search-choice {
|
||||
margin: 8px 0 0 8px;
|
||||
margin: 5px 0 0 8px;
|
||||
box-shadow: none;
|
||||
border-color: $input-border;
|
||||
color: $gl-text-color;
|
||||
|
|
|
@ -1,17 +1,3 @@
|
|||
.member-search-form {
|
||||
float: left;
|
||||
|
||||
input[type='search'] {
|
||||
width: 225px;
|
||||
vertical-align: bottom;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
width: 100px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.milestone-row {
|
||||
@include str-truncated(90%);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
.project-members-title {
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.member {
|
||||
.list-item-name {
|
||||
@media (min-width: $screen-sm-min) {
|
||||
float: left;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
@media (min-width: $screen-sm-min) {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
width: 400px;
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.form-horizontal {
|
||||
margin-top: 5px;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-remove {
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.member-form-control {
|
||||
@media (max-width: $screen-xs-max) {
|
||||
padding: 5px 0;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.member-access-text {
|
||||
margin-left: auto;
|
||||
line-height: 43px;
|
||||
}
|
||||
|
||||
.member.existing-title {
|
||||
@media (min-width: $screen-sm-min) {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.member-search-form {
|
||||
position: relative;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding-right: 35px;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.member-search-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 35px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
color: $gray-darkest;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
class Projects::GroupLinksController < Projects::ApplicationController
|
||||
layout 'project_settings'
|
||||
before_action :authorize_admin_project!
|
||||
before_action :authorize_admin_project_member!, only: [:update]
|
||||
|
||||
def index
|
||||
@group_links = project.project_group_links.all
|
||||
|
@ -27,9 +28,26 @@ class Projects::GroupLinksController < Projects::ApplicationController
|
|||
redirect_to namespace_project_group_links_path(project.namespace, project)
|
||||
end
|
||||
|
||||
def update
|
||||
@group_link = @project.project_group_links.find(params[:id])
|
||||
|
||||
@group_link.update_attributes(group_link_params)
|
||||
end
|
||||
|
||||
def destroy
|
||||
project.project_group_links.find(params[:id]).destroy
|
||||
|
||||
redirect_to namespace_project_group_links_path(project.namespace, project)
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
redirect_to namespace_project_group_links_path(project.namespace, project)
|
||||
end
|
||||
format.js { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def group_link_params
|
||||
params.require(:group_link).permit(:group_access, :expires_at)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,34 +5,23 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
|||
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
|
||||
|
||||
def index
|
||||
@group_links = @project.project_group_links
|
||||
|
||||
@project_members = @project.project_members
|
||||
@project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
|
||||
|
||||
if params[:search].present?
|
||||
users = @project.users.search(params[:search]).to_a
|
||||
@project_members = @project_members.where(user_id: users)
|
||||
|
||||
@group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
|
||||
end
|
||||
|
||||
@project_members = @project_members.order('access_level DESC')
|
||||
|
||||
@group = @project.group
|
||||
|
||||
if @group
|
||||
@group_members = @group.group_members
|
||||
@group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group)
|
||||
|
||||
if params[:search].present?
|
||||
users = @group.users.search(params[:search]).to_a
|
||||
@group_members = @group_members.where(user_id: users)
|
||||
end
|
||||
|
||||
@group_members = @group_members.order('access_level DESC')
|
||||
end
|
||||
@project_members = @project_members.order(access_level: :desc).page(params[:page])
|
||||
|
||||
@requesters = AccessRequestsFinder.new(@project).execute(current_user)
|
||||
|
||||
@project_member = @project.project_members.new
|
||||
@project_group_links = @project.project_group_links
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -43,6 +32,21 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
|||
current_user: current_user
|
||||
)
|
||||
|
||||
if params[:group_ids].present?
|
||||
group_ids = params[:group_ids].split(',')
|
||||
groups = Group.where(id: group_ids)
|
||||
|
||||
groups.each do |group|
|
||||
next unless can?(current_user, :read_group, group)
|
||||
|
||||
project.project_group_links.create(
|
||||
group: group,
|
||||
group_access: params[:access_level],
|
||||
expires_at: params[:expires_at]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to namespace_project_project_members_path(@project.namespace, @project)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,27 +1,22 @@
|
|||
= form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
|
||||
.form-group
|
||||
= f.label :user_ids, "People", class: 'control-label'
|
||||
.col-sm-10
|
||||
= users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true)
|
||||
.help-block
|
||||
= form_for @group_member, url: group_group_members_path(@group), html: { class: 'users-project-form users-group-form' } do |f|
|
||||
.row
|
||||
.col-md-4.col-lg-6
|
||||
= users_select_tag(:user_ids, multiple: true, class: 'input-clamp', scope: :all, email_user: true)
|
||||
.help-block.append-bottom-10
|
||||
Search for users by name, username, or email, or invite new ones using their email address.
|
||||
|
||||
.form-group
|
||||
= f.label :access_level, "Group Access", class: 'control-label'
|
||||
.col-sm-10
|
||||
= select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "project-access-select select2"
|
||||
.help-block
|
||||
Read more about role permissions
|
||||
%strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
|
||||
.col-md-3.col-lg-2
|
||||
= select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "form-control project-access-select"
|
||||
.help-block.append-bottom-10
|
||||
= link_to "Read more", help_page_path("user/permissions"), class: "vlink"
|
||||
about role permissions
|
||||
|
||||
.form-group
|
||||
= f.label :expires_at, 'Access expiration date', class: 'control-label'
|
||||
.col-sm-10
|
||||
.col-md-3.col-lg-2
|
||||
.clearable-input
|
||||
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
|
||||
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
|
||||
%i.clear-icon.js-clear-input
|
||||
.help-block
|
||||
.help-block.append-bottom-10
|
||||
On this date, the user(s) will automatically lose access to this group and all of its projects.
|
||||
|
||||
.form-actions
|
||||
= f.submit 'Add users to group', class: "btn btn-create"
|
||||
.col-md-2
|
||||
= f.submit 'Add to group', class: "btn btn-create btn-block"
|
||||
|
|
|
@ -1,35 +1,31 @@
|
|||
- page_title "Members"
|
||||
|
||||
.group-members-page.prepend-top-default
|
||||
.project-members-page.prepend-top-default
|
||||
%h4
|
||||
Members
|
||||
%hr
|
||||
- if can?(current_user, :admin_group_member, @group)
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Add new user to group
|
||||
.panel-body
|
||||
%p.light
|
||||
Members of group have access to all group projects.
|
||||
.new-group-member-holder
|
||||
= render "new_group_member"
|
||||
.project-members-new.append-bottom-default
|
||||
%p.clearfix
|
||||
Add new user to
|
||||
%strong= @group.name
|
||||
= render "new_group_member"
|
||||
|
||||
= render 'shared/members/requests', membership_source: @group, requesters: @requesters
|
||||
|
||||
.append-bottom-default.clearfix
|
||||
%h5.member.existing-title
|
||||
Existing users
|
||||
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
|
||||
.form-group
|
||||
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
|
||||
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
|
||||
= icon("search")
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Users with access to
|
||||
%strong #{@group.name}
|
||||
group members
|
||||
%span.badge= @members.total_count
|
||||
.controls
|
||||
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
|
||||
.form-group
|
||||
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
|
||||
= button_tag class: 'btn', title: 'Search' do
|
||||
= icon("search")
|
||||
%ul.content-list
|
||||
= render partial: 'shared/members/member', collection: @members, as: :member
|
||||
= paginate @members, theme: 'gitlab'
|
||||
|
||||
:javascript
|
||||
$('form.member-search-form').on('submit', function(event) {
|
||||
event.preventDefault();
|
||||
Turbolinks.visit(this.action + '?' + $(this).serialize());
|
||||
});
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
:plain
|
||||
$("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @group_member))}');
|
||||
new gl.MemberExpirationDate();
|
||||
var $listItem = $('#{escape_javascript(render('shared/members/member', member: @group_member))}');
|
||||
$("##{dom_id(@group_member)} .list-item-name").replaceWith($listItem.find('.list-item-name'));
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
:plain
|
||||
var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link))}');
|
||||
$("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name'));
|
|
@ -1,7 +1,7 @@
|
|||
.panel.panel-default
|
||||
.panel-heading
|
||||
Group members with access to
|
||||
%strong #{@group.name}
|
||||
group members
|
||||
%span.badge= members.size
|
||||
- if can?(current_user, :admin_group_member, @group)
|
||||
.controls
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
.panel.panel-default.project-members-groups
|
||||
.panel-heading
|
||||
Groups with access to
|
||||
%strong #{@project.name}
|
||||
%span.badge= group_links.size
|
||||
%ul.content-list
|
||||
= render partial: 'shared/members/group', collection: group_links, as: :group_link
|
|
@ -1,27 +1,22 @@
|
|||
= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f|
|
||||
.form-group
|
||||
= f.label :user_ids, "People", class: 'control-label'
|
||||
.col-sm-10
|
||||
= users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true)
|
||||
.help-block
|
||||
= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f|
|
||||
.row
|
||||
.col-md-4.col-lg-6
|
||||
= users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true)
|
||||
.help-block.append-bottom-10
|
||||
Search for users by name, username, or email, or invite new ones using their email address.
|
||||
|
||||
.form-group
|
||||
= f.label :access_level, "Project Access", class: 'control-label'
|
||||
.col-sm-10
|
||||
= select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "project-access-select select2"
|
||||
.help-block
|
||||
Read more about role permissions
|
||||
%strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
|
||||
.col-md-3.col-lg-2
|
||||
= select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select"
|
||||
.help-block.append-bottom-10
|
||||
= link_to "Read more", help_page_path("user/permissions"), class: "vlink"
|
||||
about role permissions
|
||||
|
||||
.form-group
|
||||
= f.label :expires_at, 'Access expiration date', class: 'control-label'
|
||||
.col-sm-10
|
||||
.col-md-3.col-lg-2
|
||||
.clearable-input
|
||||
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
|
||||
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
|
||||
%i.clear-icon.js-clear-input
|
||||
.help-block
|
||||
.help-block.append-bottom-10
|
||||
On this date, the user(s) will automatically lose access to this project.
|
||||
|
||||
.form-actions
|
||||
= f.submit 'Add users to project', class: "btn btn-create"
|
||||
.col-md-2
|
||||
= f.submit "Add to project", class: "btn btn-create btn-block"
|
||||
|
|
|
@ -1,19 +1,7 @@
|
|||
.panel.panel-default
|
||||
.panel-heading
|
||||
Users with access to
|
||||
%strong #{@project.name}
|
||||
project members
|
||||
%span.badge= members.size
|
||||
.controls
|
||||
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
|
||||
.form-group
|
||||
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false }
|
||||
= button_tag class: 'btn', title: 'Search' do
|
||||
= icon("search")
|
||||
%span.badge= @project_members.total_count
|
||||
%ul.content-list
|
||||
= render partial: 'shared/members/member', collection: members, as: :member
|
||||
|
||||
:javascript
|
||||
$('form.member-search-form').on('submit', function (event) {
|
||||
event.preventDefault();
|
||||
Turbolinks.visit(this.action + '?' + $(this).serialize());
|
||||
});
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
- page_title "Members"
|
||||
|
||||
.project-members-page.js-project-members-page.prepend-top-default
|
||||
.project-members-page.prepend-top-default
|
||||
%h4.project-members-title.clearfix
|
||||
Members
|
||||
= link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project"
|
||||
- if can?(current_user, :admin_project_member, @project)
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Add new user to project
|
||||
.controls
|
||||
= link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
|
||||
Import members
|
||||
.panel-body
|
||||
%p.light
|
||||
Users with access to this project are listed below.
|
||||
= render "new_project_member"
|
||||
.project-members-new.append-bottom-default
|
||||
%p.clearfix
|
||||
Add new user to
|
||||
%strong= @project.name
|
||||
= render "new_project_member"
|
||||
|
||||
= render 'shared/members/requests', membership_source: @project, requesters: @requesters
|
||||
= render 'shared/members/requests', membership_source: @project, requesters: @requesters
|
||||
|
||||
.append-bottom-default.clearfix
|
||||
%h5.member.existing-title
|
||||
Existing users and groups
|
||||
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
|
||||
.form-group
|
||||
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
|
||||
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
|
||||
= icon("search")
|
||||
- if @group_links.any?
|
||||
= render 'groups', group_links: @group_links
|
||||
|
||||
= render 'team', members: @project_members
|
||||
|
||||
- if @group
|
||||
= render "group_members", members: @group_members
|
||||
|
||||
- if @project_group_links.any? && @project.allowed_to_share_with_group?
|
||||
= render "shared_group_members"
|
||||
= paginate @project_members, theme: "gitlab"
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
:plain
|
||||
$("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render('shared/members/member', member: @project_member))}');
|
||||
new gl.MemberExpirationDate();
|
||||
var $listItem = $('#{escape_javascript(render('shared/members/member', member: @project_member))}');
|
||||
$("##{dom_id(@project_member)} .list-item-name").replaceWith($listItem.find('.list-item-name'));
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
- group_link = local_assigns[:group_link]
|
||||
- group = group_link.group
|
||||
- can_admin_member = can?(current_user, :admin_project_member, @project)
|
||||
%li.member.group_member{ id: "group_member_#{group_link.id}" }
|
||||
%span{ class: "list-item-name" }
|
||||
= image_tag group_icon(group), class: "avatar s40", alt: ''
|
||||
%strong
|
||||
= link_to group.name, group_path(group)
|
||||
.cgray
|
||||
Joined #{time_ago_with_tooltip(group.created_at)}
|
||||
- if group_link.expires?
|
||||
·
|
||||
%span{ class: ('text-warning' if group_link.expires_soon?) }
|
||||
Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)}
|
||||
.controls.member-controls
|
||||
= form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do
|
||||
= select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can_admin_member
|
||||
.prepend-left-5.clearable-input.member-form-control
|
||||
= text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member
|
||||
%i.clear-icon.js-clear-input
|
||||
- if can_admin_member
|
||||
= link_to namespace_project_group_link_path(@project.namespace, @project, group_link),
|
||||
remote: true,
|
||||
method: :delete,
|
||||
data: { confirm: "Are you sure you want to remove #{group.name}?" },
|
||||
class: 'btn btn-remove prepend-left-10' do
|
||||
%span.visible-xs-block
|
||||
Delete
|
||||
= icon('trash', class: 'hidden-xs')
|
|
@ -1,59 +1,29 @@
|
|||
- show_roles = local_assigns.fetch(:show_roles, true)
|
||||
- show_controls = local_assigns.fetch(:show_controls, true)
|
||||
- user = member.user
|
||||
- user = local_assigns.fetch(:user, member.user)
|
||||
- source = member.source
|
||||
- can_admin_member = can?(current_user, action_member_permission(:update, member), member)
|
||||
|
||||
%li.js-toggle-container{ class: dom_class(member), id: dom_id(member) }
|
||||
- if show_roles
|
||||
.controls
|
||||
%strong.control-text= member.human_access
|
||||
- if show_controls
|
||||
- if !user && can?(current_user, action_member_permission(:admin, member), member.source)
|
||||
= link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
|
||||
method: :post,
|
||||
class: 'btn'
|
||||
|
||||
- if can?(current_user, action_member_permission(:update, member), member)
|
||||
= button_tag icon('pencil'),
|
||||
type: 'button',
|
||||
class: 'btn inline js-toggle-button',
|
||||
title: 'Edit'
|
||||
|
||||
- if member.request?
|
||||
= link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]),
|
||||
method: :post,
|
||||
class: 'btn btn-success',
|
||||
title: 'Grant access'
|
||||
|
||||
- if can?(current_user, action_member_permission(:destroy, member), member)
|
||||
- if current_user == user
|
||||
= link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]),
|
||||
method: :delete,
|
||||
data: { confirm: leave_confirmation_message(member.source) },
|
||||
class: 'btn btn-remove'
|
||||
- else
|
||||
= link_to icon('trash'), member,
|
||||
remote: true,
|
||||
method: :delete,
|
||||
data: { confirm: remove_member_message(member) },
|
||||
class: 'btn btn-remove',
|
||||
title: remove_member_title(member)
|
||||
|
||||
|
||||
%span{ class: ("list-item-name" if show_controls) }
|
||||
%li.member{ class: dom_class(member), id: dom_id(member) }
|
||||
%span.list-item-name
|
||||
- if user
|
||||
= image_tag avatar_icon(user, 40), class: "avatar s40", alt: ''
|
||||
%strong
|
||||
= link_to user.name, user_path(user)
|
||||
%span.cgray= user.username
|
||||
%span.cgray= user.to_reference
|
||||
|
||||
- if user == current_user
|
||||
%span.label.label-success It's you
|
||||
%span.label.label-success.prepend-left-5 It's you
|
||||
|
||||
- if user.blocked?
|
||||
%label.label.label-danger
|
||||
%strong Blocked
|
||||
|
||||
.cgray
|
||||
- if source.instance_of?(Group) && !@group
|
||||
= link_to source, class: "member-group-link prepend-left-5" do
|
||||
= "· #{source.name}"
|
||||
|
||||
.hidden-xs.cgray
|
||||
- if member.request?
|
||||
Requested
|
||||
= time_ago_with_tooltip(member.requested_at)
|
||||
|
@ -73,20 +43,44 @@
|
|||
by
|
||||
= link_to member.created_by.name, user_path(member.created_by)
|
||||
= time_ago_with_tooltip(member.created_at)
|
||||
|
||||
- if show_roles
|
||||
.edit-member.hide.js-toggle-content
|
||||
%br
|
||||
= form_for member, remote: true, html: { class: 'form-horizontal' } do |f|
|
||||
.form-group
|
||||
= label_tag "member_access_level_#{member.id}", 'Project access', class: 'control-label'
|
||||
.col-sm-10
|
||||
= f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control', id: "member_access_level_#{member.id}"
|
||||
.form-group
|
||||
= label_tag "member_expires_at_#{member.id}", 'Access expiration date', class: 'control-label'
|
||||
.col-sm-10
|
||||
.clearable-input
|
||||
= f.text_field :expires_at, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date', id: "member_expires_at_#{member.id}"
|
||||
.controls.member-controls
|
||||
- if show_controls
|
||||
- if user != current_user
|
||||
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
|
||||
= f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member
|
||||
.prepend-left-5.clearable-input.member-form-control
|
||||
= f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member
|
||||
%i.clear-icon.js-clear-input
|
||||
.prepend-top-10
|
||||
= f.submit 'Save', class: 'btn btn-save btn-sm'
|
||||
- else
|
||||
%span.member-access-text= member.human_access
|
||||
|
||||
- if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
|
||||
= link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
|
||||
method: :post,
|
||||
class: 'btn btn-default prepend-left-10'
|
||||
|
||||
- elsif member.request? && can_admin_member
|
||||
= link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]),
|
||||
method: :post,
|
||||
class: 'btn btn-success prepend-left-10',
|
||||
title: 'Grant access'
|
||||
|
||||
- if can?(current_user, action_member_permission(:destroy, member), member)
|
||||
- if current_user == user
|
||||
= link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]),
|
||||
method: :delete,
|
||||
data: { confirm: leave_confirmation_message(member.source) },
|
||||
class: 'btn btn-remove prepend-left-10'
|
||||
- else
|
||||
= link_to member,
|
||||
remote: true,
|
||||
method: :delete,
|
||||
data: { confirm: remove_member_message(member) },
|
||||
class: 'btn btn-remove prepend-left-10',
|
||||
title: remove_member_title(member) do
|
||||
%span.visible-xs-block
|
||||
Delete
|
||||
= icon('trash', class: 'hidden-xs')
|
||||
- else
|
||||
%span.member-access-text= member.human_access
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
- if requesters.any?
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
Users requesting access to
|
||||
%strong= membership_source.name
|
||||
access requests
|
||||
%span.badge= requesters.size
|
||||
%ul.content-list
|
||||
= render partial: 'shared/members/member', collection: requesters, as: :member
|
||||
|
|
|
@ -408,7 +408,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
|
|||
end
|
||||
end
|
||||
|
||||
resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ }
|
||||
resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
|
||||
|
||||
resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
|
||||
member do
|
||||
|
|
|
@ -105,7 +105,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
|
|||
select "Developer", from: "access_level"
|
||||
end
|
||||
|
||||
click_button "Add users to group"
|
||||
click_button "Add to group"
|
||||
end
|
||||
|
||||
step 'I should see current user as "Developer"' do
|
||||
|
|
|
@ -70,7 +70,7 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
|
|||
select "Developer", from: "access_level"
|
||||
end
|
||||
|
||||
click_button "Add users to project"
|
||||
click_button "Add to project"
|
||||
end
|
||||
|
||||
step 'I should see current user as "Developer"' do
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
class Spinach::Features::GroupMembers < Spinach::FeatureSteps
|
||||
include WaitForAjax
|
||||
include SharedAuthentication
|
||||
include SharedPaths
|
||||
include SharedGroup
|
||||
|
@ -13,7 +14,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
|
|||
select "Reporter", from: "access_level"
|
||||
end
|
||||
|
||||
click_button "Add users to group"
|
||||
click_button "Add to group"
|
||||
end
|
||||
|
||||
step 'I select "Mike" as "Master"' do
|
||||
|
@ -24,7 +25,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
|
|||
select "Master", from: "access_level"
|
||||
end
|
||||
|
||||
click_button "Add users to group"
|
||||
click_button "Add to group"
|
||||
end
|
||||
|
||||
step 'I should see "Mike" in team list as "Reporter"' do
|
||||
|
@ -47,7 +48,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
|
|||
select "Reporter", from: "access_level"
|
||||
end
|
||||
|
||||
click_button "Add users to group"
|
||||
click_button "Add to group"
|
||||
end
|
||||
|
||||
step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
|
||||
|
@ -66,7 +67,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
|
|||
select "Reporter", from: "access_level"
|
||||
end
|
||||
|
||||
click_button "Add users to group"
|
||||
click_button "Add to group"
|
||||
end
|
||||
|
||||
step 'I should see user "John Doe" in team list' do
|
||||
|
@ -108,7 +109,7 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
|
|||
step 'I search for \'Mary\' member' do
|
||||
page.within '.member-search-form' do
|
||||
fill_in 'search', with: 'Mary'
|
||||
click_button 'Search'
|
||||
find('.member-search-btn').click
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -116,9 +117,8 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
|
|||
member = mary_jane_member
|
||||
|
||||
page.within "#group_member_#{member.id}" do
|
||||
click_button 'Edit'
|
||||
select 'Developer', from: "member_access_level_#{member.id}"
|
||||
click_on 'Save'
|
||||
wait_for_ajax
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
|
|||
select2(user.id, from: "#user_ids", multiple: true)
|
||||
select "Reporter", from: "access_level"
|
||||
end
|
||||
click_button "Add users to project"
|
||||
click_button "Add to project"
|
||||
end
|
||||
|
||||
step 'I should see "Mike" in team list as "Reporter"' do
|
||||
|
@ -36,10 +36,10 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
|
|||
|
||||
step 'I select "sjobs@apple.com" as "Reporter"' do
|
||||
page.within ".users-project-form" do
|
||||
select2("sjobs@apple.com", from: "#user_ids", multiple: true)
|
||||
find('#user_ids', visible: false).set('sjobs@apple.com')
|
||||
select "Reporter", from: "access_level"
|
||||
end
|
||||
click_button "Add users to project"
|
||||
click_button "Add to project"
|
||||
end
|
||||
|
||||
step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do
|
||||
|
@ -65,9 +65,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
|
|||
user = User.find_by(name: 'Dmitriy')
|
||||
project_member = project.project_members.find_by(user_id: user.id)
|
||||
page.within "#project_member_#{project_member.id}" do
|
||||
click_button 'Edit'
|
||||
select "Reporter", from: "member_access_level_#{project_member.id}"
|
||||
click_button "Save"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -112,7 +110,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I click link "Import team from another project"' do
|
||||
click_link "Import members from another project"
|
||||
click_link "Import"
|
||||
end
|
||||
|
||||
When 'I submit "Website" project for import team' do
|
||||
|
@ -144,8 +142,9 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I should see "Opensource" group user listing' do
|
||||
expect(page).to have_content("Shared with OpenSource group, members with Master role (2)")
|
||||
expect(page).to have_content(@os_user1.name)
|
||||
expect(page).to have_content(@os_user2.name)
|
||||
page.within '.project-members-groups' do
|
||||
expect(page).to have_content('OpenSource')
|
||||
expect(find('select').value).to eq('40')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,7 +41,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do
|
|||
|
||||
def expect_visible_access_request(group, user)
|
||||
expect(group.requesters.exists?(user_id: user)).to be_truthy
|
||||
expect(page).to have_content "#{group.name} access requests 1"
|
||||
expect(page).to have_content "Users requesting access to #{group.name} 1"
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Projects > Members > Anonymous user sees members', feature: true, js: true do
|
||||
include WaitForAjax
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group, :public) }
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
|
||||
background do
|
||||
project.team << [user, :master]
|
||||
@group_link = create(:project_group_link, project: project, group: group)
|
||||
|
||||
login_as(user)
|
||||
visit namespace_project_project_members_path(project.namespace, project)
|
||||
end
|
||||
|
||||
it 'updates group access level' do
|
||||
select 'Guest', from: "member_access_level_#{group.id}"
|
||||
wait_for_ajax
|
||||
|
||||
visit namespace_project_project_members_path(project.namespace, project)
|
||||
|
||||
expect(page).to have_select("member_access_level_#{group.id}", selected: 'Guest')
|
||||
end
|
||||
|
||||
it 'updates expiry date' do
|
||||
tomorrow = Date.today + 3
|
||||
|
||||
fill_in "member_expires_at_#{group.id}", with: tomorrow.strftime("%F")
|
||||
wait_for_ajax
|
||||
|
||||
page.within(find('li.group_member')) do
|
||||
expect(page).to have_content('Expires in')
|
||||
end
|
||||
end
|
||||
|
||||
it 'deletes group link' do
|
||||
page.within(first('.group_member')) do
|
||||
find('.btn-remove').click
|
||||
end
|
||||
wait_for_ajax
|
||||
|
||||
expect(page).not_to have_selector('.group_member')
|
||||
end
|
||||
|
||||
context 'search' do
|
||||
it 'finds no results' do
|
||||
page.within '.member-search-form' do
|
||||
fill_in 'search', with: 'testing 123'
|
||||
find('.member-search-btn').click
|
||||
end
|
||||
|
||||
expect(page).not_to have_selector('.group_member')
|
||||
end
|
||||
|
||||
it 'finds results' do
|
||||
page.within '.member-search-form' do
|
||||
fill_in 'search', with: group.name
|
||||
find('.member-search-btn').click
|
||||
end
|
||||
|
||||
expect(page).to have_selector('.group_member', count: 1)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Projects > Members > Master adds member with expiration date', feature: true, js: true do
|
||||
include WaitForAjax
|
||||
include Select2Helper
|
||||
include ActiveSupport::Testing::TimeHelpers
|
||||
|
||||
|
@ -20,7 +21,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
|
|||
page.within '.users-project-form' do
|
||||
select2(new_member.id, from: '#user_ids', multiple: true)
|
||||
fill_in 'expires_at', with: '2016-08-10'
|
||||
click_on 'Add users to project'
|
||||
click_on 'Add to project'
|
||||
end
|
||||
|
||||
page.within '.project_member:first-child' do
|
||||
|
@ -35,9 +36,8 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
|
|||
visit namespace_project_project_members_path(project.namespace, project)
|
||||
|
||||
page.within '.project_member:first-child' do
|
||||
click_on 'Edit'
|
||||
fill_in 'Access expiration date', with: '2016-08-09'
|
||||
click_on 'Save'
|
||||
find('.js-access-expiration-date').set '2016-08-09'
|
||||
wait_for_ajax
|
||||
expect(page).to have_content('Expires in 3 days')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,7 +41,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do
|
|||
|
||||
def expect_visible_access_request(project, user)
|
||||
expect(project.requesters.exists?(user_id: user)).to be_truthy
|
||||
expect(page).to have_content "#{project.name} access requests 1"
|
||||
expect(page).to have_content "Users requesting access to #{project.name} 1"
|
||||
expect(page).to have_content user.name
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue