Commit 37383966ef3fada865d3d21a8ce7a3c640bbd11e

Authored by Steven Thonus
1 parent 99490159

Archiving old projects; archived projects aren't shown on dashboard

features for archive projects
abilities for archived project
other abilities for archive projects

only limit commits and merges for archived projects

ability changed to prohibited actions on archived projects

added spec and feature tests for archive projects

changed search bar not to include archived projects
app/controllers/dashboard_controller.rb
@@ -73,6 +73,6 @@ class DashboardController < ApplicationController @@ -73,6 +73,6 @@ class DashboardController < ApplicationController
73 protected 73 protected
74 74
75 def load_projects 75 def load_projects
76 - @projects = current_user.authorized_projects.sorted_by_activity 76 + @projects = current_user.authorized_projects.sorted_by_activity.non_archived
77 end 77 end
78 end 78 end
app/controllers/projects_controller.rb
@@ -5,7 +5,7 @@ class ProjectsController < ApplicationController @@ -5,7 +5,7 @@ class ProjectsController < ApplicationController
5 5
6 # Authorize 6 # Authorize
7 before_filter :authorize_read_project!, except: [:index, :new, :create] 7 before_filter :authorize_read_project!, except: [:index, :new, :create]
8 - before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer] 8 + before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
9 before_filter :require_non_empty_project, only: [:blob, :tree, :graph] 9 before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
10 10
11 layout 'navless', only: [:new, :create, :fork] 11 layout 'navless', only: [:new, :create, :fork]
@@ -116,6 +116,24 @@ class ProjectsController < ApplicationController @@ -116,6 +116,24 @@ class ProjectsController < ApplicationController
116 end 116 end
117 end 117 end
118 118
  119 + def archive
  120 + return access_denied! unless can?(current_user, :archive_project, project)
  121 + project.archive!
  122 +
  123 + respond_to do |format|
  124 + format.html { redirect_to @project }
  125 + end
  126 + end
  127 +
  128 + def unarchive
  129 + return access_denied! unless can?(current_user, :archive_project, project)
  130 + project.unarchive!
  131 +
  132 + respond_to do |format|
  133 + format.html { redirect_to @project }
  134 + end
  135 + end
  136 +
119 private 137 private
120 138
121 def set_title 139 def set_title
app/helpers/search_helper.rb
@@ -73,14 +73,14 @@ module SearchHelper @@ -73,14 +73,14 @@ module SearchHelper
73 73
74 # Autocomplete results for the current user's projects 74 # Autocomplete results for the current user's projects
75 def projects_autocomplete 75 def projects_autocomplete
76 - current_user.authorized_projects.map do |p| 76 + current_user.authorized_projects.non_archived.map do |p|
77 { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } 77 { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
78 end 78 end
79 end 79 end
80 80
81 # Autocomplete results for the current user's projects 81 # Autocomplete results for the current user's projects
82 def public_projects_autocomplete 82 def public_projects_autocomplete
83 - Project.public_or_internal_only(current_user).map do |p| 83 + Project.public_or_internal_only(current_user).non_archived.map do |p|
84 { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } 84 { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
85 end 85 end
86 end 86 end
app/models/ability.rb
@@ -59,31 +59,35 @@ class Ability @@ -59,31 +59,35 @@ class Ability
59 59
60 # Rules based on role in project 60 # Rules based on role in project
61 if team.masters.include?(user) 61 if team.masters.include?(user)
62 - rules << project_master_rules 62 + rules += project_master_rules
63 63
64 elsif team.developers.include?(user) 64 elsif team.developers.include?(user)
65 - rules << project_dev_rules 65 + rules += project_dev_rules
66 66
67 elsif team.reporters.include?(user) 67 elsif team.reporters.include?(user)
68 - rules << project_report_rules 68 + rules += project_report_rules
69 69
70 elsif team.guests.include?(user) 70 elsif team.guests.include?(user)
71 - rules << project_guest_rules 71 + rules += project_guest_rules
72 end 72 end
73 73
74 if project.public? || project.internal? 74 if project.public? || project.internal?
75 - rules << public_project_rules 75 + rules += public_project_rules
76 end 76 end
77 77
78 if project.owner == user || user.admin? 78 if project.owner == user || user.admin?
79 - rules << project_admin_rules 79 + rules += project_admin_rules
80 end 80 end
81 81
82 if project.group && project.group.has_owner?(user) 82 if project.group && project.group.has_owner?(user)
83 - rules << project_admin_rules 83 + rules += project_admin_rules
84 end 84 end
85 85
86 - rules.flatten 86 + if project.archived?
  87 + rules -= project_archived_rules
  88 + end
  89 +
  90 + rules
87 end 91 end
88 92
89 def public_project_rules 93 def public_project_rules
@@ -125,6 +129,16 @@ class Ability @@ -125,6 +129,16 @@ class Ability
125 ] 129 ]
126 end 130 end
127 131
  132 + def project_archived_rules
  133 + [
  134 + :write_merge_request,
  135 + :push_code,
  136 + :push_code_to_protected_branches,
  137 + :modify_merge_request,
  138 + :admin_merge_request
  139 + ]
  140 + end
  141 +
