From 695becc4cb017d76329efb55aae7ddb9a208895b Mon Sep 17 00:00:00 2001 From: Andrey Kumanyaev Date: Sat, 19 Jan 2013 21:32:09 +0400 Subject: [PATCH] Add teams into Public sections --- app/controllers/teams_controller.rb | 113 ++++++++++ app/views/teams/_filter.html.haml | 33 +++ app/views/teams/_projects.html.haml | 22 ++ app/views/teams/_team_head.html.haml | 19 ++ app/views/teams/edit.html.haml | 32 +++ app/views/teams/index.html.haml | 37 ++++ app/views/teams/issues.html.haml | 25 +++ app/views/teams/merge_requests.html.haml | 24 +++ app/views/teams/new.html.haml | 21 ++ app/views/teams/search.html.haml | 11 + app/views/teams/show.html.haml | 30 +++ config/routes.rb | 14 ++ features/steps/userteams/userteams.rb | 250 +++++++++++++++++++++++ features/teams/team.feature | 75 +++++++ 14 files changed, 706 insertions(+) create mode 100644 app/controllers/teams_controller.rb create mode 100644 app/views/teams/_filter.html.haml create mode 100644 app/views/teams/_projects.html.haml create mode 100644 app/views/teams/_team_head.html.haml create mode 100644 app/views/teams/edit.html.haml create mode 100644 app/views/teams/index.html.haml create mode 100644 app/views/teams/issues.html.haml create mode 100644 app/views/teams/merge_requests.html.haml create mode 100644 app/views/teams/new.html.haml create mode 100644 app/views/teams/search.html.haml create mode 100644 app/views/teams/show.html.haml create mode 100644 features/steps/userteams/userteams.rb create mode 100644 features/teams/team.feature diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb new file mode 100644 index 00000000..4e3703d7 --- /dev/null +++ b/app/controllers/teams_controller.rb @@ -0,0 +1,113 @@ +class TeamsController < ApplicationController + respond_to :html + layout 'user_team', only: [:show, :edit, :update, :destroy, :issues, :merge_requests, :search] + + before_filter :user_team, only: [:show, :edit, :update, :destroy, :issues, :merge_requests, :search] + before_filter :projects, only: [:show, :edit, :update, :destroy, :issues, :merge_requests, :search] + + # Authorize + before_filter :authorize_manage_user_team!, only: [:edit, :update] + before_filter :authorize_admin_user_team!, only: [:destroy] + + def index + @teams = UserTeam.all + end + + def show + @events = Event.in_projects(project_ids).limit(20).offset(params[:offset] || 0) + + respond_to do |format| + format.html + format.js + format.atom { render layout: false } + end + end + + def edit + + end + + def update + if user_team.update_attributes(params[:user_team]) + redirect_to team_path(user_team) + else + render action: :edit + end + end + + def destroy + user_team.destroy + redirect_to teams_path + end + + def new + @team = UserTeam.new + end + + def create + @team = UserTeam.new(params[:user_team]) + @team.owner = current_user unless params[:owner] + @team.path = @team.name.dup.parameterize if @team.name + + if @team.save + redirect_to team_path(@team) + else + render action: :new + end + end + + # Get authored or assigned open merge requests + def merge_requests + @merge_requests = MergeRequest.of_user_team(@user_team) + @merge_requests = FilterContext.new(@merge_requests, params).execute + @merge_requests = @merge_requests.recent.page(params[:page]).per(20) + end + + # Get only assigned issues + def issues + @issues = Issue.of_user_team(@user_team) + @issues = FilterContext.new(@issues, params).execute + @issues = @issues.recent.page(params[:page]).per(20) + @issues = @issues.includes(:author, :project) + + respond_to do |format| + format.html + format.atom { render layout: false } + end + end + + def search + result = SearchContext.new(project_ids, params).execute + + @projects = result[:projects] + @merge_requests = result[:merge_requests] + @issues = result[:issues] + @wiki_pages = result[:wiki_pages] + end + + protected + + def user_team + @user_team ||= UserTeam.find_by_path(params[:id]) + end + + def projects + @projects ||= user_team.projects.sorted_by_activity + end + + def project_ids + projects.map(&:id) + end + + def authorize_manage_user_team! + unless user_team.present? or can?(current_user, :manage_user_team, user_team) + return render_404 + end + end + + def authorize_admin_user_team! + unless user_team.owner == current_user || current_user.admin? + return render_404 + end + end +end diff --git a/app/views/teams/_filter.html.haml b/app/views/teams/_filter.html.haml new file mode 100644 index 00000000..8e358319 --- /dev/null +++ b/app/views/teams/_filter.html.haml @@ -0,0 +1,33 @@ += form_tag team_filter_path(entity), method: 'get' do + %fieldset.dashboard-search-filter + = search_field_tag "search", params[:search], { placeholder: 'Search', class: 'search-text-input' } + = button_tag type: 'submit', class: 'btn' do + %i.icon-search + + %fieldset + %legend Status: + %ul.nav.nav-pills.nav-stacked + %li{class: ("active" if !params[:status])} + = link_to team_filter_path(entity, status: nil) do + Open + %li{class: ("active" if params[:status] == 'closed')} + = link_to team_filter_path(entity, status: 'closed') do + Closed + %li{class: ("active" if params[:status] == 'all')} + = link_to team_filter_path(entity, status: 'all') do + All + + %fieldset + %legend Projects: + %ul.nav.nav-pills.nav-stacked + - @projects.each do |project| + - unless entities_per_project(project, entity).zero? + %li{class: ("active" if params[:project_id] == project.id.to_s)} + = link_to team_filter_path(entity, project_id: project.id) do + = project.name_with_namespace + %small.right= entities_per_project(project, entity) + + %fieldset + %hr + = link_to "Reset", team_filter_path(entity), class: 'btn right' + diff --git a/app/views/teams/_projects.html.haml b/app/views/teams/_projects.html.haml new file mode 100644 index 00000000..040d1ae9 --- /dev/null +++ b/app/views/teams/_projects.html.haml @@ -0,0 +1,22 @@ +.projects_box + %h5.title + Projects + %small + (#{projects.count}) + - if can? current_user, :manage_group, @group + %span.right + = link_to new_project_path(namespace_id: @group.id), class: "btn very_small info" do + %i.icon-plus + New Project + %ul.well-list + - if projects.blank? + %p.nothing_here_message This groups has no projects yet + - projects.each do |project| + %li + = link_to project_path(project), class: dom_class(project) do + %strong.well-title= truncate(project.name, length: 25) + %span.arrow + → + %span.last_activity + %strong Last activity: + %span= project_last_activity(project) diff --git a/app/views/teams/_team_head.html.haml b/app/views/teams/_team_head.html.haml new file mode 100644 index 00000000..53796623 --- /dev/null +++ b/app/views/teams/_team_head.html.haml @@ -0,0 +1,19 @@ +%ul.nav.nav-tabs + = nav_link(path: 'teams#show') do + = link_to team_path(@user_team), class: "activities-tab tab" do + %i.icon-home + Show + = nav_link(controller: [:members]) do + = link_to team_members_path(@user_team), class: "team-tab tab" do + %i.icon-user + Members + = nav_link(controller: [:projects]) do + = link_to team_projects_path(@user_team), class: "team-tab tab" do + %i.icon-briefcase + Projects + + - if can? current_user, :manage_user_team, @user_team + = nav_link(path: 'teams#edit', html_options: {class: 'right'}) do + = link_to edit_team_path(@user_team), class: "stat-tab tab " do + %i.icon-edit + Edit diff --git a/app/views/teams/edit.html.haml b/app/views/teams/edit.html.haml new file mode 100644 index 00000000..4c239e8f --- /dev/null +++ b/app/views/teams/edit.html.haml @@ -0,0 +1,32 @@ += render "team_head" + +%h3.page_title= "Edit Team #{@user_team.name}" +%hr += form_for @user_team, url: teams_path do |f| + - if @user_team.errors.any? + .alert-message.block-message.error + %span= @user_team.errors.full_messages.first + .clearfix + = f.label :name do + Team name is + .input + = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" + + .clearfix + = f.label :path do + Team path is + .input + = f.text_field :path, placeholder: "opensource", class: "xxlarge left" + .clearfix + .input.span3.center + = f.submit 'Save team changes', class: "btn primary" + .input.span3.center + = link_to 'Delete team', team_path(@user_team), method: :delete, confirm: "You are shure?", class: "btn danger" + %hr + .padded + %ul + %li Team is kind of directory for several projects + %li All created teams are private + %li People within a team see only projects they have access to + %li All projects of team will be stored in team directory + %li You will be able to move existing projects into team diff --git a/app/views/teams/index.html.haml b/app/views/teams/index.html.haml new file mode 100644 index 00000000..9ac54594 --- /dev/null +++ b/app/views/teams/index.html.haml @@ -0,0 +1,37 @@ +%h3.page_title + Teams + %small + list of all teams + + = link_to 'New Team', new_team_path, class: "btn small right" + %br + += form_tag search_teams_path, method: :get, class: 'form-inline' do + = text_field_tag :name, params[:name], class: "xlarge" + = submit_tag "Search", class: "btn submit primary" + +%table.teams_list + %thead + %tr + %th + Name + %i.icon-sort-down + %th Path + %th Projects + %th Members + %th Owner + %th + + - @teams.each do |team| + %tr + %td + %strong= link_to team.name, team_path(team) + %td= team.path + %td= link_to team.projects.count, team_projects_path(team) + %td= link_to team.members.count, team_members_path(team) + %td= link_to team.owner.name, team_member_path(team, team.owner) + %td + - if current_user.can?(:manage_user_team, team) + - if team.owner == current_user + = link_to "Destroy", team_path(team), method: :delete, confirm: "You are shure?", class: "danger btn small right" + = link_to "Edit", edit_team_path(team), class: "btn small right" diff --git a/app/views/teams/issues.html.haml b/app/views/teams/issues.html.haml new file mode 100644 index 00000000..3c17e85a --- /dev/null +++ b/app/views/teams/issues.html.haml @@ -0,0 +1,25 @@ += render "team_head" + +%h3.page_title + Issues + %small (in Team projects assigned to Team members) + %small.right #{@issues.total_count} issues + +%hr +.row + .span3 + = render 'filter', entity: 'issue' + .span9 + - if @issues.any? + - @issues.group_by(&:project).each do |group| + %div.ui-box + - @project = group[0] + %h5.title + = link_to_project @project + %ul.well-list.issues_table + - group[1].each do |issue| + = render(partial: 'issues/show', locals: {issue: issue}) + %hr + = paginate @issues, theme: "gitlab" + - else + %p.nothing_here_message Nothing to show here diff --git a/app/views/teams/merge_requests.html.haml b/app/views/teams/merge_requests.html.haml new file mode 100644 index 00000000..c9af529e --- /dev/null +++ b/app/views/teams/merge_requests.html.haml @@ -0,0 +1,24 @@ +%h3.page_title + Merge Requests + %small (authored by or assigned to Team members) + %small.right #{@merge_requests.total_count} merge requests + +%hr +.row + .span3 + = render 'filter', entity: 'merge_request' + .span9 + - if @merge_requests.any? + - @merge_requests.group_by(&:project).each do |group| + .ui-box + - @project = group[0] + %h5.title + = link_to_project @project + %ul.well-list + - group[1].each do |merge_request| + = render(partial: 'merge_requests/merge_request', locals: {merge_request: merge_request}) + %hr + = paginate @merge_requests, theme: "gitlab" + + - else + %h3.nothing_here_message Nothing to show here diff --git a/app/views/teams/new.html.haml b/app/views/teams/new.html.haml new file mode 100644 index 00000000..d8312e0e --- /dev/null +++ b/app/views/teams/new.html.haml @@ -0,0 +1,21 @@ +%h3.page_title New Team +%hr += form_for @team, url: teams_path do |f| + - if @team.errors.any? + .alert-message.block-message.error + %span= @team.errors.full_messages.first + .clearfix + = f.label :name do + Team name is + .input + = f.text_field :name, placeholder: "Ex. OpenSource", class: "xxlarge left" +   + = f.submit 'Create team', class: "btn primary" + %hr + .padded + %ul + %li Team is kind of directory for several projects + %li All created teams are private + %li People within a team see only projects they have access to + %li All projects of team will be stored in team directory + %li You will be able to move existing projects into team diff --git a/app/views/teams/search.html.haml b/app/views/teams/search.html.haml new file mode 100644 index 00000000..601f2d57 --- /dev/null +++ b/app/views/teams/search.html.haml @@ -0,0 +1,11 @@ += render "team_head" + += form_tag search_team_path(@user_team), method: :get, class: 'form-inline' do |f| + .padded + = label_tag :search do + %strong Looking for + .input + = search_field_tag :search, params[:search], placeholder: "issue 143", class: "input-xxlarge search-text-input", id: "dashboard_search" + = submit_tag 'Search', class: "btn primary wide" +- if params[:search].present? + = render 'search/result' diff --git a/app/views/teams/show.html.haml b/app/views/teams/show.html.haml new file mode 100644 index 00000000..9acbf3e1 --- /dev/null +++ b/app/views/teams/show.html.haml @@ -0,0 +1,30 @@ += render "team_head" + +.projects + .activities.span8 + = link_to dashboard_path, class: 'btn very_small' do + ← To dashboard +   + %span.cgray Events and projects are filtered in scope of team + %hr + - if @events.any? + .content_list + - else + %p.nothing_here_message Projects activity will be displayed here + .loading.hide + .side.span4 + = render "projects", projects: @projects + %div + %span.rss-icon + = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do + = image_tag "rss_ui.png", title: "feed" + %strong News Feed + + %hr + .gitlab-promo + = link_to "Homepage", "http://gitlabhq.com" + = link_to "Blog", "http://blog.gitlabhq.com" + = link_to "@gitlabhq", "https://twitter.com/gitlabhq" + +:javascript + $(function(){ Pager.init(20, true); }); diff --git a/config/routes.rb b/config/routes.rb index d364f805..4a6b0d0b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -130,6 +130,20 @@ Gitlab::Application.routes.draw do end end + resources :teams do + member do + get :issues + get :merge_requests + get :search + post :delegate_projects + delete :relegate_project + put :update_access + end + collection do + get :search + end + end + resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create] devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations } diff --git a/features/steps/userteams/userteams.rb b/features/steps/userteams/userteams.rb new file mode 100644 index 00000000..59ec3d2d --- /dev/null +++ b/features/steps/userteams/userteams.rb @@ -0,0 +1,250 @@ +class Userteams < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + + When 'I do not have teams with me' do + UserTeam.with_member(current_user).destroy_all + end + + Then 'I should see dashboard page without teams info block' do + page.has_no_css?(".teams_box").must_equal true + end + + When 'I have teams with my membership' do + team = create :user_team + team.add_member(current_user, UserTeam.access_roles["Master"], true) + end + + Then 'I should see dashboard page with teams information block' do + page.should have_css(".teams_box") + end + + When 'exist user teams' do + team = create :user_team + team.add_member(current_user, UserTeam.access_roles["Master"], true) + end + + And 'I click on "All teams" link' do + click_link("All Teams") + end + + Then 'I should see "All teams" page' do + current_path.should == teams_path + end + + And 'I should see exist teams in teams list' do + team = UserTeam.last + find_in_list(".teams_list tr", team).must_equal true + end + + When 'I click to "New team" link' do + click_link("New Team") + end + + And 'I submit form with new team info' do + fill_in 'name', with: 'gitlab' + click_button 'Create team' + end + + Then 'I should be redirected to new team page' do + team = UserTeam.last + current_path.should == team_path(team) + end + + When 'I have teams with projects and members' do + team = create :user_team + @project = create :project + team.add_member(current_user, UserTeam.access_roles["Master"], true) + team.assign_to_project(@project, UserTeam.access_roles["Master"]) + @event = create(:closed_issue_event, project: @project) + end + + When 'I visit team page' do + visit team_path(UserTeam.last) + end + + Then 'I should see projects list' do + page.should have_css(".projects_box") + projects_box = find(".projects_box") + projects_box.should have_content(@project.name) + end + + And 'project from team has issues assigned to me' do + team = UserTeam.last + team.projects.each do |project| + project.issues << create(:issue, assignee: current_user) + end + end + + When 'I visit team issues page' do + team = UserTeam.last + visit issues_team_path(team) + end + + Then 'I should see issues from this team assigned to me' do + team = UserTeam.last + team.projects.each do |project| + project.issues.assigned(current_user).each do |issue| + page.should have_content issue.title + end + end + end + + Given 'I have team with projects and members' do + team = create :user_team + project = create :project + user = create :user + team.add_member(current_user, UserTeam.access_roles["Master"], true) + team.add_member(user, UserTeam.access_roles["Developer"], false) + team.assign_to_project(project, UserTeam.access_roles["Master"]) + end + + Given 'project from team has issues assigned to teams members' do + team = UserTeam.last + team.projects.each do |project| + team.members.each do |member| + project.issues << create(:issue, assignee: member) + end + end + end + + Then 'I should see issues from this team assigned to teams members' do + team = UserTeam.last + team.projects.each do |project| + team.members.each do |member| + project.issues.assigned(member).each do |issue| + page.should have_content issue.title + end + end + end + end + + Given 'project from team has merge requests assigned to me' do + team = UserTeam.last + team.projects.each do |project| + team.members.each do |member| + 3.times { project.merge_requests << create(:merge_request, assignee: member) } + end + end + end + + When 'I visit team merge requests page' do + team = UserTeam.last + visit merge_requests_team_path(team) + end + + Then 'I should see merge requests from this team assigned to me' do + team = UserTeam.last + team.projects.each do |project| + team.members.each do |member| + project.issues.assigned(member).each do |merge_request| + page.should have_content merge_request.title + end + end + end + end + + Given 'project from team has merge requests assigned to team members' do + team = UserTeam.last + team.projects.each do |project| + team.members.each do |member| + 3.times { project.merge_requests << create(:merge_request, assignee: member) } + end + end + end + + Then 'I should see merge requests from this team assigned to me' do + team = UserTeam.last + team.projects.each do |project| + team.members.each do |member| + project.issues.assigned(member).each do |merge_request| + page.should have_content merge_request.title + end + end + end + end + + Given 'I have new user "John"' do + create :user, name: "John" + end + + When 'I visit team people page' do + team = UserTeam.last + visit team_members_path(team) + end + + And 'I select user "John" from list with role "Reporter"' do + pending 'step not implemented' + end + + Then 'I should see user "John" in team list' do + user = User.find_by_name("John") + team_members_list = find(".team-table") + team_members_list.should have_content user.name + end + + And 'I have my own project without teams' do + project = create :project, creator: current_user + end + + And 'I visit my team page' do + team = UserTeam.last + visit team_path(team) + end + + When 'I click on link "Projects"' do + click_link "Projects" + end + + Then 'I should see form with my own project in avaliable projects list' do + project = current_user.projects.first + projects_select = find("#project_ids") + projects_select.should have_content(project.name) + end + + When 'I submit form with selected project and max access' do + project = current_user.projects.first + within "#team_projects" do + select project.name, :from => "project_ids" + select "Reporter", :from => "greatest_project_access" + end + click_button "Add" + end + + Then 'I should see my own project in team projects list' do + project = current_user.projects.first + projects = all("table .project") + projects.each do |project_row| + project_row.should have_content(project.name) + end + end + + When 'I click link "New Team Member"' do + click_link "New Team Member" + end + + protected + + def current_team + @user_team ||= Team.first + end + + def project + current_team.projects.first + end + + def assigned_to_user key, user + project.send(key).where(assignee_id: user) + end + + def find_in_list(selector, item) + members_list = all(selector) + entered = false + members_list.each do |member_item| + entered = true if member_item.has_content?(item.name) + end + entered + end + +end diff --git a/features/teams/team.feature b/features/teams/team.feature new file mode 100644 index 00000000..d914313e --- /dev/null +++ b/features/teams/team.feature @@ -0,0 +1,75 @@ +Feature: UserTeams + Background: + Given I sign in as a user + And I own project "Shop" + And project "Shop" has push event + + Scenario: No teams, no dashboard info block + When I do not have teams with me + And I visit dashboard page + Then I should see dashboard page without teams info block + + Scenario: I should see teams info block + When I have teams with my membership + And I visit dashboard page + Then I should see dashboard page with teams information block + + Scenario: I should see all teams list + When exist user teams + And I visit dashboard page + And I click on "All teams" link + Then I should see "All teams" page + And I should see exist teams in teams list + + Scenario: I should can create new team + When I have teams with my membership + And I visit dashboard page + When I click to "New team" link + And I submit form with new team info + Then I should be redirected to new team page + + Scenario: I should see team dashboard list + When I have teams with projects and members + When I visit team page + Then I should see projects list + + Scenario: I should see team issues list + Given I have team with projects and members + And project from team has issues assigned to me + When I visit team issues page + Then I should see issues from this team assigned to me + + Scenario: I should see teams members issues list + Given I have team with projects and members + Given project from team has issues assigned to teams members + When I visit team issues page + Then I should see issues from this team assigned to teams members + + Scenario: I should see team merge requests list + Given I have team with projects and members + Given project from team has merge requests assigned to me + When I visit team merge requests page + Then I should see merge requests from this team assigned to me + + Scenario: I should see teams members merge requests list + Given I have team with projects and members + Given project from team has merge requests assigned to team members + When I visit team merge requests page + Then I should see merge requests from this team assigned to me + + Scenario: I should add user to projects in Team + Given I have team with projects and members + Given I have new user "John" + When I visit team people page + When I click link "New Team Member" + And I select user "John" from list with role "Reporter" + Then I should see user "John" in team list + + Scenario: I should assign my team to my own project + Given I have team with projects and members + And I have my own project without teams + And I visit my team page + When I click on link "Projects" + Then I should see form with my own project in avaliable projects list + When I submit form with selected project and max access + Then I should see my own project in team projects list