Commit aa1f1eb680e4328c2cf619a770f1e90f74c41987
Exists in
master
and in
4 other branches
Merge pull request #2746 from gitlabhq/features/teams
New feature: Teams
Showing
118 changed files
with
2862 additions
and
322 deletions
Show diff stats
Too many changes.
To preserve performance only 100 of 118 files displayed.
app/assets/javascripts/dashboard.js.coffee
| ... | ... | @@ -4,11 +4,11 @@ window.dashboardPage = -> |
| 4 | 4 | event.preventDefault() |
| 5 | 5 | toggleFilter $(this) |
| 6 | 6 | reloadActivities() |
| 7 | - | |
| 7 | + | |
| 8 | 8 | reloadActivities = -> |
| 9 | 9 | $(".content_list").html '' |
| 10 | 10 | Pager.init 20, true |
| 11 | - | |
| 11 | + | |
| 12 | 12 | toggleFilter = (sender) -> |
| 13 | 13 | sender.parent().toggleClass "inactive" |
| 14 | 14 | event_filters = $.cookie("event_filter") |
| ... | ... | @@ -17,11 +17,11 @@ toggleFilter = (sender) -> |
| 17 | 17 | event_filters = event_filters.split(",") |
| 18 | 18 | else |
| 19 | 19 | event_filters = new Array() |
| 20 | - | |
| 20 | + | |
| 21 | 21 | index = event_filters.indexOf(filter) |
| 22 | 22 | if index is -1 |
| 23 | 23 | event_filters.push filter |
| 24 | 24 | else |
| 25 | 25 | event_filters.splice index, 1 |
| 26 | - | |
| 26 | + | |
| 27 | 27 | $.cookie "event_filter", event_filters.join(",") | ... | ... |
app/assets/javascripts/merge_requests.js.coffee
| 1 | 1 | # |
| 2 | 2 | # * Filter merge requests |
| 3 | -# | |
| 3 | +# | |
| 4 | 4 | @merge_requestsPage = -> |
| 5 | 5 | $('#assignee_id').chosen() |
| 6 | 6 | $('#milestone_id').chosen() |
| ... | ... | @@ -8,16 +8,16 @@ |
| 8 | 8 | $(this).closest('form').submit() |
| 9 | 9 | |
| 10 | 10 | class MergeRequest |
| 11 | - | |
| 11 | + | |
| 12 | 12 | constructor: (@opts) -> |
| 13 | 13 | this.$el = $('.merge-request') |
| 14 | 14 | @diffs_loaded = false |
| 15 | 15 | @commits_loaded = false |
| 16 | - | |
| 16 | + | |
| 17 | 17 | this.activateTab(@opts.action) |
| 18 | - | |
| 18 | + | |
| 19 | 19 | this.bindEvents() |
| 20 | - | |
| 20 | + | |
| 21 | 21 | this.initMergeWidget() |
| 22 | 22 | this.$('.show-all-commits').on 'click', => |
| 23 | 23 | this.showAllCommits() |
| ... | ... | @@ -28,7 +28,7 @@ class MergeRequest |
| 28 | 28 | |
| 29 | 29 | initMergeWidget: -> |
| 30 | 30 | this.showState( @opts.current_state ) |
| 31 | - | |
| 31 | + | |
| 32 | 32 | if this.$('.automerge_widget').length and @opts.check_enable |
| 33 | 33 | $.get @opts.url_to_automerge_check, (data) => |
| 34 | 34 | this.showState( data.state ) |
| ... | ... | @@ -42,12 +42,12 @@ class MergeRequest |
| 42 | 42 | bindEvents: -> |
| 43 | 43 | this.$('.nav-tabs').on 'click', 'a', (event) => |
| 44 | 44 | a = $(event.currentTarget) |
| 45 | - | |
| 45 | + | |
| 46 | 46 | href = a.attr('href') |
| 47 | 47 | History.replaceState {path: href}, document.title, href |
| 48 | - | |
| 48 | + | |
| 49 | 49 | event.preventDefault() |
| 50 | - | |
| 50 | + | |
| 51 | 51 | this.$('.nav-tabs').on 'click', 'li', (event) => |
| 52 | 52 | this.activateTab($(event.currentTarget).data('action')) |
| 53 | 53 | ... | ... |
app/assets/stylesheets/sections/projects.scss
| ... | ... | @@ -0,0 +1,11 @@ |
| 1 | +# Provides a base class for Admin controllers to subclass | |
| 2 | +# | |
| 3 | +# Automatically sets the layout and ensures an administrator is logged in | |
| 4 | +class Admin::ApplicationController < ApplicationController | |
| 5 | + layout 'admin' | |
| 6 | + before_filter :authenticate_admin! | |
| 7 | + | |
| 8 | + def authenticate_admin! | |
| 9 | + return render_404 unless current_user.is_admin? | |
| 10 | + end | |
| 11 | +end | ... | ... |
app/controllers/admin/dashboard_controller.rb
app/controllers/admin/groups_controller.rb
app/controllers/admin/hooks_controller.rb
app/controllers/admin/logs_controller.rb
app/controllers/admin/projects/application_controller.rb
0 → 100644
| ... | ... | @@ -0,0 +1,11 @@ |
| 1 | +# Provides a base class for Admin controllers to subclass | |
| 2 | +# | |
| 3 | +# Automatically sets the layout and ensures an administrator is logged in | |
| 4 | +class Admin::Projects::ApplicationController < Admin::ApplicationController | |
| 5 | + | |
| 6 | + protected | |
| 7 | + | |
| 8 | + def project | |
| 9 | + @project ||= Project.find_by_path(params[:project_id]) | |
| 10 | + end | |
| 11 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,32 @@ |
| 1 | +class Admin::Projects::MembersController < Admin::Projects::ApplicationController | |
| 2 | + def edit | |
| 3 | + @member = team_member | |
| 4 | + @project = project | |
| 5 | + @team_member_relation = team_member_relation | |
| 6 | + end | |
| 7 | + | |
| 8 | + def update | |
| 9 | + if team_member_relation.update_attributes(params[:team_member]) | |
| 10 | + redirect_to [:admin, project], notice: 'Project Access was successfully updated.' | |
| 11 | + else | |
| 12 | + render action: "edit" | |
| 13 | + end | |
| 14 | + end | |
| 15 | + | |
| 16 | + def destroy | |
| 17 | + team_member_relation.destroy | |
| 18 | + | |
| 19 | + redirect_to :back | |
| 20 | + end | |
| 21 | + | |
| 22 | + private | |
| 23 | + | |
| 24 | + def team_member | |
| 25 | + @member ||= project.users.find(params[:id]) | |
| 26 | + end | |
| 27 | + | |
| 28 | + def team_member_relation | |
| 29 | + team_member.users_projects.find_by_project_id(project) | |
| 30 | + end | |
| 31 | + | |
| 32 | +end | ... | ... |
app/controllers/admin/projects_controller.rb
| 1 | -class Admin::ProjectsController < AdminController | |
| 1 | +class Admin::ProjectsController < Admin::ApplicationController | |
| 2 | 2 | before_filter :project, only: [:edit, :show, :update, :destroy, :team_update] |
| 3 | 3 | |
| 4 | 4 | def index |
| ... | ... | @@ -29,7 +29,9 @@ class Admin::ProjectsController < AdminController |
| 29 | 29 | end |
| 30 | 30 | |
| 31 | 31 | def update |
| 32 | - status = Projects::UpdateContext.new(project, current_user, params).execute(:admin) | |
| 32 | + project.creator = current_user unless project.creator | |
| 33 | + | |
| 34 | + status = ::Projects::UpdateContext.new(project, current_user, params).execute(:admin) | |
| 33 | 35 | |
| 34 | 36 | if status |
| 35 | 37 | redirect_to [:admin, @project], notice: 'Project was successfully updated.' | ... | ... |
app/controllers/admin/resque_controller.rb
app/controllers/admin/team_members_controller.rb
| ... | ... | @@ -1,22 +0,0 @@ |
| 1 | -class Admin::TeamMembersController < AdminController | |
| 2 | - def edit | |
| 3 | - @admin_team_member = UsersProject.find(params[:id]) | |
| 4 | - end | |
| 5 | - | |
| 6 | - def update | |
| 7 | - @admin_team_member = UsersProject.find(params[:id]) | |
| 8 | - | |
| 9 | - if @admin_team_member.update_attributes(params[:team_member]) | |
| 10 | - redirect_to [:admin, @admin_team_member.project], notice: 'Project Access was successfully updated.' | |
| 11 | - else | |
| 12 | - render action: "edit" | |
| 13 | - end | |
| 14 | - end | |
| 15 | - | |
| 16 | - def destroy | |
| 17 | - @admin_team_member = UsersProject.find(params[:id]) | |
| 18 | - @admin_team_member.destroy | |
| 19 | - | |
| 20 | - redirect_to :back | |
| 21 | - end | |
| 22 | -end |
| ... | ... | @@ -0,0 +1,11 @@ |
| 1 | +# Provides a base class for Admin controllers to subclass | |
| 2 | +# | |
| 3 | +# Automatically sets the layout and ensures an administrator is logged in | |
| 4 | +class Admin::Teams::ApplicationController < Admin::ApplicationController | |
| 5 | + | |
| 6 | + private | |
| 7 | + | |
| 8 | + def user_team | |
| 9 | + @team = UserTeam.find_by_path(params[:team_id]) | |
| 10 | + end | |
| 11 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,41 @@ |
| 1 | +class Admin::Teams::MembersController < Admin::Teams::ApplicationController | |
| 2 | + def new | |
| 3 | + @users = User.potential_team_members(user_team) | |
| 4 | + @users = UserDecorator.decorate @users | |
| 5 | + end | |
| 6 | + | |
| 7 | + def create | |
| 8 | + unless params[:user_ids].blank? | |
| 9 | + user_ids = params[:user_ids] | |
| 10 | + access = params[:default_project_access] | |
| 11 | + is_admin = params[:group_admin] | |
| 12 | + user_team.add_members(user_ids, access, is_admin) | |
| 13 | + end | |
| 14 | + | |
| 15 | + redirect_to admin_team_path(user_team), notice: 'Members was successfully added into Team of users.' | |
| 16 | + end | |
| 17 | + | |
| 18 | + def edit | |
| 19 | + team_member | |
| 20 | + end | |
| 21 | + | |
| 22 | + def update | |
| 23 | + options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]} | |
| 24 | + if user_team.update_membership(team_member, options) | |
| 25 | + redirect_to admin_team_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users." | |
| 26 | + else | |
| 27 | + render :edit | |
| 28 | + end | |
| 29 | + end | |
| 30 | + | |
| 31 | + def destroy | |
| 32 | + user_team.remove_member(team_member) | |
| 33 | + redirect_to admin_team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users." | |
| 34 | + end | |
| 35 | + | |
| 36 | + protected | |
| 37 | + | |
| 38 | + def team_member | |
| 39 | + @member ||= user_team.members.find(params[:id]) | |
| 40 | + end | |
| 41 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,41 @@ |
| 1 | +class Admin::Teams::ProjectsController < Admin::Teams::ApplicationController | |
| 2 | + def new | |
| 3 | + @projects = Project.scoped | |
| 4 | + @projects = @projects.without_team(user_team) if user_team.projects.any? | |
| 5 | + #@projects.reject!(&:empty_repo?) | |
| 6 | + end | |
| 7 | + | |
| 8 | + def create | |
| 9 | + unless params[:project_ids].blank? | |
| 10 | + project_ids = params[:project_ids] | |
| 11 | + access = params[:greatest_project_access] | |
| 12 | + user_team.assign_to_projects(project_ids, access) | |
| 13 | + end | |
| 14 | + | |
| 15 | + redirect_to admin_team_path(user_team), notice: 'Team of users was successfully assgned to projects.' | |
| 16 | + end | |
| 17 | + | |
| 18 | + def edit | |
| 19 | + team_project | |
| 20 | + end | |
| 21 | + | |
| 22 | + def update | |
| 23 | + if user_team.update_project_access(team_project, params[:greatest_project_access]) | |
| 24 | + redirect_to admin_team_path(user_team), notice: 'Access was successfully updated.' | |
| 25 | + else | |
| 26 | + render :edit | |
| 27 | + end | |
| 28 | + end | |
| 29 | + | |
| 30 | + def destroy | |
| 31 | + user_team.resign_from_project(team_project) | |
| 32 | + redirect_to admin_team_path(user_team), notice: 'Team of users was successfully reassigned from project.' | |
| 33 | + end | |
| 34 | + | |
| 35 | + protected | |
| 36 | + | |
| 37 | + def team_project | |
| 38 | + @project ||= user_team.projects.find_with_namespace(params[:id]) | |
| 39 | + end | |
| 40 | + | |
| 41 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,59 @@ |
| 1 | +class Admin::TeamsController < Admin::ApplicationController | |
| 2 | + def index | |
| 3 | + @teams = UserTeam.order('name ASC') | |
| 4 | + @teams = @teams.search(params[:name]) if params[:name].present? | |
| 5 | + @teams = @teams.page(params[:page]).per(20) | |
| 6 | + end | |
| 7 | + | |
| 8 | + def show | |
| 9 | + user_team | |
| 10 | + end | |
| 11 | + | |
| 12 | + def new | |
| 13 | + @team = UserTeam.new | |
| 14 | + end | |
| 15 | + | |
| 16 | + def edit | |
| 17 | + user_team | |
| 18 | + end | |
| 19 | + | |
| 20 | + def create | |
| 21 | + @team = UserTeam.new(params[:user_team]) | |
| 22 | + @team.path = @team.name.dup.parameterize if @team.name | |
| 23 | + @team.owner = current_user | |
| 24 | + | |
| 25 | + if @team.save | |
| 26 | + redirect_to admin_team_path(@team), notice: 'Team of users was successfully created.' | |
| 27 | + else | |
| 28 | + render action: "new" | |
| 29 | + end | |
| 30 | + end | |
| 31 | + | |
| 32 | + def update | |
| 33 | + user_team_params = params[:user_team].dup | |
| 34 | + owner_id = user_team_params.delete(:owner_id) | |
| 35 | + | |
| 36 | + if owner_id | |
| 37 | + user_team.owner = User.find(owner_id) | |
| 38 | + end | |
| 39 | + | |
| 40 | + if user_team.update_attributes(user_team_params) | |
| 41 | + redirect_to admin_team_path(user_team), notice: 'Team of users was successfully updated.' | |
| 42 | + else | |
| 43 | + render action: "edit" | |
| 44 | + end | |
| 45 | + end | |
| 46 | + | |
| 47 | + def destroy | |
| 48 | + user_team.destroy | |
| 49 | + | |
| 50 | + redirect_to admin_user_teams_path, notice: 'Team of users was successfully deleted.' | |
| 51 | + end | |
| 52 | + | |
| 53 | + protected | |
| 54 | + | |
| 55 | + def user_team | |
| 56 | + @team ||= UserTeam.find_by_path(params[:id]) | |
| 57 | + end | |
| 58 | + | |
| 59 | +end | ... | ... |
app/controllers/admin/users_controller.rb
app/controllers/admin_controller.rb
| ... | ... | @@ -1,11 +0,0 @@ |
| 1 | -# Provides a base class for Admin controllers to subclass | |
| 2 | -# | |
| 3 | -# Automatically sets the layout and ensures an administrator is logged in | |
| 4 | -class AdminController < ApplicationController | |
| 5 | - layout 'admin' | |
| 6 | - before_filter :authenticate_admin! | |
| 7 | - | |
| 8 | - def authenticate_admin! | |
| 9 | - return render_404 unless current_user.is_admin? | |
| 10 | - end | |
| 11 | -end |
app/controllers/application_controller.rb
| ... | ... | @@ -94,6 +94,14 @@ class ApplicationController < ActionController::Base |
| 94 | 94 | return access_denied! unless can?(current_user, :download_code, project) |
| 95 | 95 | end |
| 96 | 96 | |
| 97 | + def authorize_manage_user_team! | |
| 98 | + return access_denied! unless user_team.present? && can?(current_user, :manage_user_team, user_team) | |
| 99 | + end | |
| 100 | + | |
| 101 | + def authorize_admin_user_team! | |
| 102 | + return access_denied! unless user_team.present? && can?(current_user, :admin_user_team, user_team) | |
| 103 | + end | |
| 104 | + | |
| 97 | 105 | def access_denied! |
| 98 | 106 | render "errors/access_denied", layout: "errors", status: 404 |
| 99 | 107 | end |
| ... | ... | @@ -135,4 +143,5 @@ class ApplicationController < ActionController::Base |
| 135 | 143 | def dev_tools |
| 136 | 144 | Rack::MiniProfiler.authorize_request |
| 137 | 145 | end |
| 146 | + | |
| 138 | 147 | end | ... | ... |
app/controllers/dashboard_controller.rb
| ... | ... | @@ -18,6 +18,8 @@ class DashboardController < ApplicationController |
| 18 | 18 | @projects |
| 19 | 19 | end |
| 20 | 20 | |
| 21 | + @teams = (UserTeam.with_member(current_user) + UserTeam.created_by(current_user)).uniq | |
| 22 | + | |
| 21 | 23 | @projects = @projects.page(params[:page]).per(30) |
| 22 | 24 | |
| 23 | 25 | @events = Event.in_projects(current_user.authorized_projects.pluck(:id)) | ... | ... |
| ... | ... | @@ -0,0 +1,27 @@ |
| 1 | +class Projects::TeamsController < Projects::ApplicationController | |
| 2 | + | |
| 3 | + def available | |
| 4 | + @teams = current_user.is_admin? ? UserTeam.scoped : current_user.user_teams | |
| 5 | + @teams = @teams.without_project(project) | |
| 6 | + unless @teams.any? | |
| 7 | + redirect_to project_team_index_path(project), notice: "No avaliable teams for assigment." | |
| 8 | + end | |
| 9 | + end | |
| 10 | + | |
| 11 | + def assign | |
| 12 | + unless params[:team_id].blank? | |
| 13 | + team = UserTeam.find(params[:team_id]) | |
| 14 | + access = params[:greatest_project_access] | |
| 15 | + team.assign_to_project(project, access) | |
| 16 | + end | |
| 17 | + redirect_to project_team_index_path(project) | |
| 18 | + end | |
| 19 | + | |
| 20 | + def resign | |
| 21 | + team = project.user_teams.find_by_path(params[:id]) | |
| 22 | + team.resign_from_project(project) | |
| 23 | + | |
| 24 | + redirect_to project_team_index_path(project) | |
| 25 | + end | |
| 26 | + | |
| 27 | +end | ... | ... |
app/controllers/projects_controller.rb
| ... | ... | @@ -19,7 +19,7 @@ class ProjectsController < ProjectResourceController |
| 19 | 19 | end |
| 20 | 20 | |
| 21 | 21 | def create |
| 22 | - @project = Projects::CreateContext.new(current_user, params[:project]).execute | |
| 22 | + @project = ::Projects::CreateContext.new(current_user, params[:project]).execute | |
| 23 | 23 | |
| 24 | 24 | respond_to do |format| |
| 25 | 25 | flash[:notice] = 'Project was successfully created.' if @project.saved? |
| ... | ... | @@ -35,7 +35,7 @@ class ProjectsController < ProjectResourceController |
| 35 | 35 | end |
| 36 | 36 | |
| 37 | 37 | def update |
| 38 | - status = Projects::UpdateContext.new(project, current_user, params).execute | |
| 38 | + status = ::Projects::UpdateContext.new(project, current_user, params).execute | |
| 39 | 39 | |
| 40 | 40 | respond_to do |format| |
| 41 | 41 | if status | ... | ... |
app/controllers/team_members_controller.rb
| ... | ... | @@ -4,15 +4,16 @@ class TeamMembersController < ProjectResourceController |
| 4 | 4 | before_filter :authorize_admin_project!, except: [:index, :show] |
| 5 | 5 | |
| 6 | 6 | def index |
| 7 | + @teams = UserTeam.scoped | |
| 7 | 8 | end |
| 8 | 9 | |
| 9 | 10 | def show |
| 10 | - @team_member = project.users_projects.find(params[:id]) | |
| 11 | - @events = @team_member.user.recent_events.where(:project_id => @project.id).limit(7) | |
| 11 | + @user_project_relation = project.users_projects.find_by_user_id(member) | |
| 12 | + @events = member.recent_events.in_projects(project).limit(7) | |
| 12 | 13 | end |
| 13 | 14 | |
| 14 | 15 | def new |
| 15 | - @team_member = project.users_projects.new | |
| 16 | + @user_project_relation = project.users_projects.new | |
| 16 | 17 | end |
| 17 | 18 | |
| 18 | 19 | def create |
| ... | ... | @@ -28,18 +29,18 @@ class TeamMembersController < ProjectResourceController |
| 28 | 29 | end |
| 29 | 30 | |
| 30 | 31 | def update |
| 31 | - @team_member = project.users_projects.find(params[:id]) | |
| 32 | - @team_member.update_attributes(params[:team_member]) | |
| 32 | + @user_project_relation = project.users_projects.find_by_user_id(member) | |
| 33 | + @user_project_relation.update_attributes(params[:team_member]) | |
| 33 | 34 | |
| 34 | - unless @team_member.valid? | |
| 35 | + unless @user_project_relation.valid? | |
| 35 | 36 | flash[:alert] = "User should have at least one role" |
| 36 | 37 | end |
| 37 | 38 | redirect_to project_team_index_path(@project) |
| 38 | 39 | end |
| 39 | 40 | |
| 40 | 41 | def destroy |
| 41 | - @team_member = project.users_projects.find(params[:id]) | |
| 42 | - @team_member.destroy | |
| 42 | + @user_project_relation = project.users_projects.find_by_user_id(params[:id]) | |
| 43 | + @user_project_relation.destroy | |
| 43 | 44 | |
| 44 | 45 | respond_to do |format| |
| 45 | 46 | format.html { redirect_to project_team_index_path(@project) } |
| ... | ... | @@ -54,4 +55,10 @@ class TeamMembersController < ProjectResourceController |
| 54 | 55 | |
| 55 | 56 | redirect_to project_team_members_path(project), notice: notice |
| 56 | 57 | end |
| 58 | + | |
| 59 | + protected | |
| 60 | + | |
| 61 | + def member | |
| 62 | + @member ||= User.find(params[:id]) | |
| 63 | + end | |
| 57 | 64 | end | ... | ... |
| ... | ... | @@ -0,0 +1,49 @@ |
| 1 | +class Teams::MembersController < Teams::ApplicationController | |
| 2 | + | |
| 3 | + skip_before_filter :authorize_manage_user_team!, only: [:index] | |
| 4 | + | |
| 5 | + def index | |
| 6 | + @members = user_team.members | |
| 7 | + end | |
| 8 | + | |
| 9 | + def new | |
| 10 | + @users = User.potential_team_members(user_team) | |
| 11 | + @users = UserDecorator.decorate @users | |
| 12 | + end | |
| 13 | + | |
| 14 | + def create | |
| 15 | + unless params[:user_ids].blank? | |
| 16 | + user_ids = params[:user_ids] | |
| 17 | + access = params[:default_project_access] | |
| 18 | + is_admin = params[:group_admin] | |
| 19 | + user_team.add_members(user_ids, access, is_admin) | |
| 20 | + end | |
| 21 | + | |
| 22 | + redirect_to team_members_path(user_team), notice: 'Members was successfully added into Team of users.' | |
| 23 | + end | |
| 24 | + | |
| 25 | + def edit | |
| 26 | + team_member | |
| 27 | + end | |
| 28 | + | |
| 29 | + def update | |
| 30 | + options = {default_projects_access: params[:default_project_access], group_admin: params[:group_admin]} | |
| 31 | + if user_team.update_membership(team_member, options) | |
| 32 | + redirect_to team_members_path(user_team), notice: "Membership for #{team_member.name} was successfully updated in Team of users." | |
| 33 | + else | |
| 34 | + render :edit | |
| 35 | + end | |
| 36 | + end | |
| 37 | + | |
| 38 | + def destroy | |
| 39 | + user_team.remove_member(team_member) | |
| 40 | + redirect_to team_path(user_team), notice: "Member #{team_member.name} was successfully removed from Team of users." | |
| 41 | + end | |
| 42 | + | |
| 43 | + protected | |
| 44 | + | |
| 45 | + def team_member | |
| 46 | + @member ||= user_team.members.find(params[:id]) | |
| 47 | + end | |
| 48 | + | |
| 49 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,57 @@ |
| 1 | +class Teams::ProjectsController < Teams::ApplicationController | |
| 2 | + | |
| 3 | + skip_before_filter :authorize_manage_user_team!, only: [:index] | |
| 4 | + | |
| 5 | + def index | |
| 6 | + @projects = user_team.projects | |
| 7 | + @avaliable_projects = current_user.admin? ? Project.without_team(user_team) : current_user.owned_projects.without_team(user_team) | |
| 8 | + end | |
| 9 | + | |
| 10 | + def new | |
| 11 | + user_team | |
| 12 | + @avaliable_projects = current_user.owned_projects.scoped | |
| 13 | + @avaliable_projects = @avaliable_projects.without_team(user_team) if user_team.projects.any? | |
| 14 | + | |
| 15 | + redirect_to team_projects_path(user_team), notice: "No avalible projects." unless @avaliable_projects.any? | |
| 16 | + end | |
| 17 | + | |
| 18 | + def create | |
| 19 | + redirect_to :back if params[:project_ids].blank? | |
| 20 | + | |
| 21 | + project_ids = params[:project_ids] | |
| 22 | + access = params[:greatest_project_access] | |
| 23 | + | |
| 24 | + # Reject non-allowed projects | |
| 25 | + allowed_project_ids = current_user.owned_projects.map(&:id) | |
| 26 | + project_ids.select! { |id| allowed_project_ids.include?(id) } | |
| 27 | + | |
| 28 | + # Assign projects to team | |
| 29 | + user_team.assign_to_projects(project_ids, access) | |
| 30 | + | |
| 31 | + redirect_to team_projects_path(user_team), notice: 'Team of users was successfully assigned to projects.' | |
| 32 | + end | |
| 33 | + | |
| 34 | + def edit | |
| 35 | + team_project | |
| 36 | + end | |
| 37 | + | |
| 38 | + def update | |
| 39 | + if user_team.update_project_access(team_project, params[:greatest_project_access]) | |
| 40 | + redirect_to team_projects_path(user_team), notice: 'Access was successfully updated.' | |
| 41 | + else | |
| 42 | + render :edit | |
| 43 | + end | |
| 44 | + end | |
| 45 | + | |
| 46 | + def destroy | |
| 47 | + user_team.resign_from_project(team_project) | |
| 48 | + redirect_to team_projects_path(user_team), notice: 'Team of users was successfully reassigned from project.' | |
| 49 | + end | |
| 50 | + | |
| 51 | + private | |
| 52 | + | |
| 53 | + def team_project | |
| 54 | + @project ||= user_team.projects.find_with_namespace(params[:id]) | |
| 55 | + end | |
| 56 | + | |
| 57 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,92 @@ |
| 1 | +class TeamsController < ApplicationController | |
| 2 | + # Authorize | |
| 3 | + before_filter :authorize_manage_user_team! | |
| 4 | + before_filter :authorize_admin_user_team! | |
| 5 | + | |
| 6 | + # Skip access control on public section | |
| 7 | + skip_before_filter :authorize_manage_user_team!, only: [:index, :show, :new, :destroy, :create, :search, :issues, :merge_requests] | |
| 8 | + skip_before_filter :authorize_admin_user_team!, only: [:index, :show, :new, :create, :search, :issues, :merge_requests] | |
| 9 | + | |
| 10 | + layout 'user_team', only: [:show, :edit, :update, :destroy, :issues, :merge_requests, :search] | |
| 11 | + | |
| 12 | + def index | |
| 13 | + @teams = current_user.user_teams.order('name ASC') | |
| 14 | + end | |
| 15 | + | |
| 16 | + def show | |
| 17 | + user_team | |
| 18 | + projects | |
| 19 | + @events = Event.in_projects(user_team.project_ids).limit(20).offset(params[:offset] || 0) | |
| 20 | + end | |
| 21 | + | |
| 22 | + def edit | |
| 23 | + user_team | |
| 24 | + end | |
| 25 | + | |
| 26 | + def update | |
| 27 | + if user_team.update_attributes(params[:user_team]) | |
| 28 | + redirect_to team_path(user_team) | |
| 29 | + else | |
| 30 | + render action: :edit | |
| 31 | + end | |
| 32 | + end | |
| 33 | + | |
| 34 | + def destroy | |
| 35 | + user_team.destroy | |
| 36 | + redirect_to teams_path | |
| 37 | + end | |
| 38 | + | |
| 39 | + def new | |
| 40 | + @team = UserTeam.new | |
| 41 | + end | |
| 42 | + | |
| 43 | + def create | |
| 44 | + @team = UserTeam.new(params[:user_team]) | |
| 45 | + @team.owner = current_user unless params[:owner] | |
| 46 | + @team.path = @team.name.dup.parameterize if @team.name | |
| 47 | + | |
| 48 | + if @team.save | |
| 49 | + redirect_to team_path(@team) | |
| 50 | + else | |
| 51 | + render action: :new | |
| 52 | + end | |
| 53 | + end | |
| 54 | + | |
| 55 | + # Get authored or assigned open merge requests | |
| 56 | + def merge_requests | |
| 57 | + projects | |
| 58 | + @merge_requests = MergeRequest.of_user_team(user_team) | |
| 59 | + @merge_requests = FilterContext.new(@merge_requests, params).execute | |
| 60 | + @merge_requests = @merge_requests.recent.page(params[:page]).per(20) | |
| 61 | + end | |
| 62 | + | |
| 63 | + # Get only assigned issues | |
| 64 | + def issues | |
| 65 | + projects | |
| 66 | + @issues = Issue.of_user_team(user_team) | |
| 67 | + @issues = FilterContext.new(@issues, params).execute | |
| 68 | + @issues = @issues.recent.page(params[:page]).per(20) | |
| 69 | + @issues = @issues.includes(:author, :project) | |
| 70 | + end | |
| 71 | + | |
| 72 | + def search | |
| 73 | + result = SearchContext.new(user_team.project_ids, params).execute | |
| 74 | + | |
| 75 | + @projects = result[:projects] | |
| 76 | + @merge_requests = result[:merge_requests] | |
| 77 | + @issues = result[:issues] | |
| 78 | + @wiki_pages = result[:wiki_pages] | |
| 79 | + @teams = result[:teams] | |
| 80 | + end | |
| 81 | + | |
| 82 | + protected | |
| 83 | + | |
| 84 | + def projects | |
| 85 | + @projects ||= user_team.projects.sorted_by_activity | |
| 86 | + end | |
| 87 | + | |
| 88 | + def user_team | |
| 89 | + @team ||= UserTeam.find_by_path(params[:id]) | |
| 90 | + end | |
| 91 | + | |
| 92 | +end | ... | ... |
app/decorators/user_decorator.rb
app/helpers/projects_helper.rb
| ... | ... | @@ -3,8 +3,12 @@ module ProjectsHelper |
| 3 | 3 | @project.users_projects.sort_by(&:project_access).reverse.group_by(&:project_access) |
| 4 | 4 | end |
| 5 | 5 | |
| 6 | - def remove_from_team_message(project, member) | |
| 7 | - "You are going to remove #{member.user_name} from #{project.name}. Are you sure?" | |
| 6 | + def grouper_project_teams(project) | |
| 7 | + @project.user_team_project_relationships.sort_by(&:greatest_access).reverse.group_by(&:greatest_access) | |
| 8 | + end | |
| 9 | + | |
| 10 | + def remove_from_project_team_message(project, user) | |
| 11 | + "You are going to remove #{user.name} from #{project.name} project team. Are you sure?" | |
| 8 | 12 | end |
| 9 | 13 | |
| 10 | 14 | def link_to_project project | ... | ... |
| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | +module UserTeamsHelper | |
| 2 | + def team_filter_path(entity, options={}) | |
| 3 | + exist_opts = { | |
| 4 | + status: params[:status], | |
| 5 | + project_id: params[:project_id], | |
| 6 | + } | |
| 7 | + | |
| 8 | + options = exist_opts.merge(options) | |
| 9 | + | |
| 10 | + case entity | |
| 11 | + when 'issue' then | |
| 12 | + issues_team_path(@team, options) | |
| 13 | + when 'merge_request' | |
| 14 | + merge_requests_team_path(@team, options) | |
| 15 | + end | |
| 16 | + end | |
| 17 | + | |
| 18 | + def grouped_user_team_members(team) | |
| 19 | + team.user_team_user_relationships.sort_by(&:permission).reverse.group_by(&:permission) | |
| 20 | + end | |
| 21 | + | |
| 22 | + def remove_from_user_team_message(team, member) | |
| 23 | + "You are going to remove #{member.name} from #{team.name}. Are you sure?" | |
| 24 | + end | |
| 25 | + | |
| 26 | +end | ... | ... |
app/models/ability.rb
| ... | ... | @@ -8,6 +8,7 @@ class Ability |
| 8 | 8 | when "Snippet" then snippet_abilities(object, subject) |
| 9 | 9 | when "MergeRequest" then merge_request_abilities(object, subject) |
| 10 | 10 | when "Group", "Namespace" then group_abilities(object, subject) |
| 11 | + when "UserTeam" then user_team_abilities(object, subject) | |
| 11 | 12 | else [] |
| 12 | 13 | end |
| 13 | 14 | end |
| ... | ... | @@ -110,6 +111,22 @@ class Ability |
| 110 | 111 | rules.flatten |
| 111 | 112 | end |
| 112 | 113 | |
| 114 | + def user_team_abilities user, team | |
| 115 | + rules = [] | |
| 116 | + | |
| 117 | + # Only group owner and administrators can manage group | |
| 118 | + if team.owner == user || team.admin?(user) || user.admin? | |
| 119 | + rules << [ :manage_user_team ] | |
| 120 | + end | |
| 121 | + | |
| 122 | + if team.owner == user || user.admin? | |
| 123 | + rules << [ :admin_user_team ] | |
| 124 | + end | |
| 125 | + | |
| 126 | + rules.flatten | |
| 127 | + end | |
| 128 | + | |
| 129 | + | |
| 113 | 130 | [:issue, :note, :snippet, :merge_request].each do |name| |
| 114 | 131 | define_method "#{name}_abilities" do |user, subject| |
| 115 | 132 | if subject.author == user | ... | ... |
app/models/concerns/issuable.rb
| ... | ... | @@ -22,6 +22,7 @@ module Issuable |
| 22 | 22 | scope :opened, where(closed: false) |
| 23 | 23 | scope :closed, where(closed: true) |
| 24 | 24 | scope :of_group, ->(group) { where(project_id: group.project_ids) } |
| 25 | + scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } | |
| 25 | 26 | scope :assigned, ->(u) { where(assignee_id: u.id)} |
| 26 | 27 | scope :recent, order("created_at DESC") |
| 27 | 28 | ... | ... |
app/models/project.rb
| ... | ... | @@ -33,28 +33,31 @@ class Project < ActiveRecord::Base |
| 33 | 33 | attr_accessor :error_code |
| 34 | 34 | |
| 35 | 35 | # Relations |
| 36 | - belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" | |
| 36 | + belongs_to :creator, foreign_key: "creator_id", class_name: "User" | |
| 37 | + belongs_to :group, foreign_key: "namespace_id", conditions: "type = 'Group'" | |
| 37 | 38 | belongs_to :namespace |
| 38 | 39 | |
| 39 | - belongs_to :creator, | |
| 40 | - class_name: "User", | |
| 41 | - foreign_key: "creator_id" | |
| 42 | - | |
| 43 | - has_many :users, through: :users_projects | |
| 44 | - has_many :events, dependent: :destroy | |
| 45 | - has_many :merge_requests, dependent: :destroy | |
| 46 | - has_many :issues, dependent: :destroy, order: "closed, created_at DESC" | |
| 47 | - has_many :milestones, dependent: :destroy | |
| 48 | - has_many :users_projects, dependent: :destroy | |
| 49 | - has_many :notes, dependent: :destroy | |
| 50 | - has_many :snippets, dependent: :destroy | |
| 51 | - has_many :deploy_keys, dependent: :destroy, foreign_key: "project_id", class_name: "Key" | |
| 52 | - has_many :hooks, dependent: :destroy, class_name: "ProjectHook" | |
| 53 | - has_many :wikis, dependent: :destroy | |
| 54 | - has_many :protected_branches, dependent: :destroy | |
| 55 | 40 | has_one :last_event, class_name: 'Event', order: 'events.created_at DESC', foreign_key: 'project_id' |
| 56 | 41 | has_one :gitlab_ci_service, dependent: :destroy |
| 57 | 42 | |
| 43 | + has_many :events, dependent: :destroy | |
| 44 | + has_many :merge_requests, dependent: :destroy | |
| 45 | + has_many :issues, dependent: :destroy, order: "closed, created_at DESC" | |
| 46 | + has_many :milestones, dependent: :destroy | |
| 47 | + has_many :users_projects, dependent: :destroy | |
| 48 | + has_many :notes, dependent: :destroy | |
| 49 | + has_many :snippets, dependent: :destroy | |
| 50 | + has_many :deploy_keys, dependent: :destroy, class_name: "Key", foreign_key: "project_id" | |
| 51 | + has_many :hooks, dependent: :destroy, class_name: "ProjectHook" | |
| 52 | + has_many :wikis, dependent: :destroy | |
| 53 | + has_many :protected_branches, dependent: :destroy | |
| 54 | + has_many :user_team_project_relationships, dependent: :destroy | |
| 55 | + | |
| 56 | + has_many :users, through: :users_projects | |
| 57 | + has_many :user_teams, through: :user_team_project_relationships | |
| 58 | + has_many :user_team_user_relationships, through: :user_teams | |
| 59 | + has_many :user_teams_members, through: :user_team_user_relationships | |
| 60 | + | |
| 58 | 61 | delegate :name, to: :owner, allow_nil: true, prefix: true |
| 59 | 62 | |
| 60 | 63 | # Validations |
| ... | ... | @@ -77,6 +80,8 @@ class Project < ActiveRecord::Base |
| 77 | 80 | # Scopes |
| 78 | 81 | scope :without_user, ->(user) { where("id NOT IN (:ids)", ids: user.authorized_projects.map(&:id) ) } |
| 79 | 82 | scope :not_in_group, ->(group) { where("id NOT IN (:ids)", ids: group.project_ids ) } |
| 83 | + scope :without_team, ->(team) { where("id NOT IN (:ids)", ids: team.projects.map(&:id)) } | |
| 84 | + scope :in_team, ->(team) { where("id IN (:ids)", ids: team.projects.map(&:id)) } | |
| 80 | 85 | scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } |
| 81 | 86 | scope :sorted_by_activity, ->() { order("(SELECT max(events.created_at) FROM events WHERE events.project_id = projects.id) DESC") } |
| 82 | 87 | scope :personal, ->(user) { where(namespace_id: user.namespace_id) } |
| ... | ... | @@ -122,7 +127,7 @@ class Project < ActiveRecord::Base |
| 122 | 127 | end |
| 123 | 128 | |
| 124 | 129 | def team |
| 125 | - @team ||= Team.new(self) | |
| 130 | + @team ||= ProjectTeam.new(self) | |
| 126 | 131 | end |
| 127 | 132 | |
| 128 | 133 | def repository |
| ... | ... | @@ -489,6 +494,11 @@ class Project < ActiveRecord::Base |
| 489 | 494 | http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') |
| 490 | 495 | end |
| 491 | 496 | |
| 497 | + def project_access_human(member) | |
| 498 | + project_user_relation = self.users_projects.find_by_user_id(member.id) | |
| 499 | + self.class.access_options.key(project_user_relation.project_access) | |
| 500 | + end | |
| 501 | + | |
| 492 | 502 | # Check if current branch name is marked as protected in the system |
| 493 | 503 | def protected_branch? branch_name |
| 494 | 504 | protected_branches.map(&:name).include?(branch_name) | ... | ... |
| ... | ... | @@ -0,0 +1,122 @@ |
| 1 | +class ProjectTeam | |
| 2 | + attr_accessor :project | |
| 3 | + | |
| 4 | + def initialize(project) | |
| 5 | + @project = project | |
| 6 | + end | |
| 7 | + | |
| 8 | + # Shortcut to add users | |
| 9 | + # | |
| 10 | + # Use: | |
| 11 | + # @team << [@user, :master] | |
| 12 | + # @team << [@users, :master] | |
| 13 | + # | |
| 14 | + def << args | |
| 15 | + users = args.first | |
| 16 | + | |
| 17 | + if users.respond_to?(:each) | |
| 18 | + add_users(users, args.second) | |
| 19 | + else | |
| 20 | + add_user(users, args.second) | |
| 21 | + end | |
| 22 | + end | |
| 23 | + | |
| 24 | + def get_tm user_id | |
| 25 | + project.users_projects.find_by_user_id(user_id) | |
| 26 | + end | |
| 27 | + | |
| 28 | + def add_user(user, access) | |
| 29 | + add_users_ids([user.id], access) | |
| 30 | + end | |
| 31 | + | |
| 32 | + def add_users(users, access) | |
| 33 | + add_users_ids(users.map(&:id), access) | |
| 34 | + end | |
| 35 | + | |
| 36 | + def add_users_ids(user_ids, access) | |
| 37 | + UsersProject.add_users_into_projects( | |
| 38 | + [project.id], | |
| 39 | + user_ids, | |
| 40 | + access | |
| 41 | + ) | |
| 42 | + end | |
| 43 | + | |
| 44 | + # Remove all users from project team | |
| 45 | + def truncate | |
| 46 | + UsersProject.truncate_team(project) | |
| 47 | + end | |
| 48 | + | |
| 49 | + def members | |
| 50 | + project.users_projects | |
| 51 | + end | |
| 52 | + | |
| 53 | + def guests | |
| 54 | + members.guests.map(&:user) | |
| 55 | + end | |
| 56 | + | |
| 57 | + def reporters | |
| 58 | + members.reporters.map(&:user) | |
| 59 | + end | |
| 60 | + | |
| 61 | + def developers | |
| 62 | + members.developers.map(&:user) | |
| 63 | + end | |
| 64 | + | |
| 65 | + def masters | |
| 66 | + members.masters.map(&:user) | |
| 67 | + end | |
| 68 | + | |
| 69 | + def repository_readers | |
| 70 | + repository_members[UsersProject::REPORTER] | |
| 71 | + end | |
| 72 | + | |
| 73 | + def repository_writers | |
| 74 | + repository_members[UsersProject::DEVELOPER] | |
| 75 | + end | |
| 76 | + | |
| 77 | + def repository_masters | |
| 78 | + repository_members[UsersProject::MASTER] | |
| 79 | + end | |
| 80 | + | |
| 81 | + def repository_members | |
| 82 | + keys = Hash.new {|h,k| h[k] = [] } | |
| 83 | + UsersProject.select("keys.identifier, project_access"). | |
| 84 | + joins(user: :keys).where(project_id: project.id). | |
| 85 | + each {|row| keys[row.project_access] << [row.identifier] } | |
| 86 | + | |
| 87 | + keys[UsersProject::REPORTER] += project.deploy_keys.pluck(:identifier) | |
| 88 | + keys | |
| 89 | + end | |
| 90 | + | |
| 91 | + def import(source_project) | |
| 92 | + target_project = project | |
| 93 | + | |
| 94 | + source_team = source_project.users_projects.all | |
| 95 | + target_team = target_project.users_projects.all | |
| 96 | + target_user_ids = target_team.map(&:user_id) | |
| 97 | + | |
| 98 | + source_team.reject! do |tm| | |
| 99 | + # Skip if user already present in team | |
| 100 | + target_user_ids.include?(tm.user_id) | |
| 101 | + end | |
| 102 | + | |
| 103 | + source_team.map! do |tm| | |
| 104 | + new_tm = tm.dup | |
| 105 | + new_tm.id = nil | |
| 106 | + new_tm.project_id = target_project.id | |
| 107 | + new_tm.skip_git = true | |
| 108 | + new_tm | |
| 109 | + end | |
| 110 | + | |
| 111 | + UsersProject.transaction do | |
| 112 | + source_team.each do |tm| | |
| 113 | + tm.save | |
| 114 | + end | |
| 115 | + target_project.update_repository | |
| 116 | + end | |
| 117 | + | |
| 118 | + true | |
| 119 | + rescue | |
| 120 | + false | |
| 121 | + end | |
| 122 | +end | ... | ... |
app/models/team.rb
| ... | ... | @@ -1,122 +0,0 @@ |
| 1 | -class Team | |
| 2 | - attr_accessor :project | |
| 3 | - | |
| 4 | - def initialize(project) | |
| 5 | - @project = project | |
| 6 | - end | |
| 7 | - | |
| 8 | - # Shortcut to add users | |
| 9 | - # | |
| 10 | - # Use: | |
| 11 | - # @team << [@user, :master] | |
| 12 | - # @team << [@users, :master] | |
| 13 | - # | |
| 14 | - def << args | |
| 15 | - users = args.first | |
| 16 | - | |
| 17 | - if users.respond_to?(:each) | |
| 18 | - add_users(users, args.second) | |
| 19 | - else | |
| 20 | - add_user(users, args.second) | |
| 21 | - end | |
| 22 | - end | |
| 23 | - | |
| 24 | - def get_tm user_id | |
| 25 | - project.users_projects.find_by_user_id(user_id) | |
| 26 | - end | |
| 27 | - | |
| 28 | - def add_user(user, access) | |
| 29 | - add_users_ids([user.id], access) | |
| 30 | - end | |
| 31 | - | |
| 32 | - def add_users(users, access) | |
| 33 | - add_users_ids(users.map(&:id), access) | |
| 34 | - end | |
| 35 | - | |
| 36 | - def add_users_ids(user_ids, access) | |
| 37 | - UsersProject.add_users_into_projects( | |
| 38 | - [project.id], | |
| 39 | - user_ids, | |
| 40 | - access | |
| 41 | - ) | |
| 42 | - end | |
| 43 | - | |
| 44 | - # Remove all users from project team | |
| 45 | - def truncate | |
| 46 | - UsersProject.truncate_team(project) | |
| 47 | - end | |
| 48 | - | |
| 49 | - def members | |
| 50 | - project.users_projects | |
| 51 | - end | |
| 52 | - | |
| 53 | - def guests | |
| 54 | - members.guests.map(&:user) | |
| 55 | - end | |
| 56 | - | |
| 57 | - def reporters | |
| 58 | - members.reporters.map(&:user) | |
| 59 | - end | |
| 60 | - | |
| 61 | - def developers | |
| 62 | - members.developers.map(&:user) | |
| 63 | - end | |
| 64 | - | |
| 65 | - def masters | |
| 66 | - members.masters.map(&:user) | |
| 67 | - end | |
| 68 | - | |
| 69 | - def repository_readers | |
| 70 | - repository_members[UsersProject::REPORTER] | |
| 71 | - end | |
| 72 | - | |
| 73 | - def repository_writers | |
| 74 | - repository_members[UsersProject::DEVELOPER] | |
| 75 | - end | |
| 76 | - | |
| 77 | - def repository_masters | |
| 78 | - repository_members[UsersProject::MASTER] | |
| 79 | - end | |
| 80 | - | |
| 81 | - def repository_members | |
| 82 | - keys = Hash.new {|h,k| h[k] = [] } | |
| 83 | - UsersProject.select("keys.identifier, project_access"). | |
| 84 | - joins(user: :keys).where(project_id: project.id). | |
| 85 | - each {|row| keys[row.project_access] << [row.identifier] } | |
| 86 | - | |
| 87 | - keys[UsersProject::REPORTER] += project.deploy_keys.pluck(:identifier) | |
| 88 | - keys | |
| 89 | - end | |
| 90 | - | |
| 91 | - def import(source_project) | |
| 92 | - target_project = project | |
| 93 | - | |
| 94 | - source_team = source_project.users_projects.all | |
| 95 | - target_team = target_project.users_projects.all | |
| 96 | - target_user_ids = target_team.map(&:user_id) | |
| 97 | - | |
| 98 | - source_team.reject! do |tm| | |
| 99 | - # Skip if user already present in team | |
| 100 | - target_user_ids.include?(tm.user_id) | |
| 101 | - end | |
| 102 | - | |
| 103 | - source_team.map! do |tm| | |
| 104 | - new_tm = tm.dup | |
| 105 | - new_tm.id = nil | |
| 106 | - new_tm.project_id = target_project.id | |
| 107 | - new_tm.skip_git = true | |
| 108 | - new_tm | |
| 109 | - end | |
| 110 | - | |
| 111 | - UsersProject.transaction do | |
| 112 | - source_team.each do |tm| | |
| 113 | - tm.save | |
| 114 | - end | |
| 115 | - target_project.update_repository | |
| 116 | - end | |
| 117 | - | |
| 118 | - true | |
| 119 | - rescue | |
| 120 | - false | |
| 121 | - end | |
| 122 | -end |
app/models/user.rb
| ... | ... | @@ -45,18 +45,27 @@ class User < ActiveRecord::Base |
| 45 | 45 | attr_accessor :force_random_password |
| 46 | 46 | |
| 47 | 47 | # Namespace for personal projects |
| 48 | - has_one :namespace, class_name: "Namespace", foreign_key: :owner_id, conditions: 'type IS NULL', dependent: :destroy | |
| 49 | - has_many :groups, class_name: "Group", foreign_key: :owner_id | |
| 50 | - | |
| 51 | - has_many :keys, dependent: :destroy | |
| 52 | - has_many :users_projects, dependent: :destroy | |
| 53 | - has_many :issues, foreign_key: :author_id, dependent: :destroy | |
| 54 | - has_many :notes, foreign_key: :author_id, dependent: :destroy | |
| 55 | - has_many :merge_requests, foreign_key: :author_id, dependent: :destroy | |
| 56 | - has_many :events, class_name: "Event", foreign_key: :author_id, dependent: :destroy | |
| 57 | - has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC" | |
| 58 | - has_many :assigned_issues, class_name: "Issue", foreign_key: :assignee_id, dependent: :destroy | |
| 59 | - has_many :assigned_merge_requests, class_name: "MergeRequest", foreign_key: :assignee_id, dependent: :destroy | |
| 48 | + has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL' | |
| 49 | + | |
| 50 | + has_many :keys, dependent: :destroy | |
| 51 | + has_many :users_projects, dependent: :destroy | |
| 52 | + has_many :issues, dependent: :destroy, foreign_key: :author_id | |
| 53 | + has_many :notes, dependent: :destroy, foreign_key: :author_id | |
| 54 | + has_many :merge_requests, dependent: :destroy, foreign_key: :author_id | |
| 55 | + has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" | |
| 56 | + has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" | |
| 57 | + has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" | |
| 58 | + | |
| 59 | + has_many :groups, class_name: "Group", foreign_key: :owner_id | |
| 60 | + has_many :recent_events, class_name: "Event", foreign_key: :author_id, order: "id DESC" | |
| 61 | + | |
| 62 | + has_many :projects, through: :users_projects | |
| 63 | + | |
| 64 | + has_many :user_team_user_relationships, dependent: :destroy | |
| 65 | + | |
| 66 | + has_many :user_teams, through: :user_team_user_relationships | |
| 67 | + has_many :user_team_project_relationships, through: :user_teams | |
| 68 | + has_many :team_projects, through: :user_team_project_relationships | |
| 60 | 69 | |
| 61 | 70 | validates :name, presence: true |
| 62 | 71 | validates :bio, length: { within: 0..255 } |
| ... | ... | @@ -80,6 +89,9 @@ class User < ActiveRecord::Base |
| 80 | 89 | scope :blocked, where(blocked: true) |
| 81 | 90 | scope :active, where(blocked: false) |
| 82 | 91 | scope :alphabetically, order('name ASC') |
| 92 | + scope :in_team, ->(team){ where(id: team.member_ids) } | |
| 93 | + scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } | |
| 94 | + scope :potential_team_members, ->(team) { team.members.any? ? active : active.not_in_team(team) } | |
| 83 | 95 | |
| 84 | 96 | # |
| 85 | 97 | # Class methods | ... | ... |
| ... | ... | @@ -0,0 +1,97 @@ |
| 1 | +class UserTeam < ActiveRecord::Base | |
| 2 | + attr_accessible :name, :owner_id, :path | |
| 3 | + | |
| 4 | + belongs_to :owner, class_name: User | |
| 5 | + | |
| 6 | + has_many :user_team_project_relationships, dependent: :destroy | |
| 7 | + has_many :user_team_user_relationships, dependent: :destroy | |
| 8 | + | |
| 9 | + has_many :projects, through: :user_team_project_relationships | |
| 10 | + has_many :members, through: :user_team_user_relationships, source: :user | |
| 11 | + | |
| 12 | + validates :name, presence: true, uniqueness: true | |
| 13 | + validates :owner, presence: true | |
| 14 | + validates :path, uniqueness: true, presence: true, length: { within: 1..255 }, | |
| 15 | + format: { with: Gitlab::Regex.path_regex, | |
| 16 | + message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" } | |
| 17 | + | |
| 18 | + scope :with_member, ->(user){ joins(:user_team_user_relationships).where(user_team_user_relationships: {user_id: user.id}) } | |
| 19 | + scope :with_project, ->(project){ joins(:user_team_project_relationships).where(user_team_project_relationships: {project_id: project})} | |
| 20 | + scope :without_project, ->(project){ where("user_teams.id NOT IN (:ids)", ids: (a = with_project(project); a.blank? ? 0 : a))} | |
| 21 | + scope :created_by, ->(user){ where(owner_id: user) } | |
| 22 | + | |
| 23 | + class << self | |
| 24 | + def search query | |
| 25 | + where("name LIKE :query OR path LIKE :query", query: "%#{query}%") | |
| 26 | + end | |
| 27 | + | |
| 28 | + def global_id | |
| 29 | + 'GLN' | |
| 30 | + end | |
| 31 | + | |
| 32 | + def access_roles | |
| 33 | + UsersProject.access_roles | |
| 34 | + end | |
| 35 | + end | |
| 36 | + | |
| 37 | + def to_param | |
| 38 | + path | |
| 39 | + end | |
| 40 | + | |
| 41 | + def assign_to_projects(projects, access) | |
| 42 | + projects.each do |project| | |
| 43 | + assign_to_project(project, access) | |
| 44 | + end | |
| 45 | + end | |
| 46 | + | |
| 47 | + def assign_to_project(project, access) | |
| 48 | + Gitlab::UserTeamManager.assign(self, project, access) | |
| 49 | + end | |
| 50 | + | |
| 51 | + def resign_from_project(project) | |
| 52 | + Gitlab::UserTeamManager.resign(self, project) | |
| 53 | + end | |
| 54 | + | |
| 55 | + def add_members(users, access, group_admin) | |
| 56 | + users.each do |user| | |
| 57 | + add_member(user, access, group_admin) | |
| 58 | + end | |
| 59 | + end | |
| 60 | + | |
| 61 | + def add_member(user, access, group_admin) | |
| 62 | + Gitlab::UserTeamManager.add_member_into_team(self, user, access, group_admin) | |
| 63 | + end | |
| 64 | + | |
| 65 | + def remove_member(user) | |
| 66 | + Gitlab::UserTeamManager.remove_member_from_team(self, user) | |
| 67 | + end | |
| 68 | + | |
| 69 | + def update_membership(user, options) | |
| 70 | + Gitlab::UserTeamManager.update_team_user_membership(self, user, options) | |
| 71 | + end | |
| 72 | + | |
| 73 | + def update_project_access(project, permission) | |
| 74 | + Gitlab::UserTeamManager.update_project_greates_access(self, project, permission) | |
| 75 | + end | |
| 76 | + | |
| 77 | + def max_project_access(project) | |
| 78 | + user_team_project_relationships.find_by_project_id(project).greatest_access | |
| 79 | + end | |
| 80 | + | |
| 81 | + def human_max_project_access(project) | |
| 82 | + self.class.access_roles.invert[max_project_access(project)] | |
| 83 | + end | |
| 84 | + | |
| 85 | + def default_projects_access(member) | |
| 86 | + user_team_user_relationships.find_by_user_id(member).permission | |
| 87 | + end | |
| 88 | + | |
| 89 | + def human_default_projects_access(member) | |
| 90 | + self.class.access_roles.invert[default_projects_access(member)] | |
| 91 | + end | |
| 92 | + | |
| 93 | + def admin?(member) | |
| 94 | + user_team_user_relationships.with_user(member).first.group_admin? | |
| 95 | + end | |
| 96 | + | |
| 97 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,28 @@ |
| 1 | +class UserTeamProjectRelationship < ActiveRecord::Base | |
| 2 | + attr_accessible :greatest_access, :project_id, :user_team_id | |
| 3 | + | |
| 4 | + belongs_to :user_team | |
| 5 | + belongs_to :project | |
| 6 | + | |
| 7 | + validates :project, presence: true | |
| 8 | + validates :user_team, presence: true | |
| 9 | + validate :check_greatest_access | |
| 10 | + | |
| 11 | + scope :with_project, ->(project){ where(project_id: project.id) } | |
| 12 | + | |
| 13 | + def team_name | |
| 14 | + user_team.name | |
| 15 | + end | |
| 16 | + | |
| 17 | + private | |
| 18 | + | |
| 19 | + def check_greatest_access | |
| 20 | + errors.add(:base, :incorrect_access_code) unless correct_access? | |
| 21 | + end | |
| 22 | + | |
| 23 | + def correct_access? | |
| 24 | + return false if greatest_access.blank? | |
| 25 | + return true if UsersProject.access_roles.has_value?(greatest_access) | |
| 26 | + false | |
| 27 | + end | |
| 28 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,19 @@ |
| 1 | +class UserTeamUserRelationship < ActiveRecord::Base | |
| 2 | + attr_accessible :group_admin, :permission, :user_id, :user_team_id | |
| 3 | + | |
| 4 | + belongs_to :user_team | |
| 5 | + belongs_to :user | |
| 6 | + | |
| 7 | + validates :user_team, presence: true | |
| 8 | + validates :user, presence: true | |
| 9 | + | |
| 10 | + scope :with_user, ->(user) { where(user_id: user.id) } | |
| 11 | + | |
| 12 | + def user_name | |
| 13 | + user.name | |
| 14 | + end | |
| 15 | + | |
| 16 | + def access_human | |
| 17 | + UsersProject.access_roles.invert[permission] | |
| 18 | + end | |
| 19 | +end | ... | ... |
app/models/users_project.rb
| ... | ... | @@ -39,7 +39,10 @@ class UsersProject < ActiveRecord::Base |
| 39 | 39 | scope :reporters, where(project_access: REPORTER) |
| 40 | 40 | scope :developers, where(project_access: DEVELOPER) |
| 41 | 41 | scope :masters, where(project_access: MASTER) |
| 42 | + | |
| 42 | 43 | scope :in_project, ->(project) { where(project_id: project.id) } |
| 44 | + scope :in_projects, ->(projects) { where(project_id: project_ids) } | |
| 45 | + scope :with_user, ->(user) { where(user_id: user.id) } | |
| 43 | 46 | |
| 44 | 47 | class << self |
| 45 | 48 | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | += form_for @team_member_relation, as: :team_member, url: admin_project_member_path(@project, @member) do |f| | |
| 2 | + -if @team_member_relation.errors.any? | |
| 3 | + .alert-message.block-message.error | |
| 4 | + %ul | |
| 5 | + - @team_member_relation.errors.full_messages.each do |msg| | |
| 6 | + %li= msg | |
| 7 | + | |
| 8 | + .clearfix | |
| 9 | + %label Project Access: | |
| 10 | + .input | |
| 11 | + = f.select :project_access, options_for_select(Project.access_options, @team_member_relation.project_access), {}, class: "project-access-select chosen span3" | |
| 12 | + | |
| 13 | + %br | |
| 14 | + .actions | |
| 15 | + = f.submit 'Save', class: "btn primary" | |
| 16 | + = link_to 'Cancel', :back, class: "btn" | ... | ... |
app/views/admin/projects/show.html.haml
| ... | ... | @@ -114,9 +114,9 @@ |
| 114 | 114 | %h5 |
| 115 | 115 | Team |
| 116 | 116 | %small |
| 117 | - (#{@project.users_projects.count}) | |
| 117 | + (#{@project.users.count}) | |
| 118 | 118 | %br |
| 119 | -%table.zebra-striped | |
| 119 | +%table.zebra-striped.team_members | |
| 120 | 120 | %thead |
| 121 | 121 | %tr |
| 122 | 122 | %th Name |
| ... | ... | @@ -124,13 +124,13 @@ |
| 124 | 124 | %th Repository Access |
| 125 | 125 | %th |
| 126 | 126 | |
| 127 | - - @project.users_projects.each do |tm| | |
| 127 | + - @project.users.each do |tm| | |
| 128 | 128 | %tr |
| 129 | 129 | %td |
| 130 | - = link_to tm.user_name, admin_user_path(tm.user) | |
| 131 | - %td= tm.project_access_human | |
| 132 | - %td= link_to 'Edit Access', edit_admin_team_member_path(tm), class: "btn small" | |
| 133 | - %td= link_to 'Remove from team', admin_team_member_path(tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small" | |
| 130 | + = link_to tm.name, admin_user_path(tm) | |
| 131 | + %td= @project.project_access_human(tm) | |
| 132 | + %td= link_to 'Edit Access', edit_admin_project_member_path(@project, tm), class: "btn small" | |
| 133 | + %td= link_to 'Remove from team', admin_project_member_path(@project, tm), confirm: 'Are you sure?', method: :delete, class: "btn danger small" | |
| 134 | 134 | |
| 135 | 135 | %br |
| 136 | 136 | %h5 Add new team member | ... | ... |
app/views/admin/team_members/_form.html.haml
| ... | ... | @@ -1,16 +0,0 @@ |
| 1 | -= form_for @admin_team_member, as: :team_member, url: admin_team_member_path(@admin_team_member) do |f| | |
| 2 | - -if @admin_team_member.errors.any? | |
| 3 | - .alert-message.block-message.error | |
| 4 | - %ul | |
| 5 | - - @admin_team_member.errors.full_messages.each do |msg| | |
| 6 | - %li= msg | |
| 7 | - | |
| 8 | - .clearfix | |
| 9 | - %label Project Access: | |
| 10 | - .input | |
| 11 | - = f.select :project_access, options_for_select(Project.access_options, @admin_team_member.project_access), {}, class: "project-access-select chosen span3" | |
| 12 | - | |
| 13 | - %br | |
| 14 | - .actions | |
| 15 | - = f.submit 'Save', class: "btn primary" | |
| 16 | - = link_to 'Cancel', :back, class: "btn" |
app/views/admin/team_members/edit.html.haml
| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | +%h3.page_title Rename Team | |
| 2 | +%hr | |
| 3 | += form_for @team, url: admin_team_path(@team), method: :put do |f| | |
| 4 | + - if @team.errors.any? | |
| 5 | + .alert-message.block-message.error | |
| 6 | + %span= @team.errors.full_messages.first | |
| 7 | + .clearfix.team_name_holder | |
| 8 | + = f.label :name do | |
| 9 | + Team name is | |
| 10 | + .input | |
| 11 | + = f.text_field :name, placeholder: "Example Team", class: "xxlarge" | |
| 12 | + | |
| 13 | + .clearfix.team_name_holder | |
| 14 | + = f.label :path do | |
| 15 | + %span.cred Team path is | |
| 16 | + .input | |
| 17 | + = f.text_field :path, placeholder: "example-team", class: "xxlarge danger" | |
| 18 | + %ul.cred | |
| 19 | + %li It will change web url for access team and team projects. | |
| 20 | + | |
| 21 | + .form-actions | |
| 22 | + = f.submit 'Rename team', class: "btn danger" | |
| 23 | + = link_to 'Cancel', admin_teams_path, class: "btn cancel-btn" | ... | ... |
| ... | ... | @@ -0,0 +1,38 @@ |
| 1 | +%h3.page_title | |
| 2 | + Teams | |
| 3 | + %small | |
| 4 | + simple Teams description | |
| 5 | + | |
| 6 | + = link_to 'New Team', new_admin_team_path, class: "btn small right" | |
| 7 | + %br | |
| 8 | + | |
| 9 | += form_tag admin_teams_path, method: :get, class: 'form-inline' do | |
| 10 | + = text_field_tag :name, params[:name], class: "xlarge" | |
| 11 | + = submit_tag "Search", class: "btn submit primary" | |
| 12 | + | |
| 13 | +%table | |
| 14 | + %thead | |
| 15 | + %tr | |
| 16 | + %th | |
| 17 | + Name | |
| 18 | + %i.icon-sort-down | |
| 19 | + %th Path | |
| 20 | + %th Projects | |
| 21 | + %th Members | |
| 22 | + %th Owner | |
| 23 | + %th.cred Danger Zone! | |
| 24 | + | |
| 25 | + - @teams.each do |team| | |
| 26 | + %tr | |
| 27 | + %td | |
| 28 | + %strong= link_to team.name, admin_team_path(team) | |
| 29 | + %td= team.path | |
| 30 | + %td= team.projects.count | |
| 31 | + %td= team.members.count | |
| 32 | + %td | |
| 33 | + = link_to team.owner.name, admin_user_path(team.owner_id) | |
| 34 | + %td.bgred | |
| 35 | + = link_to 'Rename', edit_admin_team_path(team), id: "edit_#{dom_id(team)}", class: "btn small" | |
| 36 | + = link_to 'Destroy', admin_team_path(team), confirm: "REMOVE #{team.name}? Are you sure?", method: :delete, class: "btn small danger" | |
| 37 | + | |
| 38 | += paginate @teams, theme: "admin" | ... | ... |
| ... | ... | @@ -0,0 +1,20 @@ |
| 1 | += form_tag admin_team_member_path(@team, @member), method: :put do | |
| 2 | + -if @member.errors.any? | |
| 3 | + .alert-message.block-message.error | |
| 4 | + %ul | |
| 5 | + - @member.errors.full_messages.each do |msg| | |
| 6 | + %li= msg | |
| 7 | + | |
| 8 | + .clearfix | |
| 9 | + %label Default access for Team projects: | |
| 10 | + .input | |
| 11 | + = select_tag :default_project_access, options_for_select(UserTeam.access_roles, @team.default_projects_access(@member)), class: "project-access-select chosen span3" | |
| 12 | + .clearfix | |
| 13 | + %label Team admin? | |
| 14 | + .input | |
| 15 | + = check_box_tag :group_admin, true, @team.admin?(@member) | |
| 16 | + | |
| 17 | + %br | |
| 18 | + .actions | |
| 19 | + = submit_tag 'Save', class: "btn primary" | |
| 20 | + = link_to 'Cancel', :back, class: "btn" | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | +%h3 | |
| 2 | + Edit access #{@member.name} in #{@team.name} team | |
| 3 | + | |
| 4 | +%hr | |
| 5 | +%table.zebra-striped | |
| 6 | + %tr | |
| 7 | + %td User: | |
| 8 | + %td= @member.name | |
| 9 | + %tr | |
| 10 | + %td Team: | |
| 11 | + %td= @team.name | |
| 12 | + %tr | |
| 13 | + %td Since: | |
| 14 | + %td= member_since(@team, @member).stamp("Nov 11, 2010") | |
| 15 | + | |
| 16 | += render 'form' | ... | ... |
| ... | ... | @@ -0,0 +1,29 @@ |
| 1 | +%h3.page_title | |
| 2 | + Team: #{@team.name} | |
| 3 | + | |
| 4 | +%fieldset | |
| 5 | + %legend Members (#{@team.members.count}) | |
| 6 | + = form_tag admin_team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do | |
| 7 | + %table#members_list | |
| 8 | + %thead | |
| 9 | + %tr | |
| 10 | + %th User name | |
| 11 | + %th Default project access | |
| 12 | + %th Team access | |
| 13 | + %th | |
| 14 | + - @team.members.each do |member| | |
| 15 | + %tr.member | |
| 16 | + %td | |
| 17 | + = link_to [:admin, member] do | |
| 18 | + = member.name | |
| 19 | + %small= "(#{member.email})" | |
| 20 | + %td= @team.human_default_projects_access(member) | |
| 21 | + %td= @team.admin?(member) ? "Admin" : "Member" | |
| 22 | + %td | |
| 23 | + %tr | |
| 24 | + %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_email), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5' | |
| 25 | + %td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" } | |
| 26 | + %td | |
| 27 | + %span= check_box_tag :group_admin | |
| 28 | + %span Admin? | |
| 29 | + %td= submit_tag 'Add', class: "btn primary", id: :add_members_to_team | ... | ... |
| ... | ... | @@ -0,0 +1,19 @@ |
| 1 | +%h3.page_title New Team | |
| 2 | +%hr | |
| 3 | += form_for @team, url: admin_teams_path do |f| | |
| 4 | + - if @team.errors.any? | |
| 5 | + .alert-message.block-message.error | |
| 6 | + %span= @team.errors.full_messages.first | |
| 7 | + .clearfix | |
| 8 | + = f.label :name do | |
| 9 | + Team name is | |
| 10 | + .input | |
| 11 | + = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" | |
| 12 | + | |
| 13 | + = f.submit 'Create team', class: "btn primary" | |
| 14 | + %hr | |
| 15 | + .padded | |
| 16 | + %ul | |
| 17 | + %li All created teams are public (users can view who enter into team and which project are assigned for this team) | |
| 18 | + %li People within a team see only projects they have access to | |
| 19 | + %li You will be able to assign existing projects for team | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | += form_tag admin_team_project_path(@team, @project), method: :put do | |
| 2 | + -if @project.errors.any? | |
| 3 | + .alert-message.block-message.error | |
| 4 | + %ul | |
| 5 | + - @project.errors.full_messages.each do |msg| | |
| 6 | + %li= msg | |
| 7 | + | |
| 8 | + .clearfix | |
| 9 | + %label Max access for Team members: | |
| 10 | + .input | |
| 11 | + = select_tag :greatest_project_access, options_for_select(UserTeam.access_roles, @team.max_project_access(@project)), class: "project-access-select chosen span3" | |
| 12 | + | |
| 13 | + %br | |
| 14 | + .actions | |
| 15 | + = submit_tag 'Save', class: "btn primary" | |
| 16 | + = link_to 'Cancel', :back, class: "btn" | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | +%h3 | |
| 2 | + Edit max access in #{@project.name} for #{@team.name} team | |
| 3 | + | |
| 4 | +%hr | |
| 5 | +%table.zebra-striped | |
| 6 | + %tr | |
| 7 | + %td Project: | |
| 8 | + %td= @project.name | |
| 9 | + %tr | |
| 10 | + %td Team: | |
| 11 | + %td= @team.name | |
| 12 | + %tr | |
| 13 | + %td Since: | |
| 14 | + %td= assigned_since(@team, @project).stamp("Nov 11, 2010") | |
| 15 | + | |
| 16 | += render 'form' | ... | ... |
| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | +%h3.page_title | |
| 2 | + Team: #{@team.name} | |
| 3 | + | |
| 4 | +%fieldset | |
| 5 | + %legend Projects (#{@team.projects.count}) | |
| 6 | + = form_tag admin_team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do | |
| 7 | + %table#projects_list | |
| 8 | + %thead | |
| 9 | + %tr | |
| 10 | + %th Project name | |
| 11 | + %th Max access | |
| 12 | + %th | |
| 13 | + - @team.projects.each do |project| | |
| 14 | + %tr.project | |
| 15 | + %td | |
| 16 | + = link_to project.name_with_namespace, [:admin, project] | |
| 17 | + %td | |
| 18 | + %span= @team.human_max_project_access(project) | |
| 19 | + %td | |
| 20 | + %tr | |
| 21 | + %td= select_tag :project_ids, options_from_collection_for_select(@projects , :id, :name_with_namespace), multiple: true, data: {placeholder: 'Select projects'}, class: 'chosen span5' | |
| 22 | + %td= select_tag :greatest_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" } | |
| 23 | + %td= submit_tag 'Add', class: "btn primary", id: :assign_projects_to_team | ... | ... |
| ... | ... | @@ -0,0 +1,101 @@ |
| 1 | +%h3.page_title | |
| 2 | + Team: #{@team.name} | |
| 3 | + | |
| 4 | +%br | |
| 5 | +%table.zebra-striped | |
| 6 | + %thead | |
| 7 | + %tr | |
| 8 | + %th Team | |
| 9 | + %th | |
| 10 | + %tr | |
| 11 | + %td | |
| 12 | + %b | |
| 13 | + Name: | |
| 14 | + %td | |
| 15 | + = @team.name | |
| 16 | + | |
| 17 | + = link_to edit_admin_team_path(@team), class: "btn btn-small right" do | |
| 18 | + %i.icon-edit | |
| 19 | + Rename | |
| 20 | + %tr | |
| 21 | + %td | |
| 22 | + %b | |
| 23 | + Owner: | |
| 24 | + %td | |
| 25 | + = @team.owner.name | |
| 26 | + .right | |
| 27 | + = link_to "#", class: "btn btn-small change-owner-link" do | |
| 28 | + %i.icon-edit | |
| 29 | + Change owner | |
| 30 | + | |
| 31 | + %tr.change-owner-holder.hide | |
| 32 | + %td.bgred | |
| 33 | + %b.cred | |
| 34 | + New Owner: | |
| 35 | + %td.bgred | |
| 36 | + = form_for @team, url: admin_team_path(@team) do |f| | |
| 37 | + = f.select :owner_id, User.all.map { |user| [user.name, user.id] }, {}, {class: 'chosen'} | |
| 38 | + %div | |
| 39 | + = f.submit 'Change Owner', class: "btn danger" | |
| 40 | + = link_to "Cancel", "#", class: "btn change-owner-cancel-link" | |
| 41 | + | |
| 42 | +%fieldset | |
| 43 | + %legend | |
| 44 | + Members (#{@team.members.count}) | |
| 45 | + %span= link_to 'Add members', new_admin_team_member_path(@team), class: "btn success small right", id: :add_members_to_team | |
| 46 | + - if @team.members.any? | |
| 47 | + %table#members_list | |
| 48 | + %thead | |
| 49 | + %tr | |
| 50 | + %th User name | |
| 51 | + %th Default project access | |
| 52 | + %th Team access | |
| 53 | + %th.cred.span3 Danger Zone! | |
| 54 | + - @team.members.each do |member| | |
| 55 | + %tr.member{ class: "user_#{member.id}"} | |
| 56 | + %td | |
| 57 | + = link_to [:admin, member] do | |
| 58 | + = member.name | |
| 59 | + %small= "(#{member.email})" | |
| 60 | + %td= @team.human_default_projects_access(member) | |
| 61 | + %td= @team.admin?(member) ? "Admin" : "Member" | |
| 62 | + %td.bgred | |
| 63 | + = link_to 'Edit', edit_admin_team_member_path(@team, member), class: "btn small" | |
| 64 | + | |
| 65 | + = 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}" | |
| 66 | + | |
| 67 | +%fieldset | |
| 68 | + %legend | |
| 69 | + Projects (#{@team.projects.count}) | |
| 70 | + %span= link_to 'Add projects', new_admin_team_project_path(@team), class: "btn success small right", id: :assign_projects_to_team | |
| 71 | + - if @team.projects.any? | |
| 72 | + %table#projects_list | |
| 73 | + %thead | |
| 74 | + %tr | |
| 75 | + %th Project name | |
| 76 | + %th Max access | |
| 77 | + %th.cred.span3 Danger Zone! | |
| 78 | + - @team.projects.each do |project| | |
| 79 | + %tr.project | |
| 80 | + %td | |
| 81 | + = link_to project.name_with_namespace, [:admin, project] | |
| 82 | + %td | |
| 83 | + %span= @team.human_max_project_access(project) | |
| 84 | + %td.bgred | |
| 85 | + = link_to 'Edit', edit_admin_team_project_path(@team, project), class: "btn small" | |
| 86 | + | |
| 87 | + = 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}" | |
| 88 | + | |
| 89 | +:javascript | |
| 90 | + $(function(){ | |
| 91 | + var modal = $('.change-owner-holder'); | |
| 92 | + $('.change-owner-link').bind("click", function(){ | |
| 93 | + $(this).hide(); | |
| 94 | + modal.show(); | |
| 95 | + }); | |
| 96 | + $('.change-owner-cancel-link').bind("click", function(){ | |
| 97 | + modal.hide(); | |
| 98 | + $('.change-owner-link').show(); | |
| 99 | + }) | |
| 100 | + }) | |
| 101 | + | ... | ... |
app/views/dashboard/_groups.html.haml
| 1 | -.groups_box | |
| 1 | +.ui-box | |
| 2 | 2 | %h5.title |
| 3 | 3 | Groups |
| 4 | 4 | %small |
| ... | ... | @@ -13,8 +13,6 @@ |
| 13 | 13 | %li |
| 14 | 14 | = link_to group_path(id: group.path), class: dom_class(group) do |
| 15 | 15 | %strong.well-title= truncate(group.name, length: 35) |
| 16 | - %span.arrow | |
| 17 | - → | |
| 18 | - %span.last_activity | |
| 19 | - %strong Projects: | |
| 20 | - %span= current_user.authorized_projects.where(namespace_id: group.id).count | |
| 16 | + %span.right.light | |
| 17 | + - if group.owner == current_user | |
| 18 | + %i.icon-wrench | ... | ... |
app/views/dashboard/_sidebar.html.haml
| ... | ... | @@ -0,0 +1,20 @@ |
| 1 | +.ui-box | |
| 2 | + %h5.title | |
| 3 | + Teams | |
| 4 | + %small | |
| 5 | + (#{@teams.count}) | |
| 6 | + %span.right | |
| 7 | + = link_to new_team_path, class: "btn very_small info" do | |
| 8 | + %i.icon-plus | |
| 9 | + New Team | |
| 10 | + %ul.well-list | |
| 11 | + - @teams.each do |team| | |
| 12 | + %li | |
| 13 | + = link_to team_path(id: team.path), class: dom_class(team) do | |
| 14 | + %strong.well-title= truncate(team.name, length: 35) | |
| 15 | + %span.right.light | |
| 16 | + - if team.owner == current_user | |
| 17 | + %i.icon-wrench | |
| 18 | + - tm = current_user.user_team_user_relationships.find_by_user_team_id(team.id) | |
| 19 | + - if tm | |
| 20 | + = tm.access_human | ... | ... |
app/views/layouts/admin.html.haml
| ... | ... | @@ -10,6 +10,8 @@ |
| 10 | 10 | = link_to "Stats", admin_root_path |
| 11 | 11 | = nav_link(controller: :projects) do |
| 12 | 12 | = link_to "Projects", admin_projects_path |
| 13 | + = nav_link(controller: :teams) do | |
| 14 | + = link_to "Teams", admin_teams_path | |
| 13 | 15 | = nav_link(controller: :groups) do |
| 14 | 16 | = link_to "Groups", admin_groups_path |
| 15 | 17 | = nav_link(controller: :users) do | ... | ... |
app/views/layouts/group.html.haml
| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | = render "layouts/head", title: "#{@group.name}" |
| 4 | 4 | %body{class: "#{app_theme} application"} |
| 5 | 5 | = render "layouts/flash" |
| 6 | - = render "layouts/head_panel", title: "#{@group.name}" | |
| 6 | + = render "layouts/head_panel", title: "group: #{@group.name}" | |
| 7 | 7 | .container |
| 8 | 8 | %ul.main_menu |
| 9 | 9 | = nav_link(path: 'groups#show', html_options: {class: 'home'}) do | ... | ... |
| ... | ... | @@ -0,0 +1,40 @@ |
| 1 | +!!! 5 | |
| 2 | +%html{ lang: "en"} | |
| 3 | + = render "layouts/head", title: "#{@team.name}" | |
| 4 | + %body{class: "#{app_theme} application"} | |
| 5 | + = render "layouts/flash" | |
| 6 | + = render "layouts/head_panel", title: "team: #{@team.name}" | |
| 7 | + .container | |
| 8 | + %ul.main_menu | |
| 9 | + = nav_link(path: 'teams#show', html_options: {class: 'home'}) do | |
| 10 | + = link_to "Home", team_path(@team), title: "Home" | |
| 11 | + | |
| 12 | + = nav_link(path: 'teams#issues') do | |
| 13 | + = link_to issues_team_path(@team) do | |
| 14 | + Issues | |
| 15 | + %span.count= Issue.opened.of_user_team(@team).count | |
| 16 | + | |
| 17 | + = nav_link(path: 'teams#merge_requests') do | |
| 18 | + = link_to merge_requests_team_path(@team) do | |
| 19 | + Merge Requests | |
| 20 | + %span.count= MergeRequest.opened.of_user_team(@team).count | |
| 21 | + | |
| 22 | + = nav_link(path: 'teams#search') do | |
| 23 | + = link_to "Search", search_team_path(@team) | |
| 24 | + | |
| 25 | + = nav_link(controller: [:members]) do | |
| 26 | + = link_to team_members_path(@team), class: "team-tab tab" do | |
| 27 | + Members | |
| 28 | + | |
| 29 | + - if can? current_user, :admin_user_team, @team | |
| 30 | + = nav_link(controller: [:projects]) do | |
| 31 | + = link_to team_projects_path(@team), class: "team-tab tab" do | |
| 32 | + %i.icon-briefcase | |
| 33 | + Projects | |
| 34 | + | |
| 35 | + = nav_link(path: 'teams#edit') do | |
| 36 | + = link_to edit_team_path(@team), class: "stat-tab tab " do | |
| 37 | + %i.icon-edit | |
| 38 | + Edit Team | |
| 39 | + | |
| 40 | + .content= yield | ... | ... |
app/views/projects/_project_head.html.haml
| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | = link_to project_path(@project), class: "activities-tab tab" do |
| 4 | 4 | %i.icon-home |
| 5 | 5 | Show |
| 6 | - = nav_link(controller: :team_members) do | |
| 6 | + = nav_link(controller: [:team_members, :teams]) do | |
| 7 | 7 | = link_to project_team_index_path(@project), class: "team-tab tab" do |
| 8 | 8 | %i.icon-user |
| 9 | 9 | Team | ... | ... |
| ... | ... | @@ -0,0 +1,22 @@ |
| 1 | += render "projects/project_head" | |
| 2 | + | |
| 3 | +%h3.page_title | |
| 4 | + = "Assign project to team of users" | |
| 5 | +%hr | |
| 6 | +%p.slead | |
| 7 | + Read more about assign to team of users #{link_to "here", '#', class: 'vlink'}. | |
| 8 | += form_tag assign_project_teams_path(@project), method: 'post' do | |
| 9 | + %p.slead Choose Team of users you want to assign: | |
| 10 | + .padded | |
| 11 | + = label_tag :team_id, "Team" | |
| 12 | + .input= select_tag(:team_id, options_from_collection_for_select(@teams, :id, :name), prompt: "Select team", class: "chosen xxlarge", required: true) | |
| 13 | + %p.slead Choose greatest user acces in team you want to assign: | |
| 14 | + .padded | |
| 15 | + = label_tag :team_ids, "Permission" | |
| 16 | + .input= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles), {class: "project-access-select chosen span3" } | |
| 17 | + | |
| 18 | + | |
| 19 | + .actions | |
| 20 | + = submit_tag 'Assign', class: "btn save-btn" | |
| 21 | + = link_to "Cancel", project_team_index_path(@project), class: "btn cancel-btn" | |
| 22 | + | ... | ... |
app/views/team_members/_form.html.haml
| 1 | 1 | %h3.page_title |
| 2 | 2 | = "New Team member(s)" |
| 3 | 3 | %hr |
| 4 | -= form_for @team_member, as: :team_member, url: project_team_members_path(@project, @team_member) do |f| | |
| 5 | - -if @team_member.errors.any? | |
| 4 | += form_for @user_project_relation, as: :team_member, url: project_team_members_path(@project) do |f| | |
| 5 | + -if @user_project_relation.errors.any? | |
| 6 | 6 | .alert-message.block-message.error |
| 7 | 7 | %ul |
| 8 | - - @team_member.errors.full_messages.each do |msg| | |
| 8 | + - @user_project_relation.errors.full_messages.each do |msg| | |
| 9 | 9 | %li= msg |
| 10 | 10 | |
| 11 | 11 | %h6 1. Choose people you want in the team |
| ... | ... | @@ -16,7 +16,7 @@ |
| 16 | 16 | %h6 2. Set access level for them |
| 17 | 17 | .clearfix |
| 18 | 18 | = f.label :project_access, "Project Access" |
| 19 | - .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen" | |
| 19 | + .input= select_tag :project_access, options_for_select(Project.access_options, @user_project_relation.project_access), class: "project-access-select chosen" | |
| 20 | 20 | |
| 21 | 21 | .actions |
| 22 | 22 | = f.submit 'Save', class: "btn save-btn" | ... | ... |
app/views/team_members/_show.html.haml
| 1 | 1 | - user = member.user |
| 2 | 2 | - allow_admin = can? current_user, :admin_project, @project |
| 3 | -%li{id: dom_id(member), class: "team_member_row user_#{user.id}"} | |
| 3 | +%li{id: dom_id(user), class: "team_member_row user_#{user.id}"} | |
| 4 | 4 | .row |
| 5 | 5 | .span6 |
| 6 | - = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do | |
| 6 | + = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do | |
| 7 | 7 | = image_tag gravatar_icon(user.email, 40), class: "avatar s32" |
| 8 | - = link_to project_team_member_path(@project, member), title: user.name, class: "dark" do | |
| 8 | + = link_to project_team_member_path(@project, user), title: user.name, class: "dark" do | |
| 9 | 9 | %strong= truncate(user.name, lenght: 40) |
| 10 | 10 | %br |
| 11 | 11 | %small.cgray= user.email |
| ... | ... | @@ -13,7 +13,7 @@ |
| 13 | 13 | .span5.right |
| 14 | 14 | - if allow_admin |
| 15 | 15 | .left |
| 16 | - = form_for(member, as: :team_member, url: project_team_member_path(@project, member)) do |f| | |
| 16 | + = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f| | |
| 17 | 17 | = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2" |
| 18 | 18 | .right |
| 19 | 19 | - if current_user == user |
| ... | ... | @@ -23,6 +23,6 @@ |
| 23 | 23 | - elsif user.blocked |
| 24 | 24 | %span.btn.disabled.blocked Blocked |
| 25 | 25 | - elsif allow_admin |
| 26 | - = 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 | |
| 26 | + = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "very_small btn danger" do | |
| 27 | 27 | %i.icon-minus.icon-white |
| 28 | 28 | ... | ... |
| ... | ... | @@ -0,0 +1,15 @@ |
| 1 | +- team = team_rel.user_team | |
| 2 | +- allow_admin = can? current_user, :admin_team_member, @project | |
| 3 | +%li{id: dom_id(team), class: "user_team_row team_#{team.id}"} | |
| 4 | + .row | |
| 5 | + .span6 | |
| 6 | + %strong= link_to team.name, team_path(team), title: team.name, class: "dark" | |
| 7 | + %br | |
| 8 | + %small.cgray Members: #{team.members.count} | |
| 9 | + | |
| 10 | + .span5.right | |
| 11 | + .right | |
| 12 | + - if allow_admin | |
| 13 | + .left | |
| 14 | + = link_to resign_project_team_path(@project, team), method: :delete, confirm: "Are you shure?", class: "btn danger small" do | |
| 15 | + %i.icon-minus.icon-white | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | +- grouper_project_teams(@project).each do |access, teams| | |
| 2 | + .ui-box | |
| 3 | + %h5.title | |
| 4 | + = UserTeam.access_roles.key(access).pluralize | |
| 5 | + %small= teams.size | |
| 6 | + %ul.well-list | |
| 7 | + - teams.sort_by(&:team_name).each do |tofr| | |
| 8 | + = render(partial: 'team_members/show_team', locals: {team_rel: tofr}) | |
| 9 | + | |
| 10 | + | |
| 11 | +:javascript | |
| 12 | + $(function(){ | |
| 13 | + $('.repo-access-select, .project-access-select').live("change", function() { | |
| 14 | + $(this.form).submit(); | |
| 15 | + }); | |
| 16 | + }) | ... | ... |
app/views/team_members/create.js.haml
app/views/team_members/import.html.haml
| ... | ... | @@ -4,7 +4,7 @@ |
| 4 | 4 | = "Import team from another project" |
| 5 | 5 | %hr |
| 6 | 6 | %p.slead |
| 7 | - Read more about team import #{link_to "here", '#', class: 'vlink'}. | |
| 7 | + Read more about project team import #{link_to "here", '#', class: 'vlink'}. | |
| 8 | 8 | = form_tag apply_import_project_team_members_path(@project), method: 'post' do |
| 9 | 9 | %p.slead Choose project you want to use as team source: |
| 10 | 10 | .padded | ... | ... |
app/views/team_members/index.html.haml
| 1 | 1 | = render "projects/project_head" |
| 2 | 2 | %h3.page_title |
| 3 | 3 | Team Members |
| 4 | - (#{@project.users_projects.count}) | |
| 4 | + (#{@project.users.count}) | |
| 5 | 5 | %small |
| 6 | 6 | Read more about project permissions |
| 7 | 7 | %strong= link_to "here", help_permissions_path, class: "vlink" |
| ... | ... | @@ -10,11 +10,24 @@ |
| 10 | 10 | %span.right |
| 11 | 11 | = link_to import_project_team_members_path(@project), class: "btn small grouped", title: "Import team from another project" do |
| 12 | 12 | Import team from another project |
| 13 | + = link_to available_project_teams_path(@project), class: "btn small grouped", title: "Assign project to team of users" do | |
| 14 | + Assign project to Team of users | |
| 13 | 15 | = link_to new_project_team_member_path(@project), class: "btn success small grouped", title: "New Team Member" do |
| 14 | 16 | New Team Member |
| 15 | -%hr | |
| 16 | 17 | |
| 18 | +%hr | |
| 17 | 19 | |
| 18 | 20 | .clearfix |
| 19 | 21 | %div.team-table |
| 20 | 22 | = render partial: "team_members/team", locals: {project: @project} |
| 23 | + | |
| 24 | + | |
| 25 | +%h3.page_title | |
| 26 | + Assigned teams | |
| 27 | + (#{@project.user_teams.count}) | |
| 28 | + | |
| 29 | +%hr | |
| 30 | + | |
| 31 | +.clearfix | |
| 32 | +%div.team-table | |
| 33 | + = render partial: "team_members/teams", locals: {project: @project} | ... | ... |
app/views/team_members/show.html.haml
| 1 | 1 | - allow_admin = can? current_user, :admin_project, @project |
| 2 | -- user = @team_member.user | |
| 3 | 2 | |
| 4 | 3 | .team_member_show |
| 5 | 4 | - if can? current_user, :admin_project, @project |
| 6 | - = 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" | |
| 5 | + = link_to 'Remove from team', project_team_member_path(@project, @member), confirm: 'Are you sure?', method: :delete, class: "right btn danger" | |
| 7 | 6 | .profile_avatar_holder |
| 8 | - = image_tag gravatar_icon(user.email, 60), class: "borders" | |
| 7 | + = image_tag gravatar_icon(@member.email, 60), class: "borders" | |
| 9 | 8 | %h3.page_title |
| 10 | - = user.name | |
| 11 | - %small (@#{user.username}) | |
| 9 | + = @member.name | |
| 10 | + %small (@#{@member.username}) | |
| 12 | 11 | |
| 13 | 12 | %hr |
| 14 | 13 | .back_link |
| ... | ... | @@ -21,34 +20,34 @@ |
| 21 | 20 | %table.lite |
| 22 | 21 | %tr |
| 23 | 22 | %td Email |
| 24 | - %td= mail_to user.email | |
| 23 | + %td= mail_to @member.email | |
| 25 | 24 | %tr |
| 26 | 25 | %td Skype |
| 27 | - %td= user.skype | |
| 28 | - - unless user.linkedin.blank? | |
| 26 | + %td= @member.skype | |
| 27 | + - unless @member.linkedin.blank? | |
| 29 | 28 | %tr |
| 30 | 29 | %td LinkedIn |
| 31 | - %td= user.linkedin | |
| 32 | - - unless user.twitter.blank? | |
| 30 | + %td= @member.linkedin | |
| 31 | + - unless @member.twitter.blank? | |
| 33 | 32 | %tr |
| 34 | 33 | %td Twitter |
| 35 | - %td= user.twitter | |
| 36 | - - unless user.bio.blank? | |
| 34 | + %td= @member.twitter | |
| 35 | + - unless @member.bio.blank? | |
| 37 | 36 | %tr |
| 38 | 37 | %td Bio |
| 39 | - %td= user.bio | |
| 38 | + %td= @member.bio | |
| 40 | 39 | .span6 |
| 41 | 40 | %table.lite |
| 42 | 41 | %tr |
| 43 | 42 | %td Member since |
| 44 | - %td= @team_member.created_at.stamp("Aug 21, 2011") | |
| 43 | + %td= @user_project_relation.created_at.stamp("Aug 21, 2011") | |
| 45 | 44 | %tr |
| 46 | 45 | %td |
| 47 | 46 | Project Access: |
| 48 | 47 | %small (#{link_to "read more", help_permissions_path, class: "vlink"}) |
| 49 | 48 | %td |
| 50 | - = form_for(@team_member, as: :team_member, url: project_team_member_path(@project, @team_member)) do |f| | |
| 51 | - = f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select", disabled: !allow_admin | |
| 49 | + = form_for(@user_project_relation, as: :team_member, url: project_team_member_path(@project, @member)) do |f| | |
| 50 | + = f.select :project_access, options_for_select(Project.access_options, @user_project_relation.project_access), {}, class: "project-access-select", disabled: !allow_admin | |
| 52 | 51 | %hr |
| 53 | 52 | = render @events |
| 54 | 53 | :javascript | ... | ... |
app/views/team_members/update.js.haml
| 1 | -- if @team_member.valid? | |
| 1 | +- if @user_project_relation.valid? | |
| 2 | 2 | :plain |
| 3 | - $("##{dom_id(@team_member)}").effect("highlight", {color: "#529214"}, 1000);; | |
| 3 | + $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#529214"}, 1000);; | |
| 4 | 4 | - else |
| 5 | 5 | :plain |
| 6 | - $("##{dom_id(@team_member)}").effect("highlight", {color: "#D12F19"}, 1000);; | |
| 6 | + $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#D12F19"}, 1000);; | ... | ... |
| ... | ... | @@ -0,0 +1,33 @@ |
| 1 | += form_tag team_filter_path(entity), method: 'get' do | |
| 2 | + %fieldset.dashboard-search-filter | |
| 3 | + = search_field_tag "search", params[:search], { placeholder: 'Search', class: 'search-text-input' } | |
| 4 | + = button_tag type: 'submit', class: 'btn' do | |
| 5 | + %i.icon-search | |
| 6 | + | |
| 7 | + %fieldset | |
| 8 | + %legend Status: | |
| 9 | + %ul.nav.nav-pills.nav-stacked | |
| 10 | + %li{class: ("active" if !params[:status])} | |
| 11 | + = link_to team_filter_path(entity, status: nil) do | |
| 12 | + Open | |
| 13 | + %li{class: ("active" if params[:status] == 'closed')} | |
| 14 | + = link_to team_filter_path(entity, status: 'closed') do | |
| 15 | + Closed | |
| 16 | + %li{class: ("active" if params[:status] == 'all')} | |
| 17 | + = link_to team_filter_path(entity, status: 'all') do | |
| 18 | + All | |
| 19 | + | |
| 20 | + %fieldset | |
| 21 | + %legend Projects: | |
| 22 | + %ul.nav.nav-pills.nav-stacked | |
| 23 | + - @projects.each do |project| | |
| 24 | + - unless entities_per_project(project, entity).zero? | |
| 25 | + %li{class: ("active" if params[:project_id] == project.id.to_s)} | |
| 26 | + = link_to team_filter_path(entity, project_id: project.id) do | |
| 27 | + = project.name_with_namespace | |
| 28 | + %small.right= entities_per_project(project, entity) | |
| 29 | + | |
| 30 | + %fieldset | |
| 31 | + %hr | |
| 32 | + = link_to "Reset", team_filter_path(entity), class: 'btn right' | |
| 33 | + | ... | ... |
| ... | ... | @@ -0,0 +1,22 @@ |
| 1 | +.projects_box | |
| 2 | + %h5.title | |
| 3 | + Projects | |
| 4 | + %small | |
| 5 | + (#{projects.count}) | |
| 6 | + - if can? current_user, :manage_group, @group | |
| 7 | + %span.right | |
| 8 | + = link_to new_project_path(namespace_id: @group.id), class: "btn very_small info" do | |
| 9 | + %i.icon-plus | |
| 10 | + New Project | |
| 11 | + %ul.well-list | |
| 12 | + - if projects.blank? | |
| 13 | + %p.nothing_here_message This team has no projects yet | |
| 14 | + - projects.each do |project| | |
| 15 | + %li | |
| 16 | + = link_to project_path(project), class: dom_class(project) do | |
| 17 | + %strong.well-title= truncate(project.name, length: 25) | |
| 18 | + %span.arrow | |
| 19 | + → | |
| 20 | + %span.last_activity | |
| 21 | + %strong Last activity: | |
| 22 | + %span= project_last_activity(project) | ... | ... |
| ... | ... | @@ -0,0 +1,24 @@ |
| 1 | += render "team_head" | |
| 2 | + | |
| 3 | +%h3.page_title= "Edit Team #{@team.name}" | |
| 4 | +%hr | |
| 5 | += form_for @team, url: teams_path do |f| | |
| 6 | + - if @team.errors.any? | |
| 7 | + .alert-message.block-message.error | |
| 8 | + %span= @team.errors.full_messages.first | |
| 9 | + .clearfix | |
| 10 | + = f.label :name do | |
| 11 | + Team name is | |
| 12 | + .input | |
| 13 | + = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" | |
| 14 | + | |
| 15 | + .clearfix | |
| 16 | + = f.label :path do | |
| 17 | + Team path is | |
| 18 | + .input | |
| 19 | + = f.text_field :path, placeholder: "opensource", class: "xxlarge left" | |
| 20 | + .clearfix | |
| 21 | + .input.span3.center | |
| 22 | + = f.submit 'Save team changes', class: "btn primary" | |
| 23 | + .input.span3.center | |
| 24 | + = link_to 'Delete team', team_path(@team), method: :delete, confirm: "You are shure?", class: "btn danger" | ... | ... |
| ... | ... | @@ -0,0 +1,38 @@ |
| 1 | +%h3.page_title | |
| 2 | + Teams | |
| 3 | + %small | |
| 4 | + list of all teams | |
| 5 | + | |
| 6 | + = link_to 'New Team', new_team_path, class: "btn success small right" | |
| 7 | + %br | |
| 8 | + | |
| 9 | += form_tag search_teams_path, method: :get, class: 'form-inline' do | |
| 10 | + = text_field_tag :name, params[:name], class: "xlarge" | |
| 11 | + = submit_tag "Search", class: "btn submit primary" | |
| 12 | + | |
| 13 | +%table.teams_list | |
| 14 | + %thead | |
| 15 | + %tr | |
| 16 | + %th | |
| 17 | + Name | |
| 18 | + %i.icon-sort-down | |
| 19 | + %th Path | |
| 20 | + %th Projects | |
| 21 | + %th Members | |
| 22 | + %th Owner | |
| 23 | + %th.cred Danger Zone! | |
| 24 | + | |
| 25 | + - @teams.each do |team| | |
| 26 | + %tr | |
| 27 | + %td | |
| 28 | + %strong= link_to team.name, team_path(team) | |
| 29 | + %td= team.path | |
| 30 | + %td= link_to team.projects.count, team_projects_path(team) | |
| 31 | + %td= link_to team.members.count, team_members_path(team) | |
| 32 | + %td= link_to team.owner.name, team_member_path(team, team.owner) | |
| 33 | + %td.bgred | |
| 34 | + - if current_user.can?(:manage_user_team, team) | |
| 35 | + = link_to "Edit", edit_team_path(team), class: "btn small" | |
| 36 | + - if current_user.can?(:admin_user_team, team) | |
| 37 | + = link_to "Destroy", team_path(team), method: :delete, confirm: "You are shure?", class: "danger btn small" | |
| 38 | + | ... | ... |
| ... | ... | @@ -0,0 +1,25 @@ |
| 1 | += render "team_head" | |
| 2 | + | |
| 3 | +%h3.page_title | |
| 4 | + Issues | |
| 5 | + %small (in Team projects assigned to Team members) | |
| 6 | + %small.right #{@issues.total_count} issues | |
| 7 | + | |
| 8 | +%hr | |
| 9 | +.row | |
| 10 | + .span3 | |
| 11 | + = render 'filter', entity: 'issue' | |
| 12 | + .span9 | |
| 13 | + - if @issues.any? | |
| 14 | + - @issues.group_by(&:project).each do |group| | |
| 15 | + %div.ui-box | |
| 16 | + - @project = group[0] | |
| 17 | + %h5.title | |
| 18 | + = link_to_project @project | |
| 19 | + %ul.well-list.issues_table | |
| 20 | + - group[1].each do |issue| | |
| 21 | + = render(partial: 'issues/show', locals: {issue: issue}) | |
| 22 | + %hr | |
| 23 | + = paginate @issues, theme: "gitlab" | |
| 24 | + - else | |
| 25 | + %p.nothing_here_message Nothing to show here | ... | ... |
| ... | ... | @@ -0,0 +1,20 @@ |
| 1 | += form_tag admin_team_member_path(@team, @member), method: :put do | |
| 2 | + -if @member.errors.any? | |
| 3 | + .alert-message.block-message.error | |
| 4 | + %ul | |
| 5 | + - @member.errors.full_messages.each do |msg| | |
| 6 | + %li= msg | |
| 7 | + | |
| 8 | + .clearfix | |
| 9 | + %label Default access for Team projects: | |
| 10 | + .input | |
| 11 | + = select_tag :default_project_access, options_for_select(UserTeam.access_roles, @team.default_projects_access(@member)), class: "project-access-select chosen span3" | |
| 12 | + .clearfix | |
| 13 | + %label Team admin? | |
| 14 | + .input | |
| 15 | + = check_box_tag :group_admin, true, @team.admin?(@member) | |
| 16 | + | |
| 17 | + %br | |
| 18 | + .actions | |
| 19 | + = submit_tag 'Save', class: "btn primary" | |
| 20 | + = link_to 'Cancel', :back, class: "btn" | ... | ... |
| ... | ... | @@ -0,0 +1,31 @@ |
| 1 | +- user = member.user | |
| 2 | +- allow_admin = can? current_user, :manage_user_team, @team | |
| 3 | +%li{id: dom_id(member), class: "team_member_row user_#{user.id}"} | |
| 4 | + .row | |
| 5 | + .span5 | |
| 6 | + = link_to user_path(user.username), title: user.name, class: "dark" do | |
| 7 | + = image_tag gravatar_icon(user.email, 40), class: "avatar s32" | |
| 8 | + = link_to user_path(user.username), title: user.name, class: "dark" do | |
| 9 | + %strong= truncate(user.name, lenght: 40) | |
| 10 | + %br | |
| 11 | + %small.cgray= user.email | |
| 12 | + | |
| 13 | + .span6.right | |
| 14 | + - if allow_admin | |
| 15 | + .left.span2 | |
| 16 | + = form_for(member, as: :team_member, url: team_member_path(@team, user)) do |f| | |
| 17 | + = f.select :permission, options_for_select(UsersProject.access_roles, @team.default_projects_access(user)), {}, class: "medium project-access-select span2" | |
| 18 | + .left.span2 | |
| 19 | + %span | |
| 20 | + Admin access | |
| 21 | + = check_box_tag :group_admin | |
| 22 | + .right | |
| 23 | + - if current_user == user | |
| 24 | + %span.btn.disabled This is you! | |
| 25 | + - if @team.owner == user | |
| 26 | + %span.btn.disabled.success Owner | |
| 27 | + - elsif user.blocked | |
| 28 | + %span.btn.disabled.blocked Blocked | |
| 29 | + - elsif allow_admin | |
| 30 | + = link_to team_member_path(@team, user), confirm: remove_from_user_team_message(@team, user), method: :delete, class: "very_small btn danger" do | |
| 31 | + %i.icon-minus.icon-white | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | +- grouped_user_team_members(@team).each do |access, members| | |
| 2 | + .ui-box | |
| 3 | + %h5.title | |
| 4 | + = Project.access_options.key(access).pluralize | |
| 5 | + %small= members.size | |
| 6 | + %ul.well-list | |
| 7 | + - members.sort_by(&:user_name).each do |up| | |
| 8 | + = render(partial: 'teams/members/show', locals: {member: up}) | |
| 9 | + | |
| 10 | + | |
| 11 | +:javascript | |
| 12 | + $(function(){ | |
| 13 | + $('.repo-access-select, .project-access-select').live("change", function() { | |
| 14 | + $(this.form).submit(); | |
| 15 | + }); | |
| 16 | + }) | ... | ... |
| ... | ... | @@ -0,0 +1,18 @@ |
| 1 | += render "teams/team_head" | |
| 2 | + | |
| 3 | +%h3 | |
| 4 | + Edit access #{@member.name} in #{@team.name} team | |
| 5 | + | |
| 6 | +%hr | |
| 7 | +%table.zebra-striped | |
| 8 | + %tr | |
| 9 | + %td User: | |
| 10 | + %td= @member.name | |
| 11 | + %tr | |
| 12 | + %td Team: | |
| 13 | + %td= @team.name | |
| 14 | + %tr | |
| 15 | + %td Since: | |
| 16 | + %td= member_since(@team, @member).stamp("Nov 11, 2010") | |
| 17 | + | |
| 18 | += render 'form' | ... | ... |
| ... | ... | @@ -0,0 +1,19 @@ |
| 1 | += render "teams/team_head" | |
| 2 | + | |
| 3 | +%h3.page_title | |
| 4 | + Team Members | |
| 5 | + (#{@members.count}) | |
| 6 | + %small | |
| 7 | + Read more about project permissions | |
| 8 | + %strong= link_to "here", help_permissions_path, class: "vlink" | |
| 9 | + | |
| 10 | + - if can? current_user, :manage_user_team, @team | |
| 11 | + %span.right | |
| 12 | + = link_to new_team_member_path(@team), class: "btn success small grouped", title: "New Team Member" do | |
| 13 | + New Team Member | |
| 14 | +%hr | |
| 15 | + | |
| 16 | + | |
| 17 | +.clearfix | |
| 18 | +%div.team-table | |
| 19 | + = render partial: "teams/members/team", locals: {project: @team} | ... | ... |
| ... | ... | @@ -0,0 +1,30 @@ |
| 1 | += render "teams/team_head" | |
| 2 | + | |
| 3 | +%h3.page_title | |
| 4 | + Team: #{@team.name} | |
| 5 | + | |
| 6 | +%fieldset | |
| 7 | + %legend Members (#{@team.members.count}) | |
| 8 | + = form_tag team_members_path(@team), id: "team_members", class: "bulk_import", method: :post do | |
| 9 | + %table#members_list | |
| 10 | + %thead | |
| 11 | + %tr | |
| 12 | + %th User name | |
| 13 | + %th Default project access | |
| 14 | + %th Team access | |
| 15 | + %th | |
| 16 | + - @team.members.each do |member| | |
| 17 | + %tr.member | |
| 18 | + %td | |
| 19 | + = member.name | |
| 20 | + %small= "(#{member.email})" | |
| 21 | + %td= @team.human_default_projects_access(member) | |
| 22 | + %td= @team.admin?(member) ? "Admin" : "Member" | |
| 23 | + %td | |
| 24 | + %tr | |
| 25 | + %td= select_tag :user_ids, options_from_collection_for_select(@users , :id, :name_with_email), multiple: true, data: {placeholder: 'Select users'}, class: 'chosen span5' | |
| 26 | + %td= select_tag :default_project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span3" } | |
| 27 | + %td | |
| 28 | + %span= check_box_tag :group_admin | |
| 29 | + %span Admin? | |
| 30 | + %td= submit_tag 'Add', class: "btn primary", id: :add_members_to_team | ... | ... |
| ... | ... | @@ -0,0 +1,62 @@ |
| 1 | += render "teams/team_head" | |
| 2 | + | |
| 3 | +- allow_admin = can? current_user, :admin_project, @project | |
| 4 | +- user = @team_member.user | |
| 5 | + | |
| 6 | +.team_member_show | |
| 7 | + - if can? current_user, :admin_project, @project | |
| 8 | + = 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" | |
| 9 | + .profile_avatar_holder | |
| 10 | + = image_tag gravatar_icon(user.email, 60), class: "borders" | |
| 11 | + %h3.page_title | |
| 12 | + = user.name | |
| 13 | + %small (@#{user.username}) | |
| 14 | + | |
| 15 | + %hr | |
| 16 | + .back_link | |
| 17 | + %br | |
| 18 | + = link_to project_team_index_path(@project), class: "" do | |
| 19 | + ← To team list | |
| 20 | + %br | |
| 21 | + .row | |
| 22 | + .span6 | |
| 23 | + %table.lite | |
| 24 | + %tr | |
| 25 | + %td Email | |
| 26 | + %td= mail_to user.email | |
| 27 | + %tr | |
| 28 | + %td Skype | |
| 29 | + %td= user.skype | |
| 30 | + - unless user.linkedin.blank? | |
| 31 | + %tr | |
| 32 | + %td LinkedIn | |
| 33 | + %td= user.linkedin | |
| 34 | + - unless user.twitter.blank? | |
| 35 | + %tr | |
| 36 | + %td Twitter | |
| 37 | + %td= user.twitter | |
| 38 | + - unless user.bio.blank? | |
| 39 | + %tr | |
| 40 | + %td Bio | |
| 41 | + %td= user.bio | |
| 42 | + .span6 | |
| 43 | + %table.lite | |
| 44 | + %tr | |
| 45 | + %td Member since | |
| 46 | + %td= @team_member.created_at.stamp("Aug 21, 2011") | |
| 47 | + %tr | |
| 48 | + %td | |
| 49 | + Project Access: | |
| 50 | + %small (#{link_to "read more", help_permissions_path, class: "vlink"}) | |
| 51 | + %td | |
| 52 | + = form_for(@team_member, as: :team_member, url: project_team_member_path(@project, @team_member)) do |f| | |
| 53 | + = f.select :project_access, options_for_select(Project.access_options, @team_member.project_access), {}, class: "project-access-select", disabled: !allow_admin | |
| 54 | + %hr | |
| 55 | + = render @events | |
| 56 | +:javascript | |
| 57 | + $(function(){ | |
| 58 | + $('.repo-access-select, .project-access-select').live("change", function() { | |
| 59 | + $(this.form).submit(); | |
| 60 | + }); | |
| 61 | + }) | |
| 62 | + | ... | ... |
| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | += render "team_head" | |
| 2 | + | |
| 3 | +%h3.page_title | |
| 4 | + Merge Requests | |
| 5 | + %small (authored by or assigned to Team members) | |
| 6 | + %small.right #{@merge_requests.total_count} merge requests | |
| 7 | + | |
| 8 | +%hr | |
| 9 | +.row | |
| 10 | + .span3 | |
| 11 | + = render 'filter', entity: 'merge_request' | |
| 12 | + .span9 | |
| 13 | + - if @merge_requests.any? | |
| 14 | + - @merge_requests.group_by(&:project).each do |group| | |
| 15 | + .ui-box | |
| 16 | + - @project = group[0] | |
| 17 | + %h5.title | |
| 18 | + = link_to_project @project | |
| 19 | + %ul.well-list | |
| 20 | + - group[1].each do |merge_request| | |
| 21 | + = render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request}) | |
| 22 | + %hr | |
| 23 | + = paginate @merge_requests, theme: "gitlab" | |
| 24 | + | |
| 25 | + - else | |
| 26 | + %h3.nothing_here_message Nothing to show here | ... | ... |
| ... | ... | @@ -0,0 +1,19 @@ |
| 1 | +%h3.page_title New Team | |
| 2 | +%hr | |
| 3 | += form_for @team, url: teams_path do |f| | |
| 4 | + - if @team.errors.any? | |
| 5 | + .alert-message.block-message.error | |
| 6 | + %span= @team.errors.full_messages.first | |
| 7 | + .clearfix | |
| 8 | + = f.label :name do | |
| 9 | + Team name is | |
| 10 | + .input | |
| 11 | + = f.text_field :name, placeholder: "Ex. Ruby Developers", class: "xxlarge left" | |
| 12 | + | |
| 13 | + = f.submit 'Create team', class: "btn primary" | |
| 14 | + %hr | |
| 15 | + .padded | |
| 16 | + %ul | |
| 17 | + %li All created teams are public (users can view who enter into team and which project are assigned for this team) | |
| 18 | + %li People within a team see only projects they have access to | |
| 19 | + %li You will be able to assign existing projects for team | ... | ... |
| ... | ... | @@ -0,0 +1,16 @@ |
| 1 | += form_tag team_project_path(@team, @project), method: :put do | |
| 2 | + -if @project.errors.any? | |
| 3 | + .alert-message.block-message.error | |
| 4 | + %ul | |
| 5 | + - @project.errors.full_messages.each do |msg| | |
| 6 | + %li= msg | |
| 7 | + | |
| 8 | + .clearfix | |
| 9 | + %label Max access for Team members: | |
| 10 | + .input | |
| 11 | + = select_tag :greatest_project_access, options_for_select(UserTeam.access_roles, @team.max_project_access(@project)), class: "project-access-select chosen span3" | |
| 12 | + | |
| 13 | + %br | |
| 14 | + .actions | |
| 15 | + = submit_tag 'Save', class: "btn primary" | |
| 16 | + = link_to 'Cancel', :back, class: "btn" | ... | ... |
| ... | ... | @@ -0,0 +1,18 @@ |
| 1 | += render "teams/team_head" | |
| 2 | + | |
| 3 | +%h3 | |
| 4 | + Edit max access in #{@project.name} for #{@team.name} team | |
| 5 | + | |
| 6 | +%hr | |
| 7 | +%table.zebra-striped | |
| 8 | + %tr | |
| 9 | + %td Project: | |
| 10 | + %td= @project.name | |
| 11 | + %tr | |
| 12 | + %td Team: | |
| 13 | + %td= @team.name | |
| 14 | + %tr | |
| 15 | + %td Since: | |
| 16 | + %td= assigned_since(@team, @project).stamp("Nov 11, 2010") | |
| 17 | + | |
| 18 | += render 'form' | ... | ... |
| ... | ... | @@ -0,0 +1,34 @@ |
| 1 | += render "teams/team_head" | |
| 2 | + | |
| 3 | +%h3.page_title | |
| 4 | + Assigned projects (#{@team.projects.count}) | |
| 5 | + %small | |
| 6 | + Read more about project permissions | |
| 7 | + %strong= link_to "here", help_permissions_path, class: "vlink" | |
| 8 | + | |
| 9 | + - if current_user.can?(:manage_user_team, @team) && @avaliable_projects.any? | |
| 10 | + %span.right | |
| 11 | + = link_to new_team_project_path(@team), class: "btn success small grouped", title: "New Team Member" do | |
| 12 | + Assign project to Team | |
| 13 | + | |
| 14 | +%hr | |
| 15 | + | |
| 16 | +%table.projects-table | |
| 17 | + %thead | |
| 18 | + %tr | |
| 19 | + %th Project name | |
| 20 | + %th Max access | |
| 21 | + - if current_user.can?(:admin_user_team, @team) | |
| 22 | + %th.span3 | |
| 23 | + | |
| 24 | + - @team.projects.each do |project| | |
| 25 | + %tr.project | |
| 26 | + %td | |
| 27 | + = link_to project.name_with_namespace, project_path(project) | |
| 28 | + %td | |
| 29 | + %span= @team.human_max_project_access(project) | |
| 30 | + | |
| 31 | + - if current_user.can?(:admin_user_team, @team) | |
| 32 | + %td.bgred | |
| 33 | + = link_to 'Edit max access', edit_team_project_path(@team, project), class: "btn small" | |
| 34 | + = 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" | ... | ... |
| ... | ... | @@ -0,0 +1,25 @@ |
| 1 | += render "teams/team_head" | |
| 2 | + | |
| 3 | +%h3.page_title | |
| 4 | + Team: #{@team.name} | |
| 5 | + | |
| 6 | +%fieldset | |
| 7 | + %legend Projects (#{@team.projects.count}) | |
| 8 | + = form_tag team_projects_path(@team), id: "assign_projects", class: "bulk_import", method: :post do | |
| 9 | + %table#projects_list | |
| 10 | + %thead | |
| 11 | + %tr | |
| 12 | + %th Project name | |
| 13 | + %th Max access | |
| 14 | + %th | |
| 15 | + - @team.projects.each do |project| | |
| 16 | + %tr.project | |
| 17 | + %td | |
| 18 | + = link_to project.name_with_namespace, team_project_path(@team, project) | |
| 19 | + %td | |
| 20 | + %span= @team.human_max_project_access(project) | |
| 21 | + %td | |
| 22 | + %tr | |
| 23 | + %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' | |
| 24 | + %td= select_tag :greatest_project_access, options_for_select(UserTeam.access_roles), {class: "project-access-select chosen span3" } | |
| 25 | + %td= submit_tag 'Add', class: "btn primary", id: :assign_projects_to_team | ... | ... |
| ... | ... | @@ -0,0 +1,11 @@ |
| 1 | += render "team_head" | |
| 2 | + | |
| 3 | += form_tag search_team_path(@team), method: :get, class: 'form-inline' do |f| | |
| 4 | + .padded | |
| 5 | + = label_tag :search do | |
| 6 | + %strong Looking for | |
| 7 | + .input | |
| 8 | + = search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search" | |
| 9 | + = submit_tag 'Search', class: "btn primary wide" | |
| 10 | +- if params[:search].present? | |
| 11 | + = render 'search/result' | ... | ... |
| ... | ... | @@ -0,0 +1,30 @@ |
| 1 | += render "team_head" | |
| 2 | + | |
| 3 | +.projects | |
| 4 | + .activities.span8 | |
| 5 | + = link_to dashboard_path, class: 'btn very_small' do | |
| 6 | + ← To dashboard | |
| 7 | + | |
| 8 | + %span.cgray Events and projects are filtered in scope of team | |
| 9 | + %hr | |
| 10 | + - if @events.any? | |
| 11 | + .content_list | |
| 12 | + - else | |
| 13 | + %p.nothing_here_message Projects activity will be displayed here | |
| 14 | + .loading.hide | |
| 15 | + .side.span4 | |
| 16 | + = render "projects", projects: @projects | |
| 17 | + %div | |
| 18 | + %span.rss-icon | |
| 19 | + = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do | |
| 20 | + = image_tag "rss_ui.png", title: "feed" | |
| 21 | + %strong News Feed | |
| 22 | + | |
| 23 | + %hr | |
| 24 | + .gitlab-promo | |
| 25 | + = link_to "Homepage", "http://gitlabhq.com" | |
| 26 | + = link_to "Blog", "http://blog.gitlabhq.com" | |
| 27 | + = link_to "@gitlabhq", "https://twitter.com/gitlabhq" | |
| 28 | + | |
| 29 | +:javascript | |
| 30 | + $(function(){ Pager.init(20, true); }); | ... | ... |
config/routes.rb
| ... | ... | @@ -21,7 +21,7 @@ Gitlab::Application.routes.draw do |
| 21 | 21 | project_root: Gitlab.config.gitolite.repos_path, |
| 22 | 22 | upload_pack: Gitlab.config.gitolite.upload_pack, |
| 23 | 23 | receive_pack: Gitlab.config.gitolite.receive_pack |
| 24 | - }), at: '/', constraints: lambda { |request| /[-\/\w\.-]+\.git\//.match(request.path_info) } | |
| 24 | + }), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) } | |
| 25 | 25 | |
| 26 | 26 | # |
| 27 | 27 | # Help |
| ... | ... | @@ -56,6 +56,7 @@ Gitlab::Application.routes.draw do |
| 56 | 56 | put :unblock |
| 57 | 57 | end |
| 58 | 58 | end |
| 59 | + | |
| 59 | 60 | resources :groups, constraints: { id: /[^\/]+/ } do |
| 60 | 61 | member do |
| 61 | 62 | put :project_update |
| ... | ... | @@ -63,18 +64,31 @@ Gitlab::Application.routes.draw do |
| 63 | 64 | delete :remove_project |
| 64 | 65 | end |
| 65 | 66 | end |
| 66 | - resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create] do | |
| 67 | - member do | |
| 68 | - get :team | |
| 69 | - put :team_update | |
| 67 | + | |
| 68 | + resources :teams, constraints: { id: /[^\/]+/ } do | |
| 69 | + scope module: :teams do | |
| 70 | + resources :members, only: [:edit, :update, :destroy, :new, :create] | |
| 71 | + resources :projects, only: [:edit, :update, :destroy, :new, :create], constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } | |
| 70 | 72 | end |
| 71 | 73 | end |
| 72 | - resources :team_members, only: [:edit, :update, :destroy] | |
| 74 | + | |
| 73 | 75 | resources :hooks, only: [:index, :create, :destroy] do |
| 74 | 76 | get :test |
| 75 | 77 | end |
| 78 | + | |
| 76 | 79 | resource :logs, only: [:show] |
| 77 | 80 | resource :resque, controller: 'resque', only: [:show] |
| 81 | + | |
| 82 | + resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, except: [:new, :create] do | |
| 83 | + member do | |
| 84 | + get :team | |
| 85 | + put :team_update | |
| 86 | + end | |
| 87 | + scope module: :projects, constraints: { id: /[^\/]+/ } do | |
| 88 | + resources :members, only: [:edit, :update, :destroy] | |
| 89 | + end | |
| 90 | + end | |
| 91 | + | |
| 78 | 92 | root to: "dashboard#index" |
| 79 | 93 | end |
| 80 | 94 | |
| ... | ... | @@ -108,7 +122,6 @@ Gitlab::Application.routes.draw do |
| 108 | 122 | get "dashboard/issues" => "dashboard#issues" |
| 109 | 123 | get "dashboard/merge_requests" => "dashboard#merge_requests" |
| 110 | 124 | |
| 111 | - | |
| 112 | 125 | # |
| 113 | 126 | # Groups Area |
| 114 | 127 | # |
| ... | ... | @@ -122,6 +135,24 @@ Gitlab::Application.routes.draw do |
| 122 | 135 | end |
| 123 | 136 | end |
| 124 | 137 | |
| 138 | + # | |
| 139 | + # Teams Area | |
| 140 | + # | |
| 141 | + resources :teams, constraints: { id: /[^\/]+/ } do | |
| 142 | + member do | |
| 143 | + get :issues | |
| 144 | + get :merge_requests | |
| 145 | + get :search | |
| 146 | + end | |
| 147 | + scope module: :teams do | |
| 148 | + resources :members, only: [:index, :new, :create, :edit, :update, :destroy] | |
| 149 | + resources :projects, only: [:index, :new, :create, :edit, :update, :destroy], constraints: { id: /[a-zA-Z.0-9_\-\/]+/ } | |
| 150 | + end | |
| 151 | + collection do | |
| 152 | + get :search | |
| 153 | + end | |
| 154 | + end | |
| 155 | + | |
| 125 | 156 | resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create] |
| 126 | 157 | |
| 127 | 158 | devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations } |
| ... | ... | @@ -238,6 +269,18 @@ Gitlab::Application.routes.draw do |
| 238 | 269 | end |
| 239 | 270 | end |
| 240 | 271 | |
| 272 | + scope module: :projects do | |
| 273 | + resources :teams, only: [] do | |
| 274 | + collection do | |
| 275 | + get :available | |
| 276 | + post :assign | |
| 277 | + end | |
| 278 | + member do | |
| 279 | + delete :resign | |
| 280 | + end | |
| 281 | + end | |
| 282 | + end | |
| 283 | + | |
| 241 | 284 | resources :notes, only: [:index, :create, :destroy] do |
| 242 | 285 | collection do |
| 243 | 286 | post :preview | ... | ... |
db/migrate/20121220064104_create_user_team_project_relationships.rb
0 → 100644
| ... | ... | @@ -0,0 +1,11 @@ |
| 1 | +class CreateUserTeamProjectRelationships < ActiveRecord::Migration | |
| 2 | + def change | |
| 3 | + create_table :user_team_project_relationships do |t| | |
| 4 | + t.integer :project_id | |
| 5 | + t.integer :user_team_id | |
| 6 | + t.integer :greatest_access | |
| 7 | + | |
| 8 | + t.timestamps | |
| 9 | + end | |
| 10 | + end | |
| 11 | +end | ... | ... |