128 def project_master_rules 142 def project_master_rules
129 project_dev_rules + [ 143 project_dev_rules + [
130 :push_code_to_protected_branches, 144 :push_code_to_protected_branches,
@@ -147,7 +161,8 @@ class Ability @@ -147,7 +161,8 @@ class Ability
147 :change_namespace, 161 :change_namespace,
148 :change_visibility_level, 162 :change_visibility_level,
149 :rename_project, 163 :rename_project,
150 - :remove_project 164 + :remove_project,
  165 + :archive_project
151 ] 166 ]
152 end 167 end
153 168
@@ -160,7 +175,7 @@ class Ability @@ -160,7 +175,7 @@ class Ability
160 175
161 # Only group owner and administrators can manage group 176 # Only group owner and administrators can manage group
162 if group.has_owner?(user) || user.admin? 177 if group.has_owner?(user) || user.admin?
163 - rules << [ 178 + rules += [
164 :manage_group, 179 :manage_group,
165 :manage_namespace 180 :manage_namespace
166 ] 181 ]
@@ -174,7 +189,7 @@ class Ability @@ -174,7 +189,7 @@ class Ability
174 189
175 # Only namespace owner and administrators can manage it 190 # Only namespace owner and administrators can manage it
176 if namespace.owner == user || user.admin? 191 if namespace.owner == user || user.admin?
177 - rules << [ 192 + rules += [
178 :manage_namespace 193 :manage_namespace
179 ] 194 ]
180 end 195 end
app/models/project.rb
@@ -116,6 +116,8 @@ class Project &lt; ActiveRecord::Base @@ -116,6 +116,8 @@ class Project &lt; ActiveRecord::Base
116 scope :public_only, -> { where(visibility_level: PUBLIC) } 116 scope :public_only, -> { where(visibility_level: PUBLIC) }
117 scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) } 117 scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) }
118 118
  119 + scope :non_archived, -> { where(archived: false) }
  120 +
119 enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab 121 enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
120 122
121 class << self 123 class << self
@@ -132,7 +134,7 @@ class Project &lt; ActiveRecord::Base @@ -132,7 +134,7 @@ class Project &lt; ActiveRecord::Base
132 end 134 end
133 135
134 def search query 136 def search query
135 - joins(:namespace).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%") 137 + joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%")
136 end 138 end
137 139
138 def find_with_namespace(id) 140 def find_with_namespace(id)
@@ -472,4 +474,12 @@ class Project &lt; ActiveRecord::Base @@ -472,4 +474,12 @@ class Project &lt; ActiveRecord::Base
472 def visibility_level_field 474 def visibility_level_field
473 visibility_level 475 visibility_level
474 end 476 end
  477 +
  478 + def archive!
  479 + update_attribute(:archived, true)
  480 + end
  481 +
  482 + def unarchive!
  483 + update_attribute(:archived, false)
  484 + end
475 end 485 end
app/views/dashboard/projects.html.haml
@@ -82,6 +82,10 @@ @@ -82,6 +82,10 @@
82 = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project) 82 = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project)
83 .project-info 83 .project-info
84 .pull-right 84 .pull-right
  85 + - if project.archived?
  86 + %span.label
  87 + %i.icon-book
  88 + Archived
85 - project.labels.each do |label| 89 - project.labels.each do |label|
86 %span.label.label-info 90 %span.label.label-info
87 %i.icon-tag 91 %i.icon-tag
app/views/projects/_home_panel.html.haml
@@ -7,6 +7,10 @@ @@ -7,6 +7,10 @@
7 %span.visibility-level-label 7 %span.visibility-level-label
8 = visibility_level_icon(@project.visibility_level) 8 = visibility_level_icon(@project.visibility_level)
9 = visibility_level_label(@project.visibility_level) 9 = visibility_level_label(@project.visibility_level)
  10 + - if @project.archived?
  11 + %span.visibility-level-label
  12 + %i.icon-book
  13 + Archived
