Commit f40ad6ce403e4365e4b5515cfa593437babb1298
Exists in
master
and in
4 other branches
Merge pull request #5841 from Popl7/archiving_old_projects
Archiving old projects for feature request
Showing
18 changed files
with
251 additions
and
16 deletions
Show diff stats
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 < 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 < 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 < 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
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 | ... | ... |
| ... | ... | @@ -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 | ... | ... |
| ... | ... | @@ -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 | ... | ... |
| ... | ... | @@ -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
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 | ... | ... |