Merge pull request #2746 from gitlabhq/features/teams

New feature: Teams
This commit is contained in:
Dmitriy Zaporozhets 2013-01-25 00:46:20 -08:00
commit aa1f1eb680
115 changed files with 2714 additions and 174 deletions

View file

@ -7,6 +7,7 @@
@extend .right;
.groups_box,
.teams_box,
.projects_box {
> .title {
padding: 2px 15px;

View file

@ -1,7 +1,7 @@
# Provides a base class for Admin controllers to subclass
#
# Automatically sets the layout and ensures an administrator is logged in
class AdminController < ApplicationController
class Admin::ApplicationController < ApplicationController
layout 'admin'
before_filter :authenticate_admin!

View file

@ -1,4 +1,4 @@
class Admin::DashboardController < AdminController
class Admin::DashboardController < Admin::ApplicationController
def index
@projects = Project.order("created_at DESC").limit(10)
@users = User.order("created_at DESC").limit(10)

View file

@ -1,4 +1,4 @@
class Admin::GroupsController < AdminController
class Admin::GroupsController < Admin::ApplicationController
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update]
def index

View file

@ -1,4 +1,4 @@
class Admin::HooksController < AdminController
class Admin::HooksController < Admin::ApplicationController
def index
@hooks = SystemHook.all
@hook = SystemHook.new

View file

@ -1,2 +1,2 @@
class Admin::LogsController < AdminController
class Admin::LogsController < Admin::ApplicationController
end

View file

@ -0,0 +1,11 @@
# Provides a base class for Admin controllers to subclass
#
# Automatically sets the layout and ensures an administrator is logged in
class Admin::Projects::ApplicationController < Admin::ApplicationController
protected
def project
@project ||= Project.find_by_path(params[:project_id])
end
end

View file

@ -0,0 +1,32 @@
class Admin::Projects::MembersController < Admin::Projects::ApplicationController
def edit
@member = team_member
@project = project
@team_member_relation = team_member_relation
end
def update
if team_member_relation.update_attributes(params[:team_member])
redirect_to [:admin, project], notice: 'Project Access was successfully updated.'
else
render action: "edit"
end
end
def destroy
team_member_relation.destroy
redirect_to :back
end
private
def team_member
@member ||= project.users.find(params[:id])
end
def team_member_relation
team_member.users_projects.find_by_project_id(project)
end
end

View file

@ -1,4 +1,4 @@
class Admin::ProjectsController < AdminController
class Admin::ProjectsController < Admin::ApplicationController
before_filter :project, only: [:edit, :show, :update, :destroy, :team_update]
def index
@ -29,7 +29,9 @@ class Admin::ProjectsController < AdminController
end
def update
status = Projects::UpdateContext.new(project, current_user, params).execute(:admin)
project.creator = current_user unless project.creator
status = ::Projects::UpdateContext.new(project, current_user, params).execute(:admin)
if status
redirect_to [:admin, @project], notice: 'Project was successfully updated.'

View file

@ -1,4 +1,4 @@
class Admin::ResqueController < AdminController
class Admin::ResqueController < Admin::ApplicationController
def show
end
end

View file

@ -1,22 +0,0 @@
class Admin::TeamMembersController < AdminController
def edit
@admin_team_member = UsersProject.find(params[:id])
end
def update
@admin_team_member = UsersProject.find(params[:id])
if @admin_team_member.update_attributes(params[:team_member])
redirect_to [:admin, @admin_team_member.project], notice: 'Project Access was successfully updated.'
else
render action: "edit"
end
end
def destroy
@admin_team_member = UsersProject.find(params[:id])
@admin_team_member.destroy
redirect_to :back
end
end

View file

@ -0,0 +1,11 @@
# Provides a base class for Admin controllers to subclass
#
# Automatically sets the layout and ensures an administrator is logged in
class Admin::Teams::ApplicationController < Admin::ApplicationController
private
def user_team
@team = UserTeam.find_by_path(params[:team_id])
end
end

View file

@ -0,0 +1,41 @@
class Admin::Teams::MembersController < Admin::Teams::ApplicationController
def new
@users = User.potential_team_members(user_team)
@users = UserDecorator.decorate @users
end
def create
unless params[:user_ids].blank?
user_ids = params[:user_ids]
access = params[:default_project_access]
is_admin = params[:group_admin]
user_team.add_members(user_ids, access, is_admin)
end
redirect_to admin_team_path(user_team), notice: 'Members was successfully added into Team of users.'
end
def edit
team_member
end
def update
options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]}
if user_team.update_membership(team_member, options)
redirect_to admin_team_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
else
render :edit
end
end
def destroy
user_team.remove_member(team_member)
redirect_to admin_team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users."
end
protected
def team_member
@member ||= user_team.members.find(params[:id])
end
end

View file

@ -0,0 +1,41 @@
class Admin::Teams::ProjectsController < Admin::Teams::ApplicationController
def new
@projects = Project.scoped
@projects = @projects.without_team(user_team) if user_team.projects.any?
#@projects.reject!(&:empty_repo?)
end
def create
unless params[:project_ids].blank?
project_ids = params[:project_ids]
access = params[:greatest_project_access]
user_team.assign_to_projects(project_ids, access)
end
redirect_to admin_team_path(user_team), notice: 'Team of users was successfully assgned to projects.'
end
def edit
team_project
end
def update
if user_team.update_project_access(team_project, params[:greatest_project_access])
redirect_to admin_team_path(user_team), notice: 'Access was successfully updated.'
else
render :edit
end
end
def destroy
user_team.resign_from_project(team_project)
redirect_to admin_team_path(user_team), notice: 'Team of users was successfully reassigned from project.'
end
protected
def team_project
@project ||= user_team.projects.find_with_namespace(params[:id])
end
end

View file

@ -0,0 +1,59 @@
class Admin::TeamsController < Admin::ApplicationController
def index
@teams = UserTeam.order('name ASC')
@teams = @teams.search(params[:name]) if params[:name].present?
@teams = @teams.page(params[:page]).per(20)
end
def show
user_team
end
def new
@team = UserTeam.new
end
def edit
user_team
end
def create
@team = UserTeam.new(params[:user_team])
@team.path = @team.name.dup.parameterize if @team.name
@team.owner = current_user
if @team.save
redirect_to admin_team_path(@team), notice: 'Team of users was successfully created.'
else
render action: "new"
end
end
def update
user_team_params = params[:user_team].dup
owner_id = user_team_params.delete(:owner_id)
if owner_id
user_team.owner = User.find(owner_id)
end
if user_team.update_attributes(user_team_params)
redirect_to admin_team_path(user_team), notice: 'Team of users was successfully updated.'
else
render action: "edit"
end
end
def destroy
user_team.destroy
redirect_to admin_user_teams_path, notice: 'Team of users was successfully deleted.'
end
protected
def user_team
@team ||= UserTeam.find_by_path(params[:id])
end
end

View file

@ -1,4 +1,4 @@
class Admin::UsersController < AdminController
class Admin::UsersController < Admin::ApplicationController
def index
@admin_users = User.scoped
@admin_users = @admin_users.filter(params[:filter])

View file

@ -94,6 +94,14 @@ class ApplicationController < ActionController::Base
return access_denied! unless can?(current_user, :download_code, project)
end
def authorize_manage_user_team!
return access_denied! unless user_team.present? && can?(current_user, :manage_user_team, user_team)
end
def authorize_admin_user_team!
return access_denied! unless user_team.present? && can?(current_user, :admin_user_team, user_team)
end
def access_denied!
render "errors/access_denied", layout: "errors", status: 404
end
@ -135,4 +143,5 @@ class ApplicationController < ActionController::Base
def dev_tools
Rack::MiniProfiler.authorize_request
end
end

View file

@ -18,6 +18,8 @@ class DashboardController < ApplicationController
@projects
end
@teams = (UserTeam.with_member(current_user) + UserTeam.created_by(current_user)).uniq
@projects = @projects.page(params[:page]).per(30)
@events = Event.in_projects(current_user.authorized_projects.pluck(:id))

View file

@ -0,0 +1,11 @@
class Projects::ApplicationController < ApplicationController
before_filter :authorize_admin_team_member!
protected
def user_team
@team ||= UserTeam.find_by_path(params[:id])
end
end

View file

@ -0,0 +1,27 @@
class Projects::TeamsController < Projects::ApplicationController
def available
@teams = current_user.is_admin? ? UserTeam.scoped : current_user.user_teams
@teams = @teams.without_project(project)
unless @teams.any?
redirect_to project_team_index_path(project), notice: "No avaliable teams for assigment."
end
end
def assign
unless params[:team_id].blank?
team = UserTeam.find(params[:team_id])
access = params[:greatest_project_access]
team.assign_to_project(project, access)
end
redirect_to project_team_index_path(project)
end
def resign
team = project.user_teams.find_by_path(params[:id])
team.resign_from_project(project)
redirect_to project_team_index_path(project)
end
end

View file

@ -19,7 +19,7 @@ class ProjectsController < ProjectResourceController
end
def create
@project = Projects::CreateContext.new(current_user, params[:project]).execute
@project = ::Projects::CreateContext.new(current_user, params[:project]).execute
respond_to do |format|
flash[:notice] = 'Project was successfully created.' if @project.saved?
@ -35,7 +35,7 @@ class ProjectsController < ProjectResourceController
end
def update
status = Projects::UpdateContext.new(project, current_user, params).execute
status = ::Projects::UpdateContext.new(project, current_user, params).execute
respond_to do |format|
if status