10 14
11 .span7 15 .span7
12 - unless empty_repo 16 - unless empty_repo
app/views/projects/edit.html.haml
@@ -98,6 +98,33 @@ @@ -98,6 +98,33 @@
98 %i.icon-chevron-down 98 %i.icon-chevron-down
99 99
100 .js-toggle-visibility-container.hide 100 .js-toggle-visibility-container.hide
  101 + - if can? current_user, :archive_project, @project
  102 + .ui-box.ui-box-danger
  103 + .title
  104 + - if @project.archived?
  105 + Unarchive project
  106 + - else
  107 + Archive project
  108 + .ui-box-body
  109 + - if @project.archived?
  110 + %p
  111 + Unarchiving the project will mark its repository as active.
  112 + %br
  113 + The project can be committed to.
  114 + %br
  115 + %strong Once active this project shows up in the search and on the dashboard.
  116 + = link_to 'Unarchive', unarchive_project_path(@project), confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be comitted to again.", method: :post, class: "btn btn-remove"
  117 + - else
  118 + %p
  119 + Archiving the project will mark its repository as read-only.
  120 + %br
  121 + It is hidden from the dashboard and doesn't show up in searches.
  122 + %br
  123 + %strong Archived projects cannot be committed to!
  124 + = link_to 'Archive', archive_project_path(@project), confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to.", method: :post, class: "btn btn-remove"
  125 + - else
  126 + %p.nothing_here_message Only the project owner can archive a project
  127 +
101 - if can?(current_user, :change_namespace, @project) 128 - if can?(current_user, :change_namespace, @project)
102 .ui-box.ui-box-danger 129 .ui-box.ui-box-danger
103 .title Transfer project 130 .title Transfer project
config/routes.rb
@@ -170,6 +170,8 @@ Gitlab::Application.routes.draw do @@ -170,6 +170,8 @@ Gitlab::Application.routes.draw do
170 member do 170 member do
171 put :transfer 171 put :transfer
172 post :fork 172 post :fork
  173 + post :archive
  174 + post :unarchive
173 get :autocomplete_sources 175 get :autocomplete_sources
174 end 176 end
175 177
db/migrate/20131129154016_add_archived_to_projects.rb 0 → 100644
@@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
  1 +class AddArchivedToProjects < ActiveRecord::Migration
  2 + def change
  3 + add_column :projects, :archived, :boolean, default: false, null: false
  4 + end
  5 +end
@@ -192,6 +192,7 @@ ActiveRecord::Schema.define(version: 20131214224427) do @@ -192,6 +192,7 @@ ActiveRecord::Schema.define(version: 20131214224427) do
192 t.boolean "imported", default: false, null: false 192 t.boolean "imported", default: false, null: false
193 t.string "import_url" 193 t.string "import_url"
194 t.integer "visibility_level", default: 0, null: false 194 t.integer "visibility_level", default: 0, null: false
  195 + t.boolean "archived", default: false, null: false
195 end 196 end
196 197
197 add_index "projects", ["creator_id"], name: "index_projects_on_owner_id", using: :btree 198 add_index "projects", ["creator_id"], name: "index_projects_on_owner_id", using: :btree
features/dashboard/archived_projects.feature 0 → 100644
@@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
  1 +Feature: Dashboard with archived projects
  2 + Background:
  3 + Given I sign in as a user
  4 + And I own project "Shop"
  5 + And I own project "Forum"
  6 + And project "Forum" is archived
  7 + And I visit dashboard page
  8 +
  9 + Scenario: I should see non-archived projects on dashboard
  10 + Then I should see "Shop" project link
  11 + And I should not see "Forum" project link
  12 +
  13 + Scenario: I should see all projects on projects page
  14 + And I visit dashboard projects page
  15 + Then I should see "Shop" project link
  16 + And I should see "Forum" project link
features/project/archived_projects.feature 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +Feature: Project Archived
  2 + Background:
  3 + Given I sign in as a user
  4 + And I own project "Shop"
  5 + And I own project "Forum"
  6 +
  7 + Scenario: I should not see archived on project page of not-archive project
  8 + And project "Forum" is archived
  9 + And I visit project "Shop" page
  10 + Then I should not see "Archived"
  11 +
  12 + Scenario: I should see archived on project page of archive project
  13 + And project "Forum" is archived
  14 + And I visit project "Forum" page
  15 + Then I should see "Archived"
  16 +
  17 + Scenario: I should not see archived on projects page with no archived projects
  18 + And I visit dashboard projects page
  19 + Then I should not see "Archived"
  20 +
  21 + Scenario: I should see archived on projects page with archived projects
  22 + And project "Forum" is archived
  23 + And I visit dashboard projects page
  24 + Then I should see "Archived"
  25 +
  26 + Scenario: I archive project
  27 + When project "Shop" has push event
  28 + And I visit project "Shop" page
  29 + And I visit edit project "Shop" page
  30 + And I set project archived
  31 + Then I should see "Archived"
  32 +
  33 + Scenario: I unarchive project
  34 + When project "Shop" has push event
  35 + And project "Shop" is archived
  36 + And I visit project "Shop" page
  37 + And I visit edit project "Shop" page
  38 + And I set project unarchived
  39 + Then I should not see "Archived"
