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:
Fatih Acet 2016-10-14 12:25:23 +00:00
commit 66855f6262
35 changed files with 458 additions and 246 deletions

View File

@ -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':

View File

@ -1,13 +0,0 @@
(function() {
this.GroupMembers = (function() {
function GroupMembers() {
$('li.group_member').bind('ajax:success', function() {
return $(this).fadeOut();
});
}
return GroupMembers;
})();
}).call(this);

View File

@ -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);
});

View File

@ -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);

View File

@ -1,10 +0,0 @@
(function() {
this.ProjectMembers = (function() {
function ProjectMembers() {
$('li.project_member').bind('ajax:success', function() {
return $(this).fadeOut();
});
}
return ProjectMembers;
})();
}).call(this);

View File

@ -125,7 +125,3 @@ label {
border-right: 0;
}
}
.help-block {
margin-bottom: 0;
}

View File

@ -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;

View File

@ -13,6 +13,11 @@
.dropdown-menu-toggle {
line-height: 20px;
}
.badge {
margin-top: -2px;
margin-left: 5px;
}
}
.panel-body {

View File

@ -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;

View File

@ -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%);
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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());
});

View File

@ -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'));

View File

@ -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'));

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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());
});

View File

@ -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"

View File

@ -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'));

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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