View file

@ -4,15 +4,16 @@ class TeamMembersController < ProjectResourceController
before_filter :authorize_admin_project!, except: [:index, :show]
def index
@teams = UserTeam.scoped
end
def show
@team_member = project.users_projects.find(params[:id])
@events = @team_member.user.recent_events.where(:project_id => @project.id).limit(7)
@user_project_relation = project.users_projects.find_by_user_id(member)
@events = member.recent_events.in_projects(project).limit(7)
end
def new
@team_member = project.users_projects.new
@user_project_relation = project.users_projects.new
end
def create
@ -28,18 +29,18 @@ class TeamMembersController < ProjectResourceController
end
def update
@team_member = project.users_projects.find(params[:id])
@team_member.update_attributes(params[:team_member])
@user_project_relation = project.users_projects.find_by_user_id(member)
@user_project_relation.update_attributes(params[:team_member])
unless @team_member.valid?
unless @user_project_relation.valid?
flash[:alert] = "User should have at least one role"
end
redirect_to project_team_index_path(@project)
end
def destroy
@team_member = project.users_projects.find(params[:id])
@team_member.destroy
@user_project_relation = project.users_projects.find_by_user_id(params[:id])
@user_project_relation.destroy
respond_to do |format|
format.html { redirect_to project_team_index_path(@project) }
@ -54,4 +55,10 @@ class TeamMembersController < ProjectResourceController
redirect_to project_team_members_path(project), notice: notice
end
protected
def member
@member ||= User.find(params[:id])
end
end

View file

@ -0,0 +1,13 @@
class Teams::ApplicationController < ApplicationController
layout 'user_team'
before_filter :authorize_manage_user_team!
protected
def user_team
@team ||= UserTeam.find_by_path(params[:team_id])
end
end

View file

@ -0,0 +1,49 @@
class Teams::MembersController < Teams::ApplicationController
skip_before_filter :authorize_manage_user_team!, only: [:index]
def index
@members = user_team.members
end
def new
@users = User.potential_team_members(user_team)
@users = UserDecorator.decorate @users
end
def create
unless params[:user_ids].blank?
user_ids = params[:user_ids]
access = params[:default_project_access]
is_admin = params[:group_admin]
user_team.add_members(user_ids, access, is_admin)
end
redirect_to team_members_path(user_team), notice: 'Members was successfully added into Team of users.'
end
def edit
team_member
end
def update
options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]}
if user_team.update_membership(team_member, options)
redirect_to team_members_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users."
else
render :edit
end
end
def destroy
user_team.remove_member(team_member)
redirect_to team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users."
end
protected
def team_member
@member ||= user_team.members.find(params[:id])
end
end

View file

@ -0,0 +1,57 @@
class Teams::ProjectsController < Teams::ApplicationController
skip_before_filter :authorize_manage_user_team!, only: [:index]
def index
@projects = user_team.projects
@avaliable_projects = current_user.admin? ? Project.without_team(user_team) : current_user.owned_projects.without_team(user_team)
end
def new
user_team
@avaliable_projects = current_user.owned_projects.scoped
@avaliable_projects = @avaliable_projects.without_team(user_team) if user_team.projects.any?
redirect_to team_projects_path(user_team), notice: "No avalible projects." unless @avaliable_projects.any?
end
def create
redirect_to :back if params[:project_ids].blank?
project_ids = params[:project_ids]
access = params[:greatest_project_access]
# Reject non-allowed projects
allowed_project_ids = current_user.owned_projects.map(&:id)
project_ids.select! { |id| allowed_project_ids.include?(id) }
# Assign projects to team
user_team.assign_to_projects(project_ids, access)
redirect_to team_projects_path(user_team), notice: 'Team of users was successfully assigned to projects.'
end
def edit
team_project
end
def update
if user_team.update_project_access(team_project, params[:greatest_project_access])
redirect_to team_projects_path(user_team), notice: 'Access was successfully updated.'
else
render :edit
end
end
def destroy
user_team.resign_from_project(team_project)
redirect_to team_projects_path(user_team), notice: 'Team of users was successfully reassigned from project.'
end
private
def team_project
@project ||= user_team.projects.find_with_namespace(params[:id])
end
end

View file

@ -0,0 +1,92 @@
class TeamsController < ApplicationController
# Authorize
before_filter :authorize_manage_user_team!
before_filter :authorize_admin_user_team!
# Skip access control on public section
skip_before_filter :authorize_manage_user_team!, only: [:index, :show, :new, :destroy, :create, :search, :issues, :merge_requests]
skip_before_filter :authorize_admin_user_team!, only: [:index, :show, :new, :create, :search, :issues, :merge_requests]
layout 'user_team', only: [:show, :edit, :update, :destroy, :issues, :merge_requests, :search]
def index
@teams = current_user.user_teams.order('name ASC')
end
def show
user_team
projects
@events = Event.in_projects(user_team.project_ids).limit(20).offset(params[:offset] || 0)
end
def edit
user_team
end
def update
if user_team.update_attributes(params[:user_team])
redirect_to team_path(user_team)
else
render action: :edit
end
end
def destroy
user_team.destroy
redirect_to teams_path
end
def new
@team = UserTeam.new
end
def create
@team = UserTeam.new(params[:user_team])
@team.owner = current_user unless params[:owner]
@team.path = @team.name.dup.parameterize if @team.name
if @team.save
redirect_to team_path(@team)
else
render action: :new
end
end
# Get authored or assigned open merge requests
def merge_requests
projects
@merge_requests = MergeRequest.of_user_team(user_team)
@merge_requests = FilterContext.new(@merge_requests, params).execute
@merge_requests = @merge_requests.recent.page(params[:page]).per(20)
end
# Get only assigned issues
def issues
projects
@issues = Issue.of_user_team(user_team)
@issues = FilterContext.new(@issues, params).execute
@issues = @issues.recent.page(params[:page]).per(20)
@issues = @issues.includes(:author, :project)
end
def search
result = SearchContext.new(user_team.project_ids, params).execute
@projects = result[:projects]
@merge_requests = result[:merge_requests]
@issues = result[:issues]
@wiki_pages = result[:wiki_pages]
@teams = result[:teams]
end
protected
def projects
@projects ||= user_team.projects.sorted_by_activity
end
def user_team
@team ||= UserTeam.find_by_path(params[:id])
end
end

View file

@ -8,4 +8,8 @@ class UserDecorator < ApplicationDecorator
def tm_of(project)
project.team_member_by_id(self.id)
end
def name_with_email
"#{name} (#{email})"
end
end

View file

@ -0,0 +1,5 @@
module Admin::Teams::MembersHelper
def member_since(team, member)
team.user_team_user_relationships.find_by_user_id(member).created_at
end
end

View file

@ -0,0 +1,5 @@
module Admin::Teams::ProjectsHelper
def assigned_since(team, project)
team.user_team_project_relationships.find_by_project_id(project).created_at
end
end

View file

@ -3,8 +3,12 @@ module ProjectsHelper
@project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access)
end
def remove_from_team_message(project, member)
"You are going to remove #{member.user_name} from #{project.name}. Are you sure?"
def grouper_project_teams(project)
@project.user_team_project_relationships.sort_by(&:greatest_access).reverse.group_by(&:greatest_access)
end
def remove_from_project_team_message(project, user)
"You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
end
def link_to_project project

View file

@ -0,0 +1,26 @@
module UserTeamsHelper
def team_filter_path(entity, options={})
exist_opts = {
status: params[:status],
project_id: params[:project_id],
}
options = exist_opts.merge(options)
case entity
when 'issue' then
issues_team_path(@team, options)
when 'merge_request'
merge_requests_team_path(@team, options)
end
end
def grouped_user_team_members(team)
team.user_team_user_relationships.sort_by(&:permission).reverse.group_by(&:permission)
end
def remove_from_user_team_message(team, member)
"You are going to remove #{member.name} from #{team.name}. Are you sure?"
end
end

View file

@ -8,6 +8,7 @@ class Ability
when "Snippet" then snippet_abilities(object, subject)
when "MergeRequest" then merge_request_abilities(object, subject)
when "Group", "Namespace" then group_abilities(object, subject)
when "UserTeam" then user_team_abilities(object, subject)
else []
end
end
@ -110,6 +111,22 @@ class Ability
rules.flatten
end
def user_team_abilities user, team
rules = []
# Only group owner and administrators can manage group
if team.owner == user || team.admin?(user) || user.admin?
rules << [ :manage_user_team ]
end
if team.owner == user || user.admin?
rules << [ :admin_user_team ]
end
rules.flatten
end
[:issue, :note, :snippet, :merge_request].each do |name|
define_method "#{name}_abilities" do |user, subject|
if subject.author == user

View file

@ -22,6 +22,7 @@ module Issuable
scope :opened, where(closed: false)
scope :closed, where(closed: true)
scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
scope :assigned, ->(u) { where(assignee_id: u.id)}
scope :recent, order("created_at DESC")

View file