features/steps/dashboard/dashboard_with_archived_projects.rb 0 → 100644
@@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
  1 +class DashboardWithArchivedProjects < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedPaths
  4 + include SharedProject
  5 +
  6 + When 'project "Forum" is archived' do
  7 + project = Project.find_by_name "Forum"
  8 + project.update_attribute(:archived, true)
  9 + end
  10 +
  11 + Then 'I should see "Shop" project link' do
  12 + page.should have_link "Shop"
  13 + end
  14 +
  15 + Then 'I should not see "Forum" project link' do
  16 + page.should_not have_link "Forum"
  17 + end
  18 +
  19 + Then 'I should see "Forum" project link' do
  20 + page.should have_link "Forum"
  21 + end
  22 +end
features/steps/project/project_archived.rb 0 → 100644
@@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
  1 +class ProjectArchived < Spinach::FeatureSteps
  2 + include SharedAuthentication
  3 + include SharedProject
  4 + include SharedPaths
  5 +
  6 + When 'project "Forum" is archived' do
  7 + project = Project.find_by_name "Forum"
  8 + project.update_attribute(:archived, true)
  9 + end
  10 +
  11 + When 'project "Shop" is archived' do
  12 + project = Project.find_by_name "Shop"
  13 + project.update_attribute(:archived, true)
  14 + end
  15 +
  16 + When 'I visit project "Forum" page' do
  17 + project = Project.find_by_name "Forum"
  18 + visit project_path(project)
  19 + end
  20 +
  21 + Then 'I should not see "Archived"' do
  22 + page.should_not have_content "Archived"
  23 + end
  24 +
  25 + Then 'I should see "Archived"' do
  26 + page.should have_content "Archived"
  27 + end
  28 +
  29 + When 'I set project archived' do
  30 + click_link "Archive"
  31 + end
  32 +
  33 + When 'I set project unarchived' do
  34 + click_link "Unarchive"
  35 + end
  36 +
  37 +end
0 \ No newline at end of file 38 \ No newline at end of file
features/steps/shared/project.rb
@@ -14,6 +14,13 @@ module SharedProject @@ -14,6 +14,13 @@ module SharedProject
14 @project.team << [@user, :master] 14 @project.team << [@user, :master]
15 end 15 end
16 16
  17 + # Create another specific project called "Forum"
  18 + And 'I own project "Forum"' do
  19 + @project = Project.find_by_name "Forum"
  20 + @project ||= create(:project_with_code, name: "Forum", namespace: @user.namespace, path: 'forum_project')
  21 + @project.team << [@user, :master]
  22 + end
  23 +
17 And 'project "Shop" has push event' do 24 And 'project "Shop" has push event' do
18 @project = Project.find_by_name("Shop") 25 @project = Project.find_by_name("Shop")
19 26
spec/models/project_spec.rb
@@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
21 # imported :boolean default(FALSE), not null 21 # imported :boolean default(FALSE), not null
22 # import_url :string(255) 22 # import_url :string(255)
23 # visibility_level :integer default(0), not null 23 # visibility_level :integer default(0), not null
  24 +# archived :boolean default(FALSE), not null
24 # 25 #
25 26
26 require 'spec_helper' 27 require 'spec_helper'
spec/requests/api/internal_spec.rb
@@ -103,6 +103,33 @@ describe API::API do @@ -103,6 +103,33 @@ describe API::API do
103 end 103 end
104 end 104 end
105 105
  106 + context "archived project" do
  107 + let(:personal_project) { create(:project, namespace: user.namespace) }
  108 +
  109 + before do
  110 + project.team << [user, :developer]
  111 + project.archive!
  112 + end
  113 +
  114 + context "git pull" do
  115 + it do
  116 + pull(key, project)
  117 +
  118 + response.status.should == 200
  119 + response.body.should == 'true'
  120 + end
  121 + end
  122 +
  123 + context "git push" do
  124 + it do
  125 + push(key, project)
  126 +
  127 + response.status.should == 200
  128 + response.body.should == 'false'
  129 + end
  130 + end
  131 + end
  132 +
106 context "deploy key" do 133 context "deploy key" do
107 let(:key) { create(:deploy_key) } 134 let(:key) { create(:deploy_key) }
108 135