Commit 37383966ef3fada865d3d21a8ce7a3c640bbd11e
1 parent
99490159
Exists in
master
and in
4 other branches
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
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 |