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 73 protected
74 74  
75 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 77 end
78 78 end
... ...
app/controllers/projects_controller.rb
... ... @@ -5,7 +5,7 @@ class ProjectsController < ApplicationController
5 5  
6 6 # Authorize
7 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 9 before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
10 10  
11 11 layout 'navless', only: [:new, :create, :fork]
... ... @@ -116,6 +116,24 @@ class ProjectsController < ApplicationController
116 116 end
117 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 137 private
120 138  
121 139 def set_title
... ...
app/helpers/search_helper.rb
... ... @@ -73,14 +73,14 @@ module SearchHelper
73 73  
74 74 # Autocomplete results for the current user's projects
75 75 def projects_autocomplete
76   - current_user.authorized_projects.map do |p|
  76 + current_user.authorized_projects.non_archived.map do |p|
77 77 { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
78 78 end
79 79 end
80 80  
81 81 # Autocomplete results for the current user's projects
82 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 84 { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
85 85 end
86 86 end
... ...
app/models/ability.rb
... ... @@ -59,31 +59,35 @@ class Ability
59 59  
60 60 # Rules based on role in project
61 61 if team.masters.include?(user)
62   - rules << project_master_rules
  62 + rules += project_master_rules
63 63  
64 64 elsif team.developers.include?(user)
65   - rules << project_dev_rules
  65 + rules += project_dev_rules
66 66  
67 67 elsif team.reporters.include?(user)
68   - rules << project_report_rules
  68 + rules += project_report_rules
69 69  
70 70 elsif team.guests.include?(user)
71   - rules << project_guest_rules
  71 + rules += project_guest_rules
72 72 end
73 73  
74 74 if project.public? || project.internal?
75   - rules << public_project_rules
  75 + rules += public_project_rules
76 76 end
77 77  
78 78 if project.owner == user || user.admin?
79   - rules << project_admin_rules
  79 + rules += project_admin_rules
80 80 end
81 81  
82 82 if project.group && project.group.has_owner?(user)
83   - rules << project_admin_rules
  83 + rules += project_admin_rules
84 84 end
85 85  
86   - rules.flatten
  86 + if project.archived?
  87 + rules -= project_archived_rules
  88 + end
  89 +
  90 + rules
87 91 end
88 92  
89 93 def public_project_rules
... ... @@ -125,6 +129,16 @@ class Ability
125 129 ]
126 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 142 def project_master_rules
129 143 project_dev_rules + [
130 144 :push_code_to_protected_branches,
... ... @@ -147,7 +161,8 @@ class Ability
147 161 :change_namespace,
148 162 :change_visibility_level,
149 163 :rename_project,
150   - :remove_project
  164 + :remove_project,
  165 + :archive_project
151 166 ]
152 167 end
153 168  
... ... @@ -160,7 +175,7 @@ class Ability
160 175  
161 176 # Only group owner and administrators can manage group
162 177 if group.has_owner?(user) || user.admin?
163   - rules << [
  178 + rules += [
164 179 :manage_group,
165 180 :manage_namespace
166 181 ]
... ... @@ -174,7 +189,7 @@ class Ability
174 189  
175 190 # Only namespace owner and administrators can manage it
176 191 if namespace.owner == user || user.admin?
177   - rules << [
  192 + rules += [
178 193 :manage_namespace
179 194 ]
180 195 end
... ...
app/models/project.rb
... ... @@ -116,6 +116,8 @@ class Project &lt; ActiveRecord::Base
116 116 scope :public_only, -> { where(visibility_level: PUBLIC) }
117 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 121 enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
120 122  
121 123 class << self
... ... @@ -132,7 +134,7 @@ class Project &lt; ActiveRecord::Base
132 134 end
133 135  
134 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 138 end
137 139  
138 140 def find_with_namespace(id)
... ... @@ -472,4 +474,12 @@ class Project &lt; ActiveRecord::Base
472 474 def visibility_level_field
473 475 visibility_level
474 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 485 end
... ...
app/views/dashboard/projects.html.haml
... ... @@ -82,6 +82,10 @@
82 82 = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project)
83 83 .project-info
84 84 .pull-right
  85 + - if project.archived?
  86 + %span.label
  87 + %i.icon-book
  88 + Archived
85 89 - project.labels.each do |label|
86 90 %span.label.label-info
87 91 %i.icon-tag
... ...
app/views/projects/_home_panel.html.haml
... ... @@ -7,6 +7,10 @@
7 7 %span.visibility-level-label
8 8 = visibility_level_icon(@project.visibility_level)
9 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 15 .span7
12 16 - unless empty_repo
... ...
app/views/projects/edit.html.haml
... ... @@ -98,6 +98,33 @@
98 98 %i.icon-chevron-down
99 99  
100 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 128 - if can?(current_user, :change_namespace, @project)
102 129 .ui-box.ui-box-danger
103 130 .title Transfer project
... ...
config/routes.rb
... ... @@ -170,6 +170,8 @@ Gitlab::Application.routes.draw do
170 170 member do
171 171 put :transfer
172 172 post :fork
  173 + post :archive
  174 + post :unarchive
173 175 get :autocomplete_sources
174 176 end
175 177  
... ...
db/migrate/20131129154016_add_archived_to_projects.rb 0 → 100644
... ... @@ -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
... ...
db/schema.rb
... ... @@ -192,6 +192,7 @@ ActiveRecord::Schema.define(version: 20131214224427) do
192 192 t.boolean "imported", default: false, null: false
193 193 t.string "import_url"
194 194 t.integer "visibility_level", default: 0, null: false
  195 + t.boolean "archived", default: false, null: false
195 196 end
196 197  
197 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 @@
  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 @@
  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 @@
  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 @@
  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 38 \ No newline at end of file
... ...
features/steps/shared/project.rb
... ... @@ -14,6 +14,13 @@ module SharedProject
14 14 @project.team << [@user, :master]
15 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 24 And 'project "Shop" has push event' do
18 25 @project = Project.find_by_name("Shop")
19 26  
... ...
spec/models/project_spec.rb
... ... @@ -21,6 +21,7 @@
21 21 # imported :boolean default(FALSE), not null
22 22 # import_url :string(255)
23 23 # visibility_level :integer default(0), not null
  24 +# archived :boolean default(FALSE), not null
24 25 #
25 26  
26 27 require 'spec_helper'
... ...
spec/requests/api/internal_spec.rb
... ... @@ -103,6 +103,33 @@ describe API::API do
103 103 end
104 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 133 context "deploy key" do
107 134 let(:key) { create(:deploy_key) }
108 135  
... ...