@ -33,28 +33,31 @@ class Project < ActiveRecord::Base
attr_accessor :error_code
# Relations
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
belongs_to :creator, foreign_key: "creator_id", class_name: "User"
belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'"
belongs_to :namespace
belongs_to :creator,
class_name: "User",
foreign_key: "creator_id"
has_many :users, through: :users_projects
has_many :events, dependent: :destroy
has_many :merge_requests, dependent: :destroy
has_many :issues, dependent: :destroy, order: "closed, created_at DESC"
has_many :milestones, dependent: :destroy
has_many :users_projects, dependent: :destroy
has_many :notes, dependent: :destroy
has_many :snippets, dependent: :destroy
has_many :deploy_keys, dependent: :destroy, foreign_key: "project_id", class_name: "Key"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :wikis, dependent: :destroy
has_many :protected_branches, dependent: :destroy
has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id'
has_one :gitlab_ci_service, dependent: :destroy
has_many :events, dependent: :destroy
has_many :merge_requests, dependent: :destroy
has_many :issues, dependent: :destroy, order: "closed, created_at DESC"
has_many :milestones, dependent: :destroy
has_many :users_projects, dependent: :destroy
has_many :notes, dependent: :destroy
has_many :snippets, dependent: :destroy
has_many :deploy_keys, dependent: :destroy, class_name: "Key", foreign_key: "project_id"
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
has_many :wikis, dependent: :destroy
has_many :protected_branches, dependent: :destroy
has_many :user_team_project_relationships, dependent: :destroy
has_many :users, through: :users_projects
has_many :user_teams, through: :user_team_project_relationships
has_many :user_team_user_relationships, through: :user_teams
has_many :user_teams_members, through: :user_team_user_relationships
delegate :name, to: :owner, allow_nil: true, prefix: true
# Validations
@ -77,6 +80,8 @@ class Project < ActiveRecord::Base
# Scopes
scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) }
scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) }
scope :without_team, ->(team) { where("id NOT IN (:ids)", ids: team.projects.map(&:id)) }
scope :in_team, ->(team) { where("id IN (:ids)", ids: team.projects.map(&:id)) }
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
@ -122,7 +127,7 @@ class Project < ActiveRecord::Base
end
def team
@team ||= Team.new(self)
@team ||= ProjectTeam.new(self)
end
def repository
@ -489,6 +494,11 @@ class Project < ActiveRecord::Base
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end
def project_access_human(member)
project_user_relation = self.users_projects.find_by_user_id(member.id)
self.class.access_options.key(project_user_relation.project_access)
end
# Check if current branch name is marked as protected in the system
def protected_branch? branch_name
protected_branches.map(&:name).include?(branch_name)

View file

@ -1,4 +1,4 @@
class Team
class ProjectTeam
attr_accessor :project
def initialize(project)

View file

@ -45,18 +45,27 @@ class User < ActiveRecord::Base
attr_accessor :force_random_password
# Namespace for personal projects
has_one :namespace, class_name: "Namespace", foreign_key: :owner_id, conditions: 'type IS NULL', dependent: :destroy
has_many :groups, class_name: "Group", foreign_key: :owner_id
has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL'
has_many :keys, dependent: :destroy
has_many :users_projects, dependent: :destroy
has_many :issues, foreign_key: :author_id, dependent: :destroy
has_many :notes, foreign_key: :author_id, dependent: :destroy
has_many :merge_requests, foreign_key: :author_id, dependent: :destroy
has_many :events, class_name: "Event", foreign_key: :author_id, dependent: :destroy
has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
has_many :assigned_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy
has_many :assigned_merge_requests, class_name: "MergeRequest", foreign_key: :assignee_id, dependent: :destroy
has_many :keys, dependent: :destroy
has_many :users_projects, dependent: :destroy
has_many :issues, dependent: :destroy, foreign_key: :author_id
has_many :notes, dependent: :destroy, foreign_key: :author_id
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id
has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event"
has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
has_many :groups, class_name: "Group", foreign_key: :owner_id
has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC"
has_many :projects, through: :users_projects
has_many :user_team_user_relationships, dependent: :destroy
has_many :user_teams, through: :user_team_user_relationships
has_many :user_team_project_relationships, through: :user_teams
has_many :team_projects, through: :user_team_project_relationships
validates :name, presence: true
validates :bio, length: { within: 0..255 }
@ -80,6 +89,9 @@ class User < ActiveRecord::Base
scope :blocked, where(blocked: true)
scope :active, where(blocked: false)
scope :alphabetically, order('name ASC')
scope :in_team, ->(team){ where(id: team.member_ids) }
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :potential_team_members, ->(team) { team.members.any? ? active : active.not_in_team(team) }
#
# Class methods

97
app/models/user_team.rb Normal file
View file

