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,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 < ActiveRecord::Base | @@ -116,6 +116,8 @@ class Project < 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 < ActiveRecord::Base | @@ -132,7 +134,7 @@ class Project < 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 < ActiveRecord::Base | @@ -472,4 +474,12 @@ class Project < 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/schema.rb
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |