Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2013-01-25 12:46:20 +0400
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2013-01-25 12:46:20 +0400
commitaa1f1eb680e4328c2cf619a770f1e90f74c41987 (patch)
tree739d73b52a4e6f97a1e8f27a8206f66550f33904 /app
parent097e6053efd973c70c4995784a1abc30e9384008 (diff)
parentd839f6c52571e3b873a05779b1131f7b00670b31 (diff)
Merge pull request #2746 from gitlabhq/features/teams
New feature: Teams
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/dashboard.js.coffee8
-rw-r--r--app/assets/javascripts/merge_requests.js.coffee18
-rw-r--r--app/assets/stylesheets/sections/projects.scss1
-rw-r--r--app/controllers/admin/application_controller.rb (renamed from app/controllers/admin_controller.rb)2
-rw-r--r--app/controllers/admin/dashboard_controller.rb2
-rw-r--r--app/controllers/admin/groups_controller.rb2
-rw-r--r--app/controllers/admin/hooks_controller.rb2
-rw-r--r--app/controllers/admin/logs_controller.rb2
-rw-r--r--app/controllers/admin/projects/application_controller.rb11
-rw-r--r--app/controllers/admin/projects/members_controller.rb32
-rw-r--r--app/controllers/admin/projects_controller.rb6
-rw-r--r--app/controllers/admin/resque_controller.rb2
-rw-r--r--app/controllers/admin/team_members_controller.rb22
-rw-r--r--app/controllers/admin/teams/application_controller.rb11
-rw-r--r--app/controllers/admin/teams/members_controller.rb41
-rw-r--r--app/controllers/admin/teams/projects_controller.rb41
-rw-r--r--app/controllers/admin/teams_controller.rb59
-rw-r--r--app/controllers/admin/users_controller.rb2
-rw-r--r--app/controllers/application_controller.rb9
-rw-r--r--app/controllers/dashboard_controller.rb2
-rw-r--r--app/controllers/projects/application_controller.rb11
-rw-r--r--app/controllers/projects/teams_controller.rb27
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/controllers/team_members_controller.rb23
-rw-r--r--app/controllers/teams/application_controller.rb13
-rw-r--r--app/controllers/teams/members_controller.rb49
-rw-r--r--app/controllers/teams/projects_controller.rb57
-rw-r--r--app/controllers/teams_controller.rb92
-rw-r--r--app/decorators/user_decorator.rb4
-rw-r--r--app/helpers/admin/teams/members_helper.rb5
-rw-r--r--app/helpers/admin/teams/projects_helper.rb5
-rw-r--r--app/helpers/projects_helper.rb8
-rw-r--r--app/helpers/user_teams_helper.rb26
-rw-r--r--app/models/ability.rb17
-rw-r--r--app/models/concerns/issuable.rb1
-rw-r--r--app/models/project.rb46
-rw-r--r--app/models/project_team.rb (renamed from app/models/team.rb)2
-rw-r--r--app/models/user.rb36
-rw-r--r--app/models/user_team.rb97
-rw-r--r--app/models/user_team_project_relationship.rb28
-rw-r--r--app/models/user_team_user_relationship.rb19
-rw-r--r--app/models/users_project.rb3
-rw-r--r--app/views/admin/projects/members/_form.html.haml16
-rw-r--r--app/views/admin/projects/members/edit.html.haml8
-rw-r--r--app/views/admin/projects/show.html.haml14
-rw-r--r--app/views/admin/team_members/_form.html.haml16
-rw-r--r--app/views/admin/team_members/edit.html.haml8
-rw-r--r--app/views/admin/teams/edit.html.haml23
-rw-r--r--app/views/admin/teams/index.html.haml38
-rw-r--r--app/views/admin/teams/members/_form.html.haml20
-rw-r--r--app/views/admin/teams/members/edit.html.haml16
-rw-r--r--app/views/admin/teams/members/new.html.haml29
-rw-r--r--app/views/admin/teams/new.html.haml19
-rw-r--r--app/views/admin/teams/projects/_form.html.haml16
-rw-r--r--app/views/admin/teams/projects/edit.html.haml16
-rw-r--r--app/views/admin/teams/projects/new.html.haml23
-rw-r--r--app/views/admin/teams/show.html.haml101
-rw-r--r--app/views/dashboard/_groups.html.haml10
-rw-r--r--app/views/dashboard/_sidebar.html.haml2
-rw-r--r--app/views/dashboard/_teams.html.haml20
-rw-r--r--app/views/layouts/admin.html.haml2
-rw-r--r--app/views/layouts/group.html.haml2
-rw-r--r--app/views/layouts/user_team.html.haml40
-rw-r--r--app/views/projects/_project_head.html.haml2
-rw-r--r--app/views/projects/teams/available.html.haml22
-rw-r--r--app/views/team_members/_form.html.haml8
-rw-r--r--app/views/team_members/_show.html.haml10
-rw-r--r--app/views/team_members/_show_team.html.haml15
-rw-r--r--app/views/team_members/_teams.html.haml16
-rw-r--r--app/views/team_members/create.js.haml2
-rw-r--r--app/views/team_members/import.html.haml2
-rw-r--r--app/views/team_members/index.html.haml17
-rw-r--r--app/views/team_members/show.html.haml31
-rw-r--r--app/views/team_members/update.js.haml6
-rw-r--r--app/views/teams/_filter.html.haml33
-rw-r--r--app/views/teams/_projects.html.haml22
-rw-r--r--app/views/teams/_team_head.html.haml0
-rw-r--r--app/views/teams/edit.html.haml24
-rw-r--r--app/views/teams/index.html.haml38
-rw-r--r--app/views/teams/issues.html.haml25
-rw-r--r--app/views/teams/members/_form.html.haml20
-rw-r--r--app/views/teams/members/_show.html.haml31
-rw-r--r--app/views/teams/members/_team.html.haml16
-rw-r--r--app/views/teams/members/edit.html.haml18
-rw-r--r--app/views/teams/members/index.html.haml19
-rw-r--r--app/views/teams/members/new.html.haml30
-rw-r--r--app/views/teams/members/show.html.haml62
-rw-r--r--app/views/teams/merge_requests.html.haml26
-rw-r--r--app/views/teams/new.html.haml19
-rw-r--r--app/views/teams/projects/_form.html.haml16
-rw-r--r--app/views/teams/projects/edit.html.haml18
-rw-r--r--app/views/teams/projects/index.html.haml34
-rw-r--r--app/views/teams/projects/new.html.haml25
-rw-r--r--app/views/teams/search.html.haml11
-rw-r--r--app/views/teams/show.html.haml30
95 files changed, 1777 insertions, 158 deletions
diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee
index f15d09dd475..6171e0d50fd 100644
--- a/app/assets/javascripts/dashboard.js.coffee
+++ b/app/assets/javascripts/dashboard.js.coffee
@@ -4,11 +4,11 @@ window.dashboardPage = ->
event.preventDefault()
toggleFilter $(this)
reloadActivities()
-
+
reloadActivities = ->
$(".content_list").html ''
Pager.init 20, true
-
+
toggleFilter = (sender) ->
sender.parent().toggleClass "inactive"
event_filters = $.cookie("event_filter")
@@ -17,11 +17,11 @@ toggleFilter = (sender) ->
event_filters = event_filters.split(",")
else
event_filters = new Array()
-
+
index = event_filters.indexOf(filter)
if index is -1
event_filters.push filter
else
event_filters.splice index, 1
-
+
$.cookie "event_filter", event_filters.join(",")
diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee
index 9c9cc6132b3..65ed817c7c6 100644
--- a/app/assets/javascripts/merge_requests.js.coffee
+++ b/app/assets/javascripts/merge_requests.js.coffee
@@ -1,6 +1,6 @@
#
# * Filter merge requests
-#
+#
@merge_requestsPage = ->
$('#assignee_id').chosen()
$('#milestone_id').chosen()
@@ -8,16 +8,16 @@
$(this).closest('form').submit()
class MergeRequest
-
+
constructor: (@opts) ->
this.$el = $('.merge-request')
@diffs_loaded = false
@commits_loaded = false
-
+
this.activateTab(@opts.action)
-
+
this.bindEvents()
-
+
this.initMergeWidget()
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
@@ -28,7 +28,7 @@ class MergeRequest
initMergeWidget: ->
this.showState( @opts.current_state )
-
+
if this.$('.automerge_widget').length and @opts.check_enable
$.get @opts.url_to_automerge_check, (data) =>
this.showState( data.state )
@@ -42,12 +42,12 @@ class MergeRequest
bindEvents: ->
this.$('.nav-tabs').on 'click', 'a', (event) =>
a = $(event.currentTarget)
-
+
href = a.attr('href')
History.replaceState {path: href}, document.title, href
-
+
event.preventDefault()
-
+
this.$('.nav-tabs').on 'click', 'li', (event) =>
this.activateTab($(event.currentTarget).data('action'))
diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss
index 4bdc56d2884..ee2c379f2ab 100644
--- a/app/assets/stylesheets/sections/projects.scss
+++ b/app/assets/stylesheets/sections/projects.scss
@@ -7,6 +7,7 @@
@extend .right;
.groups_box,
+ .teams_box,
.projects_box {
> .title {
padding: 2px 15px;
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin/application_controller.rb
index bce9f692385..6a8f20f6047 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -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!
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index f97c56b0b31..3c27b86180b 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -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)
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 90dbda3eeea..f552fb595b8 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -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
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 91a1d633590..c5bf76f8c39 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -1,4 +1,4 @@
-class Admin::HooksController < AdminController
+class Admin::HooksController < Admin::ApplicationController
def index
@hooks = SystemHook.all
@hook = SystemHook.new
diff --git a/app/controllers/admin/logs_controller.rb b/app/controllers/admin/logs_controller.rb
index 28c321a9e52..b999018dde4 100644
--- a/app/controllers/admin/logs_controller.rb
+++ b/app/controllers/admin/logs_controller.rb
@@ -1,2 +1,2 @@
-class Admin::LogsController < AdminController
+class Admin::LogsController < Admin::ApplicationController
end
diff --git a/app/controllers/admin/projects/application_controller.rb b/app/controllers/admin/projects/application_controller.rb
new file mode 100644
index 00000000000..0f3da998666
--- /dev/null
+++ b/app/controllers/admin/projects/application_controller.rb
@@ -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
diff --git a/app/controllers/admin/projects/members_controller.rb b/app/controllers/admin/projects/members_controller.rb
new file mode 100644
index 00000000000..5c20c0717ed
--- /dev/null
+++ b/app/controllers/admin/projects/members_controller.rb
@@ -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
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index fc2793a72e7..711817395f1 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -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.'
diff --git a/app/controllers/admin/resque_controller.rb b/app/controllers/admin/resque_controller.rb
index 9d8e7e3051f..7d489ab4876 100644
--- a/app/controllers/admin/resque_controller.rb
+++ b/app/controllers/admin/resque_controller.rb
@@ -1,4 +1,4 @@
-class Admin::ResqueController < AdminController
+class Admin::ResqueController < Admin::ApplicationController
def show
end
end
diff --git a/app/controllers/admin/team_members_controller.rb b/app/controllers/admin/team_members_controller.rb
deleted file mode 100644
index 073208057ca..00000000000
--- a/app/controllers/admin/team_members_controller.rb
+++ /dev/null
@@ -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
diff --git a/app/controllers/admin/teams/application_controller.rb b/app/controllers/admin/teams/application_controller.rb
new file mode 100644
index 00000000000..8710821454e
--- /dev/null
+++ b/app/controllers/admin/teams/application_controller.rb
@@ -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
diff --git a/app/controllers/admin/teams/members_controller.rb b/app/controllers/admin/teams/members_controller.rb
new file mode 100644
index 00000000000..139b82ab66c
--- /dev/null
+++ b/app/controllers/admin/teams/members_controller.rb
@@ -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
diff --git a/app/controllers/admin/teams/projects_controller.rb b/app/controllers/admin/teams/projects_controller.rb
new file mode 100644
index 00000000000..8584a188b20
--- /dev/null
+++ b/app/controllers/admin/teams/projects_controller.rb
@@ -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
diff --git a/app/controllers/admin/teams_controller.rb b/app/controllers/admin/teams_controller.rb
new file mode 100644
index 00000000000..335add0f57d
--- /dev/null
+++ b/app/controllers/admin/teams_controller.rb
@@ -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
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 8669f5d1d38..659dd2f2ab0 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -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])
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 3457a1ab1b4..f903c7fdd62 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -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
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index c0ec4708e0a..1322973489c 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -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))
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
new file mode 100644
index 00000000000..7e4776d2d75
--- /dev/null
+++ b/app/controllers/projects/application_controller.rb
@@ -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
diff --git a/app/controllers/projects/teams_controller.rb b/app/controllers/projects/teams_controller.rb
new file mode 100644
index 00000000000..3ca724aaf4d
--- /dev/null
+++ b/app/controllers/projects/teams_controller.rb
@@ -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
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 368737d1e0b..6e5e1f91381 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -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
diff --git a/app/controllers/team_members_controller.rb b/app/controllers/team_members_controller.rb
index 8378a8458ff..7e4c8792b50 100644
--- a/app/controllers/team_members_controller.rb
+++ b/app/controllers/team_members_controller.rb
@@ -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
diff --git a/app/controllers/teams/application_controller.rb b/app/controllers/teams/application_controller.rb
new file mode 100644
index 00000000000..fc23202610c
--- /dev/null
+++ b/app/controllers/teams/application_controller.rb
@@ -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
diff --git a/app/controllers/teams/members_controller.rb b/app/controllers/teams/members_controller.rb
new file mode 100644
index 00000000000..c41d5d7abe6
--- /dev/null
+++ b/app/controllers/teams/members_controller.rb
@@ -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
diff --git a/app/controllers/teams/projects_controller.rb b/app/controllers/teams/projects_controller.rb
new file mode 100644
index 00000000000..27dc934452f
--- /dev/null
+++ b/app/controllers/teams/projects_controller.rb
@@ -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
diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb
new file mode 100644
index 00000000000..7de094214d4
--- /dev/null
+++ b/app/controllers/teams_controller.rb
@@ -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
diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb
index af9c6a63e75..b781f237352 100644
--- a/app/decorators/user_decorator.rb
+++ b/app/decorators/user_decorator.rb
@@ -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
diff --git a/app/helpers/admin/teams/members_helper.rb b/app/helpers/admin/teams/members_helper.rb
new file mode 100644
index 00000000000..58b9f1896c4
--- /dev/null
+++ b/app/helpers/admin/teams/members_helper.rb
@@ -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
diff --git a/app/helpers/admin/teams/projects_helper.rb b/app/helpers/admin/teams/projects_helper.rb
new file mode 100644
index 00000000000..b97cc403337
--- /dev/null
+++ b/app/helpers/admin/teams/projects_helper.rb
@@ -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
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 158925ba6c0..c6cb9129499 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -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
diff --git a/app/helpers/user_teams_helper.rb b/app/helpers/user_teams_helper.rb
new file mode 100644
index 00000000000..2055bb3c8bc
--- /dev/null
+++ b/app/helpers/user_teams_helper.rb
@@ -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
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 9d33501fdbc..63d720164a1 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -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
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index d1717d3bbee..8872cf59a2c 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -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")
diff --git a/app/models/project.rb b/app/models/project.rb
index fa38093b7d5..ba46fea2864 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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)
diff --git a/app/models/team.rb b/app/models/project_team.rb
index 51f4ff68d7b..2cc76974987 100644
--- a/app/models/team.rb
+++ b/app/models/project_team.rb
@@ -1,4 +1,4 @@
-class Team
+class ProjectTeam
attr_accessor :project
def initialize(project)
diff --git a/app/models/user.rb b/app/models/user.rb
index 743d7523bdc..b61d2cb0d83 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -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_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_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, 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
diff --git a/app/models/user_team.rb b/app/models/user_team.rb
new file mode 100644
index 00000000000..b28a6a041ac
--- /dev/null
+++ b/app/models/user_team.rb
@@ -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
diff --git a/app/models/user_team_project_relationship.rb b/app/models/user_team_project_relationship.rb
new file mode 100644
index 00000000000..1b0368c7ecc
--- /dev/null
+++ b/app/models/user_team_project_relationship.rb
@@ -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
diff --git a/app/models/user_team_user_relationship.rb b/app/models/user_team_user_relationship.rb
new file mode 100644
index 00000000000..63bdc49e5b6
--- /dev/null
+++ b/app/models/user_team_user_relationship.rb
@@ -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
diff --git a/app/models/users_project.rb b/app/models/users_project.rb
index 79146289836..ca5048ca7d4 100644
--- a/app/models/users_project.rb
+++ b/app/models/users_project.rb
@@ -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
diff --git a/app/views/admin/projects/members/_form.html.haml b/app/views/admin/projects/members/_form.html.haml
new file mode 100644
index 00000000000..f1bb6cfa226
--- /dev/null
+++ b/app/views/admin/projects/members/_form.html.haml
@@ -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"
diff --git a/app/views/admin/projects/members/edit.html.haml b/app/views/admin/projects/members/edit.html.haml
new file mode 100644
index 00000000000..2d76deb2aca
--- /dev/null
+++ b/app/views/admin/projects/members/edit.html.haml
@@ -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'
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 8e0d82328df..a213c09d456 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -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
diff --git a/app/views/admin/team_members/_form.html.haml b/app/views/admin/team_members/_form.html.haml
deleted file mode 100644
index 9cd94fdd30f..00000000000
--- a/app/views/admin/team_members/_form.html.haml
+++ /dev/null
@@ -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"
diff --git a/app/views/admin/team_members/edit.html.haml b/app/views/admin/team_members/edit.html.haml
deleted file mode 100644
index aea9bd70a79..00000000000
--- a/app/views/admin/team_members/edit.html.haml
+++ /dev/null
@@ -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'
diff --git a/app/views/admin/teams/edit.html.haml b/app/views/admin/teams/edit.html.haml
new file mode 100644
index 00000000000..b2499ef6b8b
--- /dev/null
+++ b/app/views/admin/teams/edit.html.haml
@@ -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"
diff --git a/app/views/admin/teams/index.html.haml b/app/views/admin/teams/index.html.haml
new file mode 100644
index 00000000000..3ab57448ab2
--- /dev/null
+++ b/app/views/admin/teams/index.html.haml
@@ -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"
diff --git a/app/views/admin/teams/members/_form.html.haml b/app/views/admin/teams/members/_form.html.haml
new file mode 100644
index 00000000000..b75d788a94a
--- /dev/null
+++ b/app/views/admin/teams/members/_form.html.haml
@@ -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"
diff --git a/app/views/admin/teams/members/edit.html.haml b/app/views/admin/teams/members/edit.html.haml
new file mode 100644
index 00000000000..a82847ee5f8
--- /dev/null
+++ b/app/views/admin/teams/members/edit.html.haml
@@ -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'
diff --git a/app/views/admin/teams/members/new.html.haml b/app/views/admin/teams/members/new.html.haml
new file mode 100644
index 00000000000..066ab19fd08
--- /dev/null
+++ b/app/views/admin/teams/members/new.html.haml
@@ -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
diff --git a/app/views/admin/teams/new.html.haml b/app/views/admin/teams/new.html.haml
new file mode 100644
index 00000000000..a40a2c4ebf9
--- /dev/null
+++ b/app/views/admin/teams/new.html.haml
@@ -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
diff --git a/app/views/admin/teams/projects/_form.html.haml b/app/views/admin/teams/projects/_form.html.haml
new file mode 100644
index 00000000000..db4fe85b000
--- /dev/null
+++ b/app/views/admin/teams/projects/_form.html.haml
@@ -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"
diff --git a/app/views/admin/teams/projects/edit.html.haml b/app/views/admin/teams/projects/edit.html.haml
new file mode 100644
index 00000000000..b91a4982b81
--- /dev/null
+++ b/app/views/admin/teams/projects/edit.html.haml
@@ -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'
diff --git a/app/views/admin/teams/projects/new.html.haml b/app/views/admin/teams/projects/new.html.haml
new file mode 100644
index 00000000000..8a0a18a48c0
--- /dev/null
+++ b/app/views/admin/teams/projects/new.html.haml
@@ -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
diff --git a/app/views/admin/teams/show.html.haml b/app/views/admin/teams/show.html.haml
new file mode 100644
index 00000000000..6a1deaff989
--- /dev/null
+++ b/app/views/admin/teams/show.html.haml
@@ -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();
+ })
+ })
+
diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml
index dc50bffda80..f9774669d9a 100644
--- a/app/views/dashboard/_groups.html.haml
+++ b/app/views/dashboard/_groups.html.haml
@@ -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
diff --git a/app/views/dashboard/_sidebar.html.haml b/app/views/dashboard/_sidebar.html.haml
index 9830cdf4f6b..7c6daf6ec31 100644
--- a/app/views/dashboard/_sidebar.html.haml
+++ b/app/views/dashboard/_sidebar.html.haml
@@ -1,3 +1,5 @@
+- if @teams.present?
+ = render "teams", teams: @teams
- if @groups.present?
= render "groups", groups: @groups
= render "projects", projects: @projects
diff --git a/app/views/dashboard/_teams.html.haml b/app/views/dashboard/_teams.html.haml
new file mode 100644
index 00000000000..7912175b760
--- /dev/null
+++ b/app/views/dashboard/_teams.html.haml
@@ -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
diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml
index a60e7febe76..28626b9c682 100644
--- a/app/views/layouts/admin.html.haml
+++ b/app/views/layouts/admin.html.haml
@@ -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
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index f47e8b3e9ff..46bc9ef1457 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -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
diff --git a/app/views/layouts/user_team.html.haml b/app/views/layouts/user_team.html.haml
new file mode 100644
index 00000000000..12fce837238
--- /dev/null
+++ b/app/views/layouts/user_team.html.haml
@@ -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
diff --git a/app/views/projects/_project_head.html.haml b/app/views/projects/_project_head.html.haml
index 94052650694..cc215502859 100644
--- a/app/views/projects/_project_head.html.haml
+++ b/app/views/projects/_project_head.html.haml
@@ -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
diff --git a/app/views/projects/teams/available.html.haml b/app/views/projects/teams/available.html.haml
new file mode 100644
index 00000000000..814e216d6ad
--- /dev/null
+++ b/app/views/projects/teams/available.html.haml
@@ -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"
+
diff --git a/app/views/team_members/_form.html.haml b/app/views/team_members/_form.html.haml
index a963e462a78..f9ee49dbdeb 100644
--- a/app/views/team_members/_form.html.haml
+++ b/app/views/team_members/_form.html.haml
@@ -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"
diff --git a/app/views/team_members/_show.html.haml b/app/views/team_members/_show.html.haml
index 8082f47fca8..52992033805 100644
--- a/app/views/team_members/_show.html.haml
+++ b/app/views/team_members/_show.html.haml
@@ -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
diff --git a/app/views/team_members/_show_team.html.haml b/app/views/team_members/_show_team.html.haml
new file mode 100644
index 00000000000..da0262efda5
--- /dev/null
+++ b/app/views/team_members/_show_team.html.haml
@@ -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
diff --git a/app/views/team_members/_teams.html.haml b/app/views/team_members/_teams.html.haml
new file mode 100644
index 00000000000..156fdd1befa
--- /dev/null
+++ b/app/views/team_members/_teams.html.haml
@@ -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();
+ });
+ })
diff --git a/app/views/team_members/create.js.haml b/app/views/team_members/create.js.haml
index d5ae5d0cc43..b7dff35a269 100644
--- a/app/views/team_members/create.js.haml
+++ b/app/views/team_members/create.js.haml
@@ -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() {
diff --git a/app/views/team_members/import.html.haml b/app/views/team_members/import.html.haml
index de82f416248..135db946041 100644
--- a/app/views/team_members/import.html.haml
+++ b/app/views/team_members/import.html.haml
@@ -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
diff --git a/app/views/team_members/index.html.haml b/app/views/team_members/index.html.haml
index e413c81bb6c..6425302b83b 100644
--- a/app/views/team_members/index.html.haml
+++ b/app/views/team_members/index.html.haml
@@ -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}
diff --git a/app/views/team_members/show.html.haml b/app/views/team_members/show.html.haml
index 4008e8bd23e..a6a7152e92a 100644
--- a/app/views/team_members/show.html.haml
+++ b/app/views/team_members/show.html.haml
@@ -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
diff --git a/app/views/team_members/update.js.haml b/app/views/team_members/update.js.haml
index 6d7f88160de..c68fe9574a2 100644
--- a/app/views/team_members/update.js.haml
+++ b/app/views/team_members/update.js.haml
@@ -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);;
diff --git a/app/views/teams/_filter.html.haml b/app/views/teams/_filter.html.haml
new file mode 100644
index 00000000000..8e358319651
--- /dev/null
+++ b/app/views/teams/_filter.html.haml
@@ -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'
+
diff --git a/app/views/teams/_projects.html.haml b/app/views/teams/_projects.html.haml
new file mode 100644
index 00000000000..95202bc6ee5
--- /dev/null
+++ b/app/views/teams/_projects.html.haml
@@ -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)
diff --git a/app/views/teams/_team_head.html.haml b/app/views/teams/_team_head.html.haml
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/app/views/teams/_team_head.html.haml
diff --git a/app/views/teams/edit.html.haml b/app/views/teams/edit.html.haml
new file mode 100644
index 00000000000..b2ceb2bdb21
--- /dev/null
+++ b/app/views/teams/edit.html.haml
@@ -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"
diff --git a/app/views/teams/index.html.haml b/app/views/teams/index.html.haml
new file mode 100644
index 00000000000..6610cdbd76e
--- /dev/null
+++ b/app/views/teams/index.html.haml
@@ -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;
diff --git a/app/views/teams/issues.html.haml b/app/views/teams/issues.html.haml
new file mode 100644
index 00000000000..3c17e85a115
--- /dev/null
+++ b/app/views/teams/issues.html.haml
@@ -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
diff --git a/app/views/teams/members/_form.html.haml b/app/views/teams/members/_form.html.haml
new file mode 100644
index 00000000000..b75d788a94a
--- /dev/null
+++ b/app/views/teams/members/_form.html.haml
@@ -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"
diff --git a/app/views/teams/members/_show.html.haml b/app/views/teams/members/_show.html.haml
new file mode 100644
index 00000000000..dbbb382d97f
--- /dev/null
+++ b/app/views/teams/members/_show.html.haml
@@ -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
diff --git a/app/views/teams/members/_team.html.haml b/app/views/teams/members/_team.html.haml
new file mode 100644
index 00000000000..d8afc1fa371
--- /dev/null
+++ b/app/views/teams/members/_team.html.haml
@@ -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();
+ });
+ })
diff --git a/app/views/teams/members/edit.html.haml b/app/views/teams/members/edit.html.haml
new file mode 100644
index 00000000000..9caff799552
--- /dev/null
+++ b/app/views/teams/members/edit.html.haml
@@ -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'
diff --git a/app/views/teams/members/index.html.haml b/app/views/teams/members/index.html.haml
new file mode 100644
index 00000000000..1628237ede0
--- /dev/null
+++ b/app/views/teams/members/index.html.haml
@@ -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}
diff --git a/app/views/teams/members/new.html.haml b/app/views/teams/members/new.html.haml
new file mode 100644
index 00000000000..43f7c5d7b6f
--- /dev/null
+++ b/app/views/teams/members/new.html.haml
@@ -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
diff --git a/app/views/teams/members/show.html.haml b/app/views/teams/members/show.html.haml
new file mode 100644
index 00000000000..03ef21be7a6
--- /dev/null
+++ b/app/views/teams/members/show.html.haml
@@ -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();
+ });
+ })
+
diff --git a/app/views/teams/merge_requests.html.haml b/app/views/teams/merge_requests.html.haml
new file mode 100644
index 00000000000..f16331e1cdd
--- /dev/null
+++ b/app/views/teams/merge_requests.html.haml
@@ -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
diff --git a/app/views/teams/new.html.haml b/app/views/teams/new.html.haml
new file mode 100644
index 00000000000..12695f2b5ae
--- /dev/null
+++ b/app/views/teams/new.html.haml
@@ -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
diff --git a/app/views/teams/projects/_form.html.haml b/app/views/teams/projects/_form.html.haml
new file mode 100644
index 00000000000..3749dbc4f99
--- /dev/null
+++ b/app/views/teams/projects/_form.html.haml
@@ -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"
diff --git a/app/views/teams/projects/edit.html.haml b/app/views/teams/projects/edit.html.haml
new file mode 100644
index 00000000000..056ee6852c1
--- /dev/null
+++ b/app/views/teams/projects/edit.html.haml
@@ -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'
diff --git a/app/views/teams/projects/index.html.haml b/app/views/teams/projects/index.html.haml
new file mode 100644
index 00000000000..af6ffe5f3a4
--- /dev/null
+++ b/app/views/teams/projects/index.html.haml
@@ -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"
diff --git a/app/views/teams/projects/new.html.haml b/app/views/teams/projects/new.html.haml
new file mode 100644
index 00000000000..000f62bb643
--- /dev/null
+++ b/app/views/teams/projects/new.html.haml
@@ -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
diff --git a/app/views/teams/search.html.haml b/app/views/teams/search.html.haml
new file mode 100644
index 00000000000..5c357c5cfcb
--- /dev/null
+++ b/app/views/teams/search.html.haml
@@ -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'
diff --git a/app/views/teams/show.html.haml b/app/views/teams/show.html.haml
new file mode 100644
index 00000000000..9acbf3e1ecf
--- /dev/null
+++ b/app/views/teams/show.html.haml
@@ -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); });