Commit f40ad6ce403e4365e4b5515cfa593437babb1298

Authored by Dmitriy Zaporozhets
2 parents ca29617d 37383966

Merge pull request #5841 from Popl7/archiving_old_projects

Archiving old projects for feature request
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