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 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  
... ...