@ -0,0 +1,97 @@
class UserTeam < ActiveRecord::Base
attr_accessible :name, :owner_id, :path
belongs_to :owner, class_name: User
has_many :user_team_project_relationships, dependent: :destroy
has_many :user_team_user_relationships, dependent: :destroy
has_many :projects, through: :user_team_project_relationships
has_many :members, through: :user_team_user_relationships, source: :user
validates :name, presence: true, uniqueness: true
validates :owner, presence: true
validates :path, uniqueness: true, presence: true, length: { within: 1..255 },
format: { with: Gitlab::Regex.path_regex,
message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
scope :with_member, ->(user){ joins(:user_team_user_relationships).where(user_team_user_relationships: {user_id: user.id}) }
scope :with_project, ->(project){ joins(:user_team_project_relationships).where(user_team_project_relationships: {project_id: project})}
scope :without_project, ->(project){ where("user_teams.id NOT IN (:ids)", ids: (a = with_project(project); a.blank? ? 0 : a))}
scope :created_by, ->(user){ where(owner_id: user) }
class << self
def search query
where("name LIKE :query OR path LIKE :query", query: "%#{query}%")
end
def global_id
'GLN'
end
def access_roles
UsersProject.access_roles
end
end
def to_param
path
end
def assign_to_projects(projects, access)
projects.each do |project|
assign_to_project(project, access)
end
end
def assign_to_project(project, access)
Gitlab::UserTeamManager.assign(self, project, access)
end
def resign_from_project(project)
Gitlab::UserTeamManager.resign(self, project)
end
def add_members(users, access, group_admin)
users.each do |user|
add_member(user, access, group_admin)
end
end
def add_member(user, access, group_admin)
Gitlab::UserTeamManager.add_member_into_team(self, user, access, group_admin)
end
def remove_member(user)
Gitlab::UserTeamManager.remove_member_from_team(self, user)
end
def update_membership(user, options)
Gitlab::UserTeamManager.update_team_user_membership(self, user, options)
end
def update_project_access(project, permission)
Gitlab::UserTeamManager.update_project_greates_access(self, project, permission)
end
def max_project_access(project)
user_team_project_relationships.find_by_project_id(project).greatest_access
end
def human_max_project_access(project)
self.class.access_roles.invert[max_project_access(project)]
end
def default_projects_access(member)
user_team_user_relationships.find_by_user_id(member).permission
end
def human_default_projects_access(member)
self.class.access_roles.invert[default_projects_access(member)]
end
def admin?(member)
user_team_user_relationships.with_user(member).first.group_admin?
end
end

View file

@ -0,0 +1,28 @@
class UserTeamProjectRelationship < ActiveRecord::Base
attr_accessible :greatest_access, :project_id, :user_team_id
belongs_to :user_team
belongs_to :project
validates :project, presence: true
validates :user_team, presence: true
validate :check_greatest_access
scope :with_project, ->(project){ where(project_id: project.id) }
def team_name
user_team.name
end
private
def check_greatest_access
errors.add(:base, :incorrect_access_code) unless correct_access?
end
def correct_access?
return false if greatest_access.blank?
return true if UsersProject.access_roles.has_value?(greatest_access)
false
end
end

View file

@ -0,0 +1,19 @@
class UserTeamUserRelationship < ActiveRecord::Base
attr_accessible :group_admin, :permission, :user_id, :user_team_id
belongs_to :user_team
belongs_to :user
validates :user_team, presence: true
validates :user, presence: true
scope :with_user, ->(user) { where(user_id: user.id) }
def user_name
user.name
end
def access_human
UsersProject.access_roles.invert[permission]
end
end

View file

@ -39,7 +39,10 @@ class UsersProject < ActiveRecord::Base
scope :reporters, where(project_access: REPORTER)
scope :developers, where(project_access: DEVELOPER)
scope :masters, where(project_access: MASTER)
scope :in_project, ->(project) { where(project_id: project.id) }
scope :in_projects, ->(projects) { where(project_id: project_ids) }
scope :with_user, ->(user) { where(user_id: user.id) }
class << self

View file

@ -0,0 +1,16 @@
= form_for @team_member_relation, as: :team_member, url: admin_project_member_path(@project, @member) do |f|
-if @team_member_relation.errors.any?
.alert-message.block-message.error
%ul
- @team_member_relation.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Project Access:
.input
= f.select :project_access, options_for_select(Project.access_options, @team_member_relation.project_access), {}, class: "project-access-select chosen span3"
%br
.actions
= f.submit 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"

View file

@ -0,0 +1,8 @@
%p.slead
Edit access for
= link_to @member.name, admin_user_path(@member)
in
= link_to @project.name_with_namespace, admin_project_path(@project)
%hr
= render 'form'

View file

@ -114,9 +114,9 @@
%h5
Team
%small
(#{@project.users_projects.count})
(#{@project.users.count})
%br
%table.zebra-striped
%table.zebra-striped.team_members
%thead
%tr
%th Name
@ -124,13 +124,13 @@
%th Repository Access
%th
- @project.users_projects.each do |tm|
- @project.users.each do |tm|
%tr
%td
= link_to tm.user_name, admin_user_path(tm.user)
%td= tm.project_access_human
%td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small"
%td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small"
= link_to tm.name, admin_user_path(tm)
%td= @project.project_access_human(tm)
%td= link_to 'Edit Access', edit_admin_project_member_path(@project, tm), class: "btn small"
%td= link_to 'Remove from team', admin_project_member_path(@project, tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small"
%br
%h5 Add new team member

View file

@ -1,16 +0,0 @@
= form_for @admin_team_member, as: :team_member, url: admin_team_member_path(@admin_team_member) do |f|
-if @admin_team_member.errors.any?
.alert-message.block-message.error
%ul
- @admin_team_member.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Project Access:
.input
= f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3"
%br
.actions
= f.submit 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"

View file

@ -1,8 +0,0 @@
%p.slead
Edit access for
= link_to @admin_team_member.user_name, admin_user_path(@admin_team_member)
in
= link_to @admin_team_member.project.name_with_namespace, admin_project_path(@admin_team_member)
%hr
= render 'form'

View file

@ -0,0 +1,23 @@
%h3.page_title Rename Team
%hr
= form_for @team, url: admin_team_path(@team), method: :put do |f|
- if @team.errors.any?
.alert-message.block-message.error
%span= @team.errors.full_messages.first
.clearfix.team_name_holder
= f.label :name do
Team name is
.input
= f.text_field :name, placeholder: "Example Team", class: "xxlarge"
.clearfix.team_name_holder
= f.label :path do
%span.cred Team path is
.input
= f.text_field :path, placeholder: "example-team", class: "xxlarge danger"
%ul.cred
%li It will change web url for access team and team projects.
.form-actions
= f.submit 'Rename team', class: "btn danger"
= link_to 'Cancel', admin_teams_path, class: "btn cancel-btn"

View file

@ -0,0 +1,38 @@
%h3.page_title
Teams
%small
simple Teams description
= link_to 'New Team', new_admin_team_path, class: "btn small right"
%br
= form_tag admin_teams_path, method: :get, class: 'form-inline' do
= text_field_tag :name, params[:name], class: "xlarge"
= submit_tag "Search", class: "btn submit primary"
%table
%thead
%tr
%th
Name
%i.icon-sort-down
%th Path
%th Projects
%th Members
%th Owner
%th.cred Danger Zone!
- @teams.each do |team|
%tr
%td
%strong= link_to team.name, admin_team_path(team)
%td= team.path
%td= team.projects.count
%td= team.members.count
%td
= link_to team.owner.name, admin_user_path(team.owner_id)
%td.bgred
= link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn small"
= link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn small danger"
= paginate @teams, theme: "admin"

View file

@ -0,0 +1,20 @@
= form_tag admin_team_member_path(@team, @member), method: :put do
-if @member.errors.any?
.alert-message.block-message.error
%ul
- @member.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Default access for Team projects:
.input
= select_tag :default_project_access, options_for_select(UserTeam.access_roles, @team.default_projects_access(@member)), class: "project-access-select chosen span3"
.clearfix
%label Team admin?
.input
= check_box_tag :group_admin, true, @team.admin?(@member)
%br
.actions
= submit_tag 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"

View file

@ -0,0 +1,16 @@
%h3
Edit access #{@member.name} in #{@team.name} team
%hr
%table.zebra-striped
%tr
%td User:
%td= @member.name
%tr
%td Team:
%td= @team.name
%tr
%td Since:
%td= member_since(@team, @member).stamp("Nov 11, 2010")
= render 'form'

View file

@ -0,0 +1,29 @@
%h3.page_title
Team: #{@team.name}
%fieldset
%legend Members (#{@team.members.count})
= form_tag admin_team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do
%table#members_list
%thead
%tr
%th User name
%th Default project access
%th Team access
%th
- @team.members.each do |member|
%tr.member
%td
= link_to [:admin, member] do
= member.name
%small= "(#{member.email})"
%td= @team.human_default_projects_access(member)
%td= @team.admin?(member) ? "Admin" : "Member"
%td
%tr
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_email), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
%td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
%td
%span= check_box_tag :group_admin
%span Admin?
%td= submit_tag 'Add', class: "btn primary", id: :add_members_to_team

View file

@ -0,0 +1,19 @@
%h3.page_title New Team
%hr
= form_for @team, url: admin_teams_path do |f|
- if @team.errors.any?
.alert-message.block-message.error
%span= @team.errors.full_messages.first
.clearfix
= f.label :name do
Team name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
&nbsp;
= f.submit 'Create team', class: "btn primary"
%hr
.padded
%ul
%li All created teams are public (users can view who enter into team and which project are assigned for this team)
%li People within a team see only projects they have access to
%li You will be able to assign existing projects for team

View file

@ -0,0 +1,16 @@
= form_tag admin_team_project_path(@team, @project), method: :put do
-if @project.errors.any?
.alert-message.block-message.error
%ul
- @project.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Max access for Team members:
.input
= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles, @team.max_project_access(@project)), class: "project-access-select chosen span3"
%br
.actions
= submit_tag 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"

View file

@ -0,0 +1,16 @@
%h3
Edit max access in #{@project.name} for #{@team.name} team
%hr
%table.zebra-striped
%tr
%td Project:
%td= @project.name
%tr
%td Team:
%td= @team.name
%tr
%td Since:
%td= assigned_since(@team, @project).stamp("Nov 11, 2010")
= render 'form'

View file

@ -0,0 +1,23 @@
%h3.page_title
Team: #{@team.name}
%fieldset
%legend Projects (#{@team.projects.count})
= form_tag admin_team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do
%table#projects_list
%thead
%tr
%th Project name
%th Max access
%th
- @team.projects.each do |project|
%tr.project
%td
= link_to project.name_with_namespace, [:admin, project]
%td
%span= @team.human_max_project_access(project)
%td
%tr
%td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
%td= select_tag :greatest_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
%td= submit_tag 'Add', class: "btn primary", id: :assign_projects_to_team

View file

@ -0,0 +1,101 @@
%h3.page_title
Team: #{@team.name}
%br
%table.zebra-striped
%thead
%tr
%th Team
%th
%tr
%td
%b
Name:
%td
= @team.name
&nbsp;
= link_to edit_admin_team_path(@team), class: "btn btn-small right" do
%i.icon-edit
Rename
%tr
%td
%b
Owner:
%td
= @team.owner.name
.right
= link_to "#", class: "btn btn-small change-owner-link" do
%i.icon-edit
Change owner
%tr.change-owner-holder.hide
%td.bgred
%b.cred
New Owner:
%td.bgred
= form_for @team, url: admin_team_path(@team) do |f|
= f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'}
%div
= f.submit 'Change Owner', class: "btn danger"
= link_to "Cancel", "#", class: "btn change-owner-cancel-link"
%fieldset
%legend
Members (#{@team.members.count})
%span= link_to 'Add members', new_admin_team_member_path(@team), class: "btn success small right", id: :add_members_to_team
- if @team.members.any?
%table#members_list
%thead
%tr
%th User name
%th Default project access
%th Team access
%th.cred.span3 Danger Zone!
- @team.members.each do |member|
%tr.member{ class: "user_#{member.id}"}
%td
= link_to [:admin, member] do
= member.name
%small= "(#{member.email})"
%td= @team.human_default_projects_access(member)
%td= @team.admin?(member) ? "Admin" : "Member"
%td.bgred
= link_to 'Edit', edit_admin_team_member_path(@team, member), class: "btn small"
&nbsp;
= link_to 'Remove', admin_team_member_path(@team, member), confirm: 'Remove member from team. Are you sure?', method: :delete, class: "btn danger small", id: "remove_member_#{member.id}"
%fieldset
%legend
Projects (#{@team.projects.count})
%span= link_to 'Add projects', new_admin_team_project_path(@team), class: "btn success small right", id: :assign_projects_to_team
- if @team.projects.any?
%table#projects_list
%thead
%tr
%th Project name
%th Max access
%th.cred.span3 Danger Zone!
- @team.projects.each do |project|
%tr.project
%td
= link_to project.name_with_namespace, [:admin, project]
%td
%span= @team.human_max_project_access(project)
%td.bgred
= link_to 'Edit', edit_admin_team_project_path(@team, project), class: "btn small"
&nbsp;
= link_to 'Relegate', admin_team_project_path(@team, project), confirm: 'Remove project from team. Are you sure?', method: :delete, class: "btn danger small", id: "relegate_project_#{project.id}"
:javascript
$(function(){
var modal = $('.change-owner-holder');
$('.change-owner-link').bind("click", function(){
$(this).hide();
modal.show();
});
$('.change-owner-cancel-link').bind("click", function(){
modal.hide();
$('.change-owner-link').show();
})
})

View file

@ -1,4 +1,4 @@
.groups_box
.ui-box
%h5.title
Groups
%small
@ -13,8 +13,6 @@
%li
= link_to group_path(id: group.path), class: dom_class(group) do
%strong.well-title= truncate(group.name, length: 35)
%span.arrow
&rarr;
%span.last_activity
%strong Projects:
%span= current_user.authorized_projects.where(namespace_id: group.id).count
%span.right.light
- if group.owner == current_user
%i.icon-wrench

View file

@ -1,3 +1,5 @@
- if @teams.present?
= render "teams", teams: @teams
- if @groups.present?
= render "groups", groups: @groups
= render "projects", projects: @projects

View file

@ -0,0 +1,20 @@
.ui-box
%h5.title
Teams
%small
(#{@teams.count})
%span.right
= link_to new_team_path, class: "btn very_small info" do
%i.icon-plus
New Team
%ul.well-list
- @teams.each do |team|
%li
= link_to team_path(id: team.path), class: dom_class(team) do
%strong.well-title= truncate(team.name, length: 35)
%span.right.light
- if team.owner == current_user
%i.icon-wrench
- tm = current_user.user_team_user_relationships.find_by_user_team_id(team.id)
- if tm
= tm.access_human

View file

@ -10,6 +10,8 @@
= link_to "Stats", admin_root_path
= nav_link(controller: :projects) do
= link_to "Projects", admin_projects_path
= nav_link(controller: :teams) do
= link_to "Teams", admin_teams_path
= nav_link(controller: :groups) do
= link_to "Groups", admin_groups_path
= nav_link(controller: :users) do

View file

@ -3,7 +3,7 @@
= render "layouts/head", title: "#{@group.name}"
%body{class: "#{app_theme} application"}
= render "layouts/flash"
= render "layouts/head_panel", title: "#{@group.name}"
= render "layouts/head_panel", title: "group: #{@group.name}"
.container
%ul.main_menu
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do

View file

@ -0,0 +1,40 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head", title: "#{@team.name}"
%body{class: "#{app_theme} application"}
= render "layouts/flash"
= render "layouts/head_panel", title: "team: #{@team.name}"
.container
%ul.main_menu
= nav_link(path: 'teams#show', html_options: {class: 'home'}) do
= link_to "Home", team_path(@team), title: "Home"
= nav_link(path: 'teams#issues') do
= link_to issues_team_path(@team) do
Issues
%span.count= Issue.opened.of_user_team(@team).count
= nav_link(path: 'teams#merge_requests') do
= link_to merge_requests_team_path(@team) do
Merge Requests
%span.count= MergeRequest.opened.of_user_team(@team).count
= nav_link(path: 'teams#search') do
= link_to "Search", search_team_path(@team)
= nav_link(controller: [:members]) do
= link_to team_members_path(@team), class: "team-tab tab" do
Members
- if can? current_user, :admin_user_team, @team
= nav_link(controller: [:projects]) do
= link_to team_projects_path(@team), class: "team-tab tab" do
%i.icon-briefcase
Projects
= nav_link(path: 'teams#edit') do
= link_to edit_team_path(@team), class: "stat-tab tab " do
%i.icon-edit
Edit Team
.content= yield

View file

@ -3,7 +3,7 @@
= link_to project_path(@project), class: "activities-tab tab" do
%i.icon-home
Show
= nav_link(controller: :team_members) do
= nav_link(controller: [:team_members, :teams]) do
= link_to project_team_index_path(@project), class: "team-tab tab" do
%i.icon-user
Team

View file

@ -0,0 +1,22 @@
= render "projects/project_head"
%h3.page_title
= "Assign project to team of users"
%hr
%p.slead
Read more about assign to team of users #{link_to "here", '#', class: 'vlink'}.
= form_tag assign_project_teams_path(@project), method: 'post' do
%p.slead Choose Team of users you want to assign:
.padded
= label_tag :team_id, "Team"
.input= select_tag(:team_id, options_from_collection_for_select(@teams, :id, :name), prompt: "Select team", class: "chosen xxlarge", required: true)
%p.slead Choose greatest user acces in team you want to assign:
.padded
= label_tag :team_ids, "Permission"
.input= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles), {class: "project-access-select chosen span3" }
.actions
= submit_tag 'Assign', class: "btn save-btn"
= link_to "Cancel", project_team_index_path(@project), class: "btn cancel-btn"

View file

@ -1,11 +1,11 @@
%h3.page_title
= "New Team member(s)"
%hr
= form_for @team_member, as: :team_member, url: project_team_members_path(@project, @team_member) do |f|
-if @team_member.errors.any?
= form_for @user_project_relation, as: :team_member, url: project_team_members_path(@project) do |f|
-if @user_project_relation.errors.any?
.alert-message.block-message.error
%ul
- @team_member.errors.full_messages.each do |msg|
- @user_project_relation.errors.full_messages.each do |msg|
%li= msg
%h6 1. Choose people you want in the team
@ -16,7 +16,7 @@
%h6 2. Set access level for them
.clearfix
= f.label :project_access, "Project Access"
.input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen"
.input= select_tag :project_access, options_for_select(Project.access_options, @user_project_relation.project_access), class: "project-access-select chosen"
.actions
= f.submit 'Save', class: "btn save-btn"

View file

@ -1,11 +1,11 @@
- user = member.user
- allow_admin = can? current_user, :admin_project, @project
%li{id: dom_id(member), class: "team_member_row user_#{user.id}"}
%li{id: dom_id(user), class: "team_member_row user_#{user.id}"}
.row
.span6
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
= link_to project_team_member_path(@project, user), title: user.name, class: "dark" do
= image_tag gravatar_icon(user.email, 40), class: "avatar s32"
= link_to project_team_member_path(@project, member), title: user.name, class: "dark" do
= link_to project_team_member_path(@project, user), title: user.name, class: "dark" do
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.email
@ -13,7 +13,7 @@
.span5.right
- if allow_admin
.left
= form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f|
= form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f|
= f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2"
.right
- if current_user == user
@ -23,6 +23,6 @@
- elsif user.blocked
%span.btn.disabled.blocked Blocked
- elsif allow_admin
= link_to project_team_member_path(project_id: @project, id: member.id), confirm: remove_from_team_message(@project, member), method: :delete, class: "very_small btn danger" do
= link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "very_small btn danger" do
%i.icon-minus.icon-white

View file

@ -0,0 +1,15 @@
- team = team_rel.user_team
- allow_admin = can? current_user, :admin_team_member, @project
%li{id: dom_id(team), class: "user_team_row team_#{team.id}"}
.row
.span6
%strong= link_to team.name, team_path(team), title: team.name, class: "dark"
%br
%small.cgray Members: #{team.members.count}
.span5.right
.right
- if allow_admin
.left
= link_to resign_project_team_path(@project, team), method: :delete, confirm: "Are you shure?", class: "btn danger small" do
%i.icon-minus.icon-white

View file

@ -0,0 +1,16 @@
- grouper_project_teams(@project).each do |access, teams|
.ui-box
%h5.title
= UserTeam.access_roles.key(access).pluralize
%small= teams.size
%ul.well-list
- teams.sort_by(&:team_name).each do |tofr|
= render(partial: 'team_members/show_team', locals: {team_rel: tofr})
:javascript
$(function(){
$('.repo-access-select, .project-access-select').live("change", function() {
$(this.form).submit();
});
})

View file

@ -1,4 +1,4 @@
- if @team_member.valid?
- if @user_project_relation.valid?
:plain
$("#new_team_member").hide("slide", { direction: "right" }, 150, function(){
$("#team-table").show("slide", { direction: "left" }, 150, function() {

View file

@ -4,7 +4,7 @@
= "Import team from another project"
%hr
%p.slead
Read more about team import #{link_to "here", '#', class: 'vlink'}.
Read more about project team import #{link_to "here", '#', class: 'vlink'}.
= form_tag apply_import_project_team_members_path(@project), method: 'post' do
%p.slead Choose project you want to use as team source:
.padded

View file

@ -1,7 +1,7 @@
= render "projects/project_head"
%h3.page_title
Team Members
(#{@project.users_projects.count})
(#{@project.users.count})
%small
Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink"
@ -10,11 +10,24 @@
%span.right
= link_to import_project_team_members_path(@project), class: "btn small grouped", title: "Import team from another project" do
Import team from another project
= link_to available_project_teams_path(@project), class: "btn small grouped", title: "Assign project to team of users" do
Assign project to Team of users
= link_to new_project_team_member_path(@project), class: "btn success small grouped", title: "New Team Member" do
New Team Member
%hr
%hr
.clearfix
%div.team-table
= render partial: "team_members/team", locals: {project: @project}
%h3.page_title
Assigned teams
(#{@project.user_teams.count})
%hr
.clearfix
%div.team-table
= render partial: "team_members/teams", locals: {project: @project}

View file

@ -1,14 +1,13 @@
- allow_admin = can? current_user, :admin_project, @project
- user = @team_member.user
.team_member_show
- if can? current_user, :admin_project, @project
= link_to 'Remove from team', project_team_member_path(project_id: @project, id: @team_member.id), confirm: 'Are you sure?', method: :delete, class: "right btn danger"
= link_to 'Remove from team', project_team_member_path(@project, @member), confirm: 'Are you sure?', method: :delete, class: "right btn danger"
.profile_avatar_holder
= image_tag gravatar_icon(user.email, 60), class: "borders"
= image_tag gravatar_icon(@member.email, 60), class: "borders"
%h3.page_title
= user.name
%small (@#{user.username})
= @member.name
%small (@#{@member.username})
%hr
.back_link
@ -21,34 +20,34 @@
%table.lite
%tr
%td Email
%td= mail_to user.email
%td= mail_to @member.email
%tr
%td Skype
%td= user.skype
- unless user.linkedin.blank?
%td= @member.skype
- unless @member.linkedin.blank?
%tr
%td LinkedIn
%td= user.linkedin
- unless user.twitter.blank?
%td= @member.linkedin
- unless @member.twitter.blank?
%tr
%td Twitter
%td= user.twitter
- unless user.bio.blank?
%td= @member.twitter
- unless @member.bio.blank?
%tr
%td Bio
%td= user.bio
%td= @member.bio
.span6
%table.lite
%tr
%td Member since
%td= @team_member.created_at.stamp("Aug 21, 2011")
%td= @user_project_relation.created_at.stamp("Aug 21, 2011")
%tr
%td
Project Access:
%small (#{link_to "read more", help_permissions_path, class: "vlink"})
%td
= form_for(@team_member, as: :team_member, url: project_team_member_path(@project, @team_member)) do |f|
= f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select", disabled: !allow_admin
= form_for(@user_project_relation, as: :team_member, url: project_team_member_path(@project, @member)) do |f|
= f.select :project_access, options_for_select(Project.access_options, @user_project_relation.project_access), {}, class: "project-access-select", disabled: !allow_admin
%hr
= render @events
:javascript

View file

@ -1,6 +1,6 @@
- if @team_member.valid?
- if @user_project_relation.valid?
:plain
$("##{dom_id(@team_member)}").effect("highlight", {color: "#529214"}, 1000);;
$("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#529214"}, 1000);;
- else
:plain
$("##{dom_id(@team_member)}").effect("highlight", {color: "#D12F19"}, 1000);;
$("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#D12F19"}, 1000);;

View file

@ -0,0 +1,33 @@
= form_tag team_filter_path(entity), method: 'get' do
%fieldset.dashboard-search-filter
= search_field_tag "search", params[:search], { placeholder: 'Search', class: 'search-text-input' }
= button_tag type: 'submit', class: 'btn' do
%i.icon-search
%fieldset
%legend Status:
%ul.nav.nav-pills.nav-stacked
%li{class: ("active" if !params[:status])}
= link_to team_filter_path(entity, status: nil) do
Open
%li{class: ("active" if params[:status] == 'closed')}
= link_to team_filter_path(entity, status: 'closed') do
Closed
%li{class: ("active" if params[:status] == 'all')}
= link_to team_filter_path(entity, status: 'all') do
All
%fieldset
%legend Projects:
%ul.nav.nav-pills.nav-stacked
- @projects.each do |project|
- unless entities_per_project(project, entity).zero?
%li{class: ("active" if params[:project_id] == project.id.to_s)}
= link_to team_filter_path(entity, project_id: project.id) do
= project.name_with_namespace
%small.right= entities_per_project(project, entity)
%fieldset
%hr
= link_to "Reset", team_filter_path(entity), class: 'btn right'

View file

@ -0,0 +1,22 @@
.projects_box
%h5.title
Projects
%small
(#{projects.count})
- if can? current_user, :manage_group, @group
%span.right
= link_to new_project_path(namespace_id: @group.id), class: "btn very_small info" do
%i.icon-plus
New Project
%ul.well-list
- if projects.blank?
%p.nothing_here_message This team has no projects yet
- projects.each do |project|
%li
= link_to project_path(project), class: dom_class(project) do
%strong.well-title= truncate(project.name, length: 25)
%span.arrow
&rarr;
%span.last_activity
%strong Last activity:
%span= project_last_activity(project)

View file

View file

@ -0,0 +1,24 @@
= render "team_head"
%h3.page_title= "Edit Team #{@team.name}"
%hr
= form_for @team, url: teams_path do |f|
- if @team.errors.any?
.alert-message.block-message.error
%span= @team.errors.full_messages.first
.clearfix
= f.label :name do
Team name is
.input
= f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left"
.clearfix
= f.label :path do
Team path is
.input
= f.text_field :path, placeholder: "opensource", class: "xxlarge left"
.clearfix
.input.span3.center
= f.submit 'Save team changes', class: "btn primary"
.input.span3.center
= link_to 'Delete team', team_path(@team), method: :delete, confirm: "You are shure?", class: "btn danger"

View file

@ -0,0 +1,38 @@
%h3.page_title
Teams
%small
list of all teams
= link_to 'New Team', new_team_path, class: "btn success small right"
%br
= form_tag search_teams_path, method: :get, class: 'form-inline' do
= text_field_tag :name, params[:name], class: "xlarge"
= submit_tag "Search", class: "btn submit primary"
%table.teams_list
%thead
%tr
%th
Name
%i.icon-sort-down
%th Path
%th Projects
%th Members
%th Owner
%th.cred Danger Zone!
- @teams.each do |team|
%tr
%td
%strong= link_to team.name, team_path(team)
%td= team.path
%td= link_to team.projects.count, team_projects_path(team)
%td= link_to team.members.count, team_members_path(team)
%td= link_to team.owner.name, team_member_path(team, team.owner)
%td.bgred
- if current_user.can?(:manage_user_team, team)
= link_to "Edit", edit_team_path(team), class: "btn small"
- if current_user.can?(:admin_user_team, team)
= link_to "Destroy", team_path(team), method: :delete, confirm: "You are shure?", class: "danger btn small"
&nbsp;

View file

@ -0,0 +1,25 @@
= render "team_head"
%h3.page_title
Issues
%small (in Team projects assigned to Team members)
%small.right #{@issues.total_count} issues
%hr
.row
.span3
= render 'filter', entity: 'issue'
.span9
- if @issues.any?
- @issues.group_by(&:project).each do |group|
%div.ui-box
- @project = group[0]
%h5.title
= link_to_project @project
%ul.well-list.issues_table
- group[1].each do |issue|
= render(partial: 'issues/show', locals: {issue: issue})
%hr
= paginate @issues, theme: "gitlab"
- else
%p.nothing_here_message Nothing to show here

View file

@ -0,0 +1,20 @@
= form_tag admin_team_member_path(@team, @member), method: :put do
-if @member.errors.any?
.alert-message.block-message.error
%ul
- @member.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Default access for Team projects:
.input
= select_tag :default_project_access, options_for_select(UserTeam.access_roles, @team.default_projects_access(@member)), class: "project-access-select chosen span3"
.clearfix
%label Team admin?
.input
= check_box_tag :group_admin, true, @team.admin?(@member)
%br
.actions
= submit_tag 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"

View file

@ -0,0 +1,31 @@
- user = member.user
- allow_admin = can? current_user, :manage_user_team, @team
%li{id: dom_id(member), class: "team_member_row user_#{user.id}"}
.row
.span5
= link_to user_path(user.username), title: user.name, class: "dark" do
= image_tag gravatar_icon(user.email, 40), class: "avatar s32"
= link_to user_path(user.username), title: user.name, class: "dark" do
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.email
.span6.right
- if allow_admin
.left.span2
= form_for(member, as: :team_member, url: team_member_path(@team, user)) do |f|
= f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "medium project-access-select span2"
.left.span2
%span
Admin access
= check_box_tag :group_admin
.right
- if current_user == user
%span.btn.disabled This is you!
- if @team.owner == user
%span.btn.disabled.success Owner
- elsif user.blocked
%span.btn.disabled.blocked Blocked
- elsif allow_admin
= link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "very_small btn danger" do
%i.icon-minus.icon-white

View file

@ -0,0 +1,16 @@
- grouped_user_team_members(@team).each do |access, members|
.ui-box
%h5.title
= Project.access_options.key(access).pluralize
%small= members.size
%ul.well-list
- members.sort_by(&:user_name).each do |up|
= render(partial: 'teams/members/show', locals: {member: up})
:javascript
$(function(){
$('.repo-access-select, .project-access-select').live("change", function() {
$(this.form).submit();
});
})

View file

@ -0,0 +1,18 @@
= render "teams/team_head"
%h3
Edit access #{@member.name} in #{@team.name} team
%hr
%table.zebra-striped
%tr
%td User:
%td= @member.name
%tr
%td Team:
%td= @team.name
%tr
%td Since:
%td= member_since(@team, @member).stamp("Nov 11, 2010")
= render 'form'

View file

@ -0,0 +1,19 @@
= render "teams/team_head"
%h3.page_title
Team Members
(#{@members.count})
%small
Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink"
- if can? current_user, :manage_user_team, @team
%span.right
= link_to new_team_member_path(@team), class: "btn success small grouped", title: "New Team Member" do
New Team Member
%hr
.clearfix
%div.team-table
= render partial: "teams/members/team", locals: {project: @team}

View file

@ -0,0 +1,30 @@
= render "teams/team_head"
%h3.page_title
Team: #{@team.name}
%fieldset
%legend Members (#{@team.members.count})
= form_tag team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do
%table#members_list
%thead
%tr
%th User name
%th Default project access
%th Team access
%th
- @team.members.each do |member|
%tr.member
%td
= member.name
%small= "(#{member.email})"
%td= @team.human_default_projects_access(member)
%td= @team.admin?(member) ? "Admin" : "Member"
%td
%tr
%td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_email), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5'
%td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" }
%td
%span= check_box_tag :group_admin
%span Admin?
%td= submit_tag 'Add', class: "btn primary", id: :add_members_to_team

View file

@ -0,0 +1,62 @@
= render "teams/team_head"
- allow_admin = can? current_user, :admin_project, @project
- user = @team_member.user
.team_member_show
- if can? current_user, :admin_project, @project
= link_to 'Remove from team', project_team_member_path(project_id: @project, id: @team_member.id), confirm: 'Are you sure?', method: :delete, class: "right btn danger"
.profile_avatar_holder
= image_tag gravatar_icon(user.email, 60), class: "borders"
%h3.page_title
= user.name
%small (@#{user.username})
%hr
.back_link
%br
= link_to project_team_index_path(@project), class: "" do
&larr; To team list
%br
.row
.span6
%table.lite
%tr
%td Email
%td= mail_to user.email
%tr
%td Skype
%td= user.skype
- unless user.linkedin.blank?
%tr
%td LinkedIn
%td= user.linkedin
- unless user.twitter.blank?
%tr
%td Twitter
%td= user.twitter
- unless user.bio.blank?
%tr
%td Bio
%td= user.bio
.span6
%table.lite
%tr
%td Member since
%td= @team_member.created_at.stamp("Aug 21, 2011")
%tr
%td
Project Access:
%small (#{link_to "read more", help_permissions_path, class: "vlink"})
%td
= form_for(@team_member, as: :team_member, url: project_team_member_path(@project, @team_member)) do |f|
= f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select", disabled: !allow_admin
%hr
= render @events
:javascript
$(function(){
$('.repo-access-select, .project-access-select').live("change", function() {
$(this.form).submit();
});
})

View file

@ -0,0 +1,26 @@
= render "team_head"
%h3.page_title
Merge Requests
%small (authored by or assigned to Team members)
%small.right #{@merge_requests.total_count} merge requests
%hr
.row
.span3
= render 'filter', entity: 'merge_request'
.span9
- if @merge_requests.any?
- @merge_requests.group_by(&:project).each do |group|
.ui-box
- @project = group[0]
%h5.title
= link_to_project @project
%ul.well-list
- group[1].each do |merge_request|
= render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request})
%hr
= paginate @merge_requests, theme: "gitlab"
- else
%h3.nothing_here_message Nothing to show here

View file

@ -0,0 +1,19 @@
%h3.page_title New Team
%hr
= form_for @team, url: teams_path do |f|
- if @team.errors.any?
.alert-message.block-message.error
%span= @team.errors.full_messages.first
.clearfix
= f.label :name do
Team name is
.input
= f.text_field :name, placeholder: "Ex. Ruby Developers", class: "xxlarge left"
&nbsp;
= f.submit 'Create team', class: "btn primary"
%hr
.padded
%ul
%li All created teams are public (users can view who enter into team and which project are assigned for this team)
%li People within a team see only projects they have access to
%li You will be able to assign existing projects for team

View file

@ -0,0 +1,16 @@
= form_tag team_project_path(@team, @project), method: :put do
-if @project.errors.any?
.alert-message.block-message.error
%ul
- @project.errors.full_messages.each do |msg|
%li= msg
.clearfix
%label Max access for Team members:
.input
= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles, @team.max_project_access(@project)), class: "project-access-select chosen span3"
%br
.actions
= submit_tag 'Save', class: "btn primary"
= link_to 'Cancel', :back, class: "btn"

View file

@ -0,0 +1,18 @@
= render "teams/team_head"
%h3
Edit max access in #{@project.name} for #{@team.name} team
%hr
%table.zebra-striped
%tr
%td Project:
%td= @project.name
%tr
%td Team:
%td= @team.name
%tr
%td Since:
%td= assigned_since(@team, @project).stamp("Nov 11, 2010")
= render 'form'

View file

@ -0,0 +1,34 @@
= render "teams/team_head"
%h3.page_title
Assigned projects (#{@team.projects.count})
%small
Read more about project permissions
%strong= link_to "here", help_permissions_path, class: "vlink"
- if current_user.can?(:manage_user_team, @team) && @avaliable_projects.any?
%span.right
= link_to new_team_project_path(@team), class: "btn success small grouped", title: "New Team Member" do
Assign project to Team
%hr
%table.projects-table
%thead
%tr
%th Project name
%th Max access
- if current_user.can?(:admin_user_team, @team)
%th.span3
- @team.projects.each do |project|
%tr.project
%td
= link_to project.name_with_namespace, project_path(project)
%td
%span= @team.human_max_project_access(project)
- if current_user.can?(:admin_user_team, @team)
%td.bgred
= link_to 'Edit max access', edit_team_project_path(@team, project), class: "btn small"
= link_to 'Relegate', team_project_path(@team, project), confirm: 'Remove project from team and move to global namespace. Are you sure?', method: :delete, class: "btn danger small"

View file

@ -0,0 +1,25 @@
= render "teams/team_head"
%h3.page_title
Team: #{@team.name}
%fieldset
%legend Projects (#{@team.projects.count})
= form_tag team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do
%table#projects_list
%thead
%tr
%th Project name
%th Max access
%th
- @team.projects.each do |project|
%tr.project
%td
= link_to project.name_with_namespace, team_project_path(@team, project)
%td
%span= @team.human_max_project_access(project)
%td
%tr
%td= select_tag :project_ids, options_from_collection_for_select(@avaliable_projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5'
%td= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles), {class: "project-access-select chosen span3" }
%td= submit_tag 'Add', class: "btn primary", id: :assign_projects_to_team

View file

@ -0,0 +1,11 @@
= render "team_head"
= form_tag search_team_path(@team), method: :get, class: 'form-inline' do |f|
.padded
= label_tag :search do
%strong Looking for
.input
= search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search"
= submit_tag 'Search', class: "btn primary wide"
- if params[:search].present?
= render 'search/result'

View file

@ -0,0 +1,30 @@
= render "team_head"
.projects
.activities.span8
= link_to dashboard_path, class: 'btn very_small' do
&larr; To dashboard
&nbsp;
%span.cgray Events and projects are filtered in scope of team
%hr
- if @events.any?
.content_list
- else
%p.nothing_here_message Projects activity will be displayed here
.loading.hide
.side.span4
= render "projects", projects: @projects
%div
%span.rss-icon
= link_to dashboard_path(:atom, { private_token: current_user.private_token }) do
= image_tag "rss_ui.png", title: "feed"
%strong News Feed
%hr
.gitlab-promo
= link_to "Homepage", "http://gitlabhq.com"
= link_to "Blog", "http://blog.gitlabhq.com"
= link_to "@gitlabhq", "https://twitter.com/gitlabhq"
:javascript
$(function(){ Pager.init(20, true); });

View file

@ -21,7 +21,7 @@ Gitlab::Application.routes.draw do
project_root: Gitlab.config.gitolite.repos_path,
upload_pack: Gitlab.config.gitolite.upload_pack,
receive_pack: Gitlab.config.gitolite.receive_pack
}), at: '/', constraints: lambda { |request| /[-\/\w\.-]+\.git\//.match(request.path_info) }
}), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }
#
# Help
@ -56,6 +56,7 @@ Gitlab::Application.routes.draw do
put :unblock
end
end
resources :groups, constraints: { id: /[^\/]+/ } do
member do
put :project_update
@ -63,18 +64,31 @@ Gitlab::Application.routes.draw do
delete :remove_project
end
end
resources :teams, constraints: { id: /[^\/]+/ } do
scope module: :teams do
resources :members, only: [:edit, :update, :destroy, :new, :create]
resources :projects, only: [:edit, :update, :destroy, :new, :create], constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }
end
end
resources :hooks, only: [:index, :create, :destroy] do
get :test
end
resource :logs, only: [:show]
resource :resque, controller: 'resque', only: [:show]
resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create] do
member do
get :team
put :team_update
end
scope module: :projects, constraints: { id: /[^\/]+/ } do
resources :members, only: [:edit, :update, :destroy]
end
end
resources :team_members, only: [:edit, :update, :destroy]
resources :hooks, only: [:index, :create, :destroy] do
get :test
end
resource :logs, only: [:show]
resource :resque, controller: 'resque', only: [:show]
root to: "dashboard#index"
end
@ -108,7 +122,6 @@ Gitlab::Application.routes.draw do
get "dashboard/issues" => "dashboard#issues"
get "dashboard/merge_requests" => "dashboard#merge_requests"
#
# Groups Area
#
@ -122,6 +135,24 @@ Gitlab::Application.routes.draw do
end
end
#
# Teams Area
#
resources :teams, constraints: { id: /[^\/]+/ } do
member do
get :issues
get :merge_requests
get :search
end
scope module: :teams do
resources :members, only: [:index, :new, :create, :edit, :update, :destroy]
resources :projects, only: [:index, :new, :create, :edit, :update, :destroy], constraints: { id: /[a-zA-Z.0-9_\-\/]+/ }
end
collection do
get :search
end
end
resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create]
devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations }
@ -238,6 +269,18 @@ Gitlab::Application.routes.draw do
end
end
scope module: :projects do
resources :teams, only: [] do
collection do
get :available
post :assign
end
member do
delete :resign
end
end
end
resources :notes, only: [:index, :create, :destroy] do
collection do
post :preview

View file

@ -0,0 +1,11 @@
class CreateUserTeams < ActiveRecord::Migration
def change
create_table :user_teams do |t|
t.string :name
t.string :path
t.integer :owner_id
t.timestamps
end
end
end

View file

@ -0,0 +1,11 @@
class CreateUserTeamProjectRelationships < ActiveRecord::Migration
def change
create_table :user_team_project_relationships do |t|
t.integer :project_id
t.integer :user_team_id
t.integer :greatest_access
t.timestamps
end
end
end

View file

@ -0,0 +1,12 @@
class CreateUserTeamUserRelationships < ActiveRecord::Migration
def change
create_table :user_team_user_relationships do |t|
t.integer :user_id
t.integer :user_team_id
t.boolean :group_admin
t.integer :permission
t.timestamps
end
end
end

View file

@ -213,6 +213,31 @@ ActiveRecord::Schema.define(:version => 20130110172407) do
t.string "name"
end
create_table "user_team_project_relationships", :force => true do |t|
t.integer "project_id"
t.integer "user_team_id"
t.integer "greatest_access"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "user_team_user_relationships", :force => true do |t|
t.integer "user_id"
t.integer "user_team_id"
t.boolean "group_admin"
t.integer "permission"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "user_teams", :force => true do |t|
t.string "name"
t.string "path"
t.integer "owner_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "encrypted_password", :default => "", :null => false

View file

@ -0,0 +1,70 @@
Feature: Admin Teams
Background:
Given I sign in as an admin
And Create gitlab user "John"
Scenario: Create a team
When I visit admin teams page
And I click new team link
And submit form with new team info
Then I should be redirected to team page
And I should see newly created team
Scenario: Add user to team
When I visit admin teams page
When I have clean "HardCoders" team
And I visit "HardCoders" team page
When I click to "Add members" link
When I select user "John" from user list as "Developer"
And submit form with new team member info
Then I should see "John" in teams members list as "Developer"
Scenario: Assign team to existing project
When I visit admin teams page
When I have "HardCoders" team with "John" member with "Developer" role
When I have "Shop" project
And I visit "HardCoders" team page
Then I should see empty projects table
When I click to "Add projects" link
When I select project "Shop" with max access "Reporter"
And submit form with new team project info
Then I should see "Shop" project in projects list
When I visit "Shop" project admin page
Then I should see "John" user with role "Reporter" in team table
Scenario: Add user to team with ptojects
When I visit admin teams page
When I have "HardCoders" team with "John" member with "Developer" role
And "HardCoders" team assigned to "Shop" project with "Developer" max role access
When I have gitlab user "Jimm"
And I visit "HardCoders" team page
Then I should see members table without "Jimm" member
When I click to "Add members" link
When I select user "Jimm" ub team members list as "Master"
And submit form with new team member info
Then I should see "Jimm" in teams members list as "Master"
Scenario: Remove member from team
Given I have users team "HardCoders"
And gitlab user "John" is a member "HardCoders" team
And gitlab user "Jimm" is a member "HardCoders" team
And "HardCoders" team is assigned to "Shop" project
When I visit admin teams page
When I visit "HardCoders" team admin page
Then I shoould see "John" in members list
And I should see "Jimm" in members list
And I should see "Shop" in projects list
When I click on remove "Jimm" user link
Then I should be redirected to "HardCoders" team admin page
And I should not to see "Jimm" user in members list
Scenario: Remove project from team
Given I have users team "HardCoders"
And gitlab user "John" is a member "HardCoders" team
And gitlab user "Jimm" is a member "HardCoders" team
And "HardCoders" team is assigned to "Shop" project
When I visit admin teams page
When I visit "HardCoders" team admin page
Then I should see "Shop" project in projects list
When I click on "Relegate" link on "Shop" project
Then I should see projects liston team page without "Shop" project

View file

@ -0,0 +1,234 @@
class AdminTeams < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedActiveTab
include SharedAdmin
And 'I have own project' do
create :project
end
And 'Create gitlab user "John"' do
@user = create(:user, :name => "John")
end
And 'I click new team link' do
click_link "New Team"
end
And 'submit form with new team info' do
fill_in 'user_team_name', with: 'gitlab'
click_button 'Create team'
end
Then 'I should be redirected to team page' do
current_path.should == admin_team_path(UserTeam.last)
end
And 'I should see newly created team' do
page.should have_content "Team: gitlab"
end
When 'I visit admin teams page' do
visit admin_teams_path
end
When 'I have clean "HardCoders" team' do
@team = create :user_team, name: "HardCoders", owner: current_user
end
And 'I visit "HardCoders" team page' do
visit admin_team_path(UserTeam.find_by_name("HardCoders"))
end
Then 'I should see only me in members table' do
members_list = find("#members_list .member")
members_list.should have_content(current_user.name)
members_list.should have_content(current_user.email)
end
When 'I select user "John" from user list as "Developer"' do
@user ||= User.find_by_name("John")
within "#team_members" do
select @user.name, :from => "user_ids"
select "Developer", :from => "default_project_access"
end
end
And 'submit form with new team member info' do
click_button 'add_members_to_team'
end
Then 'I should see "John" in teams members list as "Developer"' do
@user ||= User.find_by_name("John")
find_in_list("#members_list .member", @user).must_equal true
end
When 'I visit "John" user admin page' do
pending 'step not implemented'
end
Then 'I should see "HardCoders" team in teams table' do
pending 'step not implemented'
end
When 'I have "HardCoders" team with "John" member with "Developer" role' do
@team = create :user_team, name: "HardCoders", owner: current_user
@user ||= User.find_by_name("John")
@team.add_member(@user, UserTeam.access_roles["Developer"], group_admin: false)
end
When 'I have "Shop" project' do
@project = create :project, name: "Shop"
end
Then 'I should see empty projects table' do
page.has_no_css?("#projects_list").must_equal true
end
When 'I select project "Shop" with max access "Reporter"' do
@project ||= Project.find_by_name("Shop")
within "#assign_projects" do
select @project.name, :from => "project_ids"
select "Reporter", :from => "greatest_project_access"
end
end
And 'submit form with new team project info' do
click_button 'assign_projects_to_team'
end
Then 'I should see "Shop" project in projects list' do
project = Project.find_by_name("Shop")
find_in_list("#projects_list .project", project).must_equal true
end
When 'I visit "Shop" project admin page' do
project = Project.find_by_name("Shop")
visit admin_project_path(project)
end
And '"HardCoders" team assigned to "Shop" project with "Developer" max role access' do
@team = UserTeam.find_by_name("HardCoders")
@project = create :project, name: "Shop"
@team.assign_to_project(@project, UserTeam.access_roles["Developer"])
end
When 'I have gitlab user "Jimm"' do
create :user, name: "Jimm"
end
Then 'I should see members table without "Jimm" member' do
user = User.find_by_name("Jimm")
find_in_list("#members_list .member", user).must_equal false
end
When 'I select user "Jimm" ub team members list as "Master"' do
user = User.find_by_name("Jimm")
within "#team_members" do
select user.name, :from => "user_ids"
select "Developer", :from => "default_project_access"
end
end
Then 'I should see "Jimm" in teams members list as "Master"' do
user = User.find_by_name("Jimm")
find_in_list("#members_list .member", user).must_equal true
end
Given 'I have users team "HardCoders"' do
@team = create :user_team, name: "HardCoders"
end
And 'gitlab user "John" is a member "HardCoders" team' do
@team = UserTeam.find_by_name("HardCoders")
@user = User.find_by_name("John")
@user = create :user, name: "John" unless @user
@team.add_member(@user, UserTeam.access_roles["Master"], group_admin: false)
end
And 'gitlab user "Jimm" is a member "HardCoders" team' do
@team = UserTeam.find_by_name("HardCoders")
@user = User.find_by_name("Jimm")
@user = create :user, name: "Jimm" unless @user
@team.add_member(@user, UserTeam.access_roles["Master"], group_admin: false)
end
And '"HardCoders" team is assigned to "Shop" project' do
@team = UserTeam.find_by_name("HardCoders")
@project = create :project, name: "Shop"
@team.assign_to_project(@project, UserTeam.access_roles["Developer"])
end
When 'I visit "HardCoders" team admin page' do
visit admin_team_path(UserTeam.find_by_name("HardCoders"))
end
Then 'I shoould see "John" in members list' do
user = User.find_by_name("John")
find_in_list("#members_list .member", user).must_equal true
end
And 'I should see "Jimm" in members list' do
user = User.find_by_name("Jimm")
find_in_list("#members_list .member", user).must_equal true
end
And 'I should see "Shop" in projects list' do
project = Project.find_by_name("Shop")
find_in_list("#projects_list .project", project).must_equal true
end
When 'I click on remove "Jimm" user link' do
user = User.find_by_name("Jimm")
click_link "remove_member_#{user.id}"
end
Then 'I should be redirected to "HardCoders" team admin page' do
current_path.should == admin_team_path(UserTeam.find_by_name("HardCoders"))
end
And 'I should not to see "Jimm" user in members list' do
user = User.find_by_name("Jimm")
find_in_list("#members_list .member", user).must_equal false
end
When 'I click on "Relegate" link on "Shop" project' do
project = Project.find_by_name("Shop")
click_link "relegate_project_#{project.id}"
end
Then 'I should see projects liston team page without "Shop" project' do
project = Project.find_by_name("Shop")
find_in_list("#projects_list .project", project).must_equal false
end
Then 'I should see "John" user with role "Reporter" in team table' do
user = User.find_by_name("John")
find_in_list(".team_members", user).must_equal true
end
When 'I click to "Add members" link' do
click_link "Add members"
end
When 'I click to "Add projects" link' do
click_link "Add projects"
end
protected
def current_team
@team ||= Team.first
end
def find_in_list(selector, item)
members_list = all(selector)
entered = false
members_list.each do |member_item|
entered = true if member_item.has_content?(item.name)
end
entered
end
end

Some files were not shown because too many files have changed in this diff Show more