Project

General

Profile

Feature #337 » private_issues.v.0.2.patch

Paul Zubarev, 2009-02-01 01:01

View differences:

app/controllers/projects_controller.rb
97 97
      @open_issues_by_tracker = Issue.count(:group => :tracker,
98 98
                                            :include => [:project, :status, :tracker],
99 99
                                            :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
100
      @private_issues_by_tracker = Issue.count(:group => :tracker,
101
                                               :include => [:project, :status, :tracker],
102
                                               :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=? AND #{Issue.table_name}.private=?", false, true])
100 103
      @total_issues_by_tracker = Issue.count(:group => :tracker,
101 104
                                            :include => [:project, :status, :tracker],
102 105
                                            :conditions => cond)
......
247 250
    @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
248 251

  
249 252
    events = @activity.events(@date_from, @date_to)
250
    
253

  
254
    # The private issues should be removed from events
255
    events.each do |event|
256
      events.delete(event) if event.kind_of?(Issue) && !event.visible?(User.current, Project.find(event.project))
257
    end
258

  
251 259
    respond_to do |format|
252 260
      format.html { 
253 261
        @events_by_day = events.group_by(&:event_date)
app/models/issue.rb
262 262
      yield
263 263
    end
264 264
  end
265

  
266
  def visible? (usr, project)
267
     private==false || private==true &&
268
                         (
269
                           usr.allowed_to?(:view_private_issues, project) ||
270
                           author == usr
271
                         )
272
  end
265 273
  
266 274
  def to_s
267 275
    "#{tracker} ##{id}: #{subject}"
app/views/issues/_form.rhtml
41 41
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
42 42
</div>
43 43

  
44
<% if User.current.allowed_to?(:add_private_issues, @project) %>
45
<p><%=f.check_box :private %></p>
46
<% end%>
47

  
44 48
<div style="clear:both;"> </div>
45 49
<%= render :partial => 'form_custom_fields' %>
46 50

  
app/views/issues/_list.rhtml
11 11
	</tr></thead>
12 12
	<tbody>
13 13
	<% issues.each do |issue| -%>
14
	<tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= css_issue_classes(issue) %>">
15
	    <td class="checkbox"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
16
		<td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
17
        <% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %>
18
	</tr>
14
          <% if issue.visible? User.current, @project %>
15
        	<tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= css_issue_classes(issue) %>">
16
              <td class="checkbox"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
17
              <td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
18
              <% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %>
19
            </tr>
20
          <% end %>
19 21
	<% end -%>
20 22
	</tbody>
21 23
</table>
app/views/issues/_list_simple.rhtml
8 8
		</tr></thead>
9 9
		<tbody>	
10 10
		<% for issue in issues %>
11
		<tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= css_issue_classes(issue) %>">
12
			<td class="id">
13
			    <%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;') %>
14
				<%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %>
15
			</td>
16
			<td><%=h issue.project.name %> - <%= issue.tracker.name %><br />
17
                <%= issue.status.name %> - <%= format_time(issue.updated_on) %></td>
18
			<td class="subject">
19
                <%= link_to h(issue.subject), :controller => 'issues', :action => 'show', :id => issue %>
20
            </td>
21
		</tr>
11
          <% if issue.visible? User.current, @project %>
12
            <tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= css_issue_classes(issue) %>">
13
                <td class="id">
14
                    <%= check_box_tag("ids[]", issue.id, false, :style => 'display:none;') %>
15
                    <%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %>
16
                </td>
17
                <td><%=h issue.project.name %> - <%= issue.tracker.name %><br />
18
                    <%= issue.status.name %> - <%= format_time(issue.updated_on) %></td>
19
                <td class="subject">
20
                    <%= link_to h(issue.subject), :controller => 'issues', :action => 'show', :id => issue %>
21
                </td>
22
            </tr>
23
          <% end %>
22 24
		<% end %>
23 25
		</tbody>
24 26
	</table>
app/views/issues/show.rhtml
1
<% if @issue.visible? User.current, @project%>
2

  
1 3
<div class="contextual">
2 4
<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
3 5
<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
......
42 44
    <% if @issue.estimated_hours %>
43 45
    <td class="estimated-hours"><b><%=l(:field_estimated_hours)%>:</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
44 46
    <% end %>
47
    <% if @issue.private %>
48
    <td class="private-issue"><b><%=l(:field_private)%></b></td>
49
    <% end %>
45 50
</tr>
46 51
<tr>
47 52
<% n = 0 -%>
......
124 129
    <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
125 130
    <%= stylesheet_link_tag 'scm' %>
126 131
<% end %>
132

  
133
<% else %>
134
  <p class="nodata"><%=l(:label_access_denied)%> </p>
135
<% end %>
app/views/projects/show.rhtml
26 26
                                                :set_filter => 1, 
27 27
                                                "tracker_id" => tracker.id %>:
28 28
      <%= @open_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_open_issues, @open_issues_by_tracker[tracker] || 0) %>
29
      (<%= @private_issues_by_tracker[tracker] || 0 %> <%= lwr(:label_private_issues, @private_issues_by_tracker[tracker] || 0)%>)
29 30
      <%= l(:label_on) %> <%= @total_issues_by_tracker[tracker] || 0 %></li>
30 31
    <% end %>
31 32
    </ul>
db/migrate/102_add_issues_private_flag.rb
1
class AddIssuesPrivateFlag < ActiveRecord::Migration
2
  def self.up
3
    add_column :issues, :private, :boolean, :default => false, :null => false
4
  end
5

  
6
  def self.down
7
    remove_column :issues, :private
8
  end
9
end
lang/en.yml
186 186
field_default_value: Default value
187 187
field_comments_sorting: Display comments
188 188
field_parent_title: Parent page
189
field_private: Private issue
189 190

  
190 191
setting_app_title: Application title
191 192
setting_app_subtitle: Application subtitle
......
233 234
permission_manage_versions: Manage versions
234 235
permission_manage_categories: Manage issue categories
235 236
permission_add_issues: Add issues
237
permission_add_private_issues: Add private issues
238
permission_view_private_issues: View private issues
236 239
permission_edit_issues: Edit issues
237 240
permission_manage_issue_relations: Manage issue relations
238 241
permission_add_issue_notes: Add notes
......
285 288
project_module_repository: Repository
286 289
project_module_boards: Boards
287 290

  
291
label_access_denied: Access denied
288 292
label_user: User
289 293
label_user_plural: Users
290 294
label_user_new: New user
......
397 401
label_public_projects: Public projects
398 402
label_open_issues: open
399 403
label_open_issues_plural: open
404
label_private_issues: private
405
label_private_issues_plural: private
400 406
label_closed_issues: closed
401 407
label_closed_issues_plural: closed
402 408
label_total: Total
lang/ru.yml
179 179
field_port: Порт
180 180
field_possible_values: Возможные значения
181 181
field_priority: Приоритет
182
field_private: Конфиденциальная задача
182 183
field_project: Проект
183 184
field_redirect_existing_links: Перенаправить существующие ссылки
184 185
field_regexp: Регулярное выражение
......
227 228
gui_validation_error_plural: %d ошибок
228 229

  
229 230
label_activity: Активность
231
label_access_denied: Доступ запрещен
230 232
label_add_another_file: Добавить ещё один файл
231 233
label_added_time_by: Добавил(а) %s %s назад
232 234
label_added: добавлено
......
430 432
label_open_issues_plural5: открыто
431 433
label_open_issues_plural: открыто
432 434
label_open_issues: открыт
435
label_private_issues_plural2: конфиденциальных
436
label_private_issues_plural5: конфиденциальных
437
label_private_issues_plural: конфиденциальных
438
label_private_issues: конфиденциальная
433 439
label_optional_description: Описание (опционально)
434 440
label_options: Опции
435 441
label_overall_activity: Сводная активность
......
622 628
permission_view_documents: Просмотр документов
623 629
permission_edit_project: Редактирование проектов
624 630
permission_add_issue_notes: Добавление примечаний
631
permission_add_private_issues: Добавление конфиденциальных задач
632
permission_view_private_issues: Просмотр конфиденциальных задач
625 633
permission_save_queries: Сохранение запросов
626 634
permission_view_wiki_pages: Просмотр wiki
627 635
permission_rename_wiki_pages: Переименование страниц wiki
lib/redmine.rb
35 35
                                  :queries => :index,
36 36
                                  :reports => :issue_report}, :public => true                    
37 37
    map.permission :add_issues, {:issues => :new}
38
    map.permission :add_private_issues, {:issues => :new}
39
    map.permission :view_private_issues, {:projects => [:changelog, :roadmap],
40
                                  :issues => [:index, :changes, :show, :context_menu],
41
                                  :versions => [:show, :status_by],
42
                                  :queries => :index,
43
                                  :reports => :issue_report}
38 44
    map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit]}
39 45
    map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
40 46
    map.permission :add_issue_notes, {:issues => [:edit, :reply]}
test/fixtures/issues.yml
108 108
  start_date: <%= 10.days.ago.to_s(:db) %>
109 109
  due_date: <%= Date.today.to_s(:db) %>
110 110
  lock_version: 0
111
  
111
issues_008: 
112
  created_on: <%= 5.days.ago.to_date.to_s(:db) %>
113
  project_id: 1
114
  updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
115
  priority_id: 5
116
  subject: Private Issue on project 2
117
  id: 8
118
  fixed_version_id: 
119
  category_id: 
120
  description: Priavte Issue on project 2
121
  tracker_id: 1
122
  assigned_to_id: 
123
  author_id: 7
124
  status_id: 1
125
  private: 1
126
  
test/fixtures/members.yml
30 30
  project_id: 5
31 31
  role_id: 1
32 32
  user_id: 2
33
members_006: 
34
  id: 6
35
  created_on: 2006-07-19 19:35:37 +02:00
36
  project_id: 1
37
  role_id: 3
38
  user_id: 7
33 39
  
test/fixtures/roles.yml
45 45
    - :browse_repository
46 46
    - :manage_repository
47 47
    - :view_changesets
48
    - :add_private_issues
49
    - :view_private_issues
48 50

  
49 51
  position: 1
50 52
roles_002: 
......
87 89
    - :manage_files
88 90
    - :browse_repository
89 91
    - :view_changesets
92
    - :view_private_issues
90 93

  
91 94
  position: 2
92 95
roles_003: 
......
124 127
    - :manage_files
125 128
    - :browse_repository
126 129
    - :view_changesets
130
    - :add_private_issues
127 131

  
128 132
  position: 3
129 133
roles_004: 
......
154 158
    - :browse_repository
155 159
    - :view_changesets
156 160

  
161

  
157 162
  position: 4
158 163
roles_005: 
159 164
  name: Anonymous
......
173 178
    - :view_changesets
174 179

  
175 180
  position: 5
176
  
181
  
test/fixtures/users.yml
96 96
  mail_notification: false
97 97
  login: ''
98 98
  type: AnonymousUser
99

  
99
users_007:
100
  id: 7
101
  created_on: 2009-01-27 01:20:19 +03:00
102
  status: 1
103
  last_login_on:
104
  language: ru
105
  hashed_password: 7feb7657aa7a7bf5aef3414a5084875f27192415
106
  updated_on: 2009-01-27 19:33:19 +03:00
107
  admin: false
108
  mail: vasja@somenet.foo
109
  lastname: Pupkin
110
  firstname: Vasja
111
  auth_source_id:
112
  mail_notification: true
113
  login: vasiliy
114
  type: User
100 115
  
test/functional/issues_controller_test.rb
260 260
    assert_not_nil assigns(:issue)
261 261
  end
262 262

  
263
  def test_show_private_issue_by_manager
264
    @request.session[:user_id] = 2
265
    get :show, :id => 8
266
    assert_response :success
267
    assert_tag :td, :attributes => { :class => 'private-issue'}
268
  end
269

  
270
  def test_show_private_issue_by_admin
271
    @request.session[:user_id] = 1
272
    get :show, :id => 8
273
    assert_response :success
274
    assert_tag :td, :attributes => { :class => 'private-issue'}
275
  end
276

  
277
  def test_show_private_issue_by_developer
278
    @request.session[:user_id] = 3
279
    get :show, :id => 8
280
    assert_response :success
281
    # Developer can view private issues
282
    assert_tag :td, :attributes => { :class => 'private-issue'}
283
  end
284

  
285
  def test_show_private_issue_by_issue_author
286
    # issue author always can browse his issue
287
    @request.session[:user_id] = 7
288
    get :show, :id => 8
289
    assert_response :success
290
    assert_tag :input, :attributes => { :name => 'issue[private]'}
291
    assert_tag :td, :attributes => { :class => 'private-issue'}
292
  end
293

  
294
  def test_show_private_issue_by_non_member
295
    @request.session[:user_id] = 4
296
    get :show, :id => 8
297
    assert_response :success
298
    assert_no_tag :input, :attributes => { :name => 'issue[private]'}
299
    assert_no_tag :td, :attributes => { :class => 'private-issue'}
300
    assert_tag :p, :attributes => { :class => 'nodata'}
301
  end
302

  
303
  def test_show_private_issue_by_anonymous
304
    get :show, :id => 8
305
    assert_response :success
306
    assert_no_tag :input, :attributes => { :name => 'issue[private]'}
307
    assert_no_tag :td, :attributes => { :class => 'private-issue'}
308
    assert_tag :p, :attributes => { :class => 'nodata'}
309
  end
310

  
263 311
  def test_get_new
264 312
    @request.session[:user_id] = 2
265 313
    get :new, :project_id => 1, :tracker_id => 1
......
270 318
                                                 :value => 'Default string' }
271 319
  end
272 320

  
321
  def test_get_new_manager
322
    # Manager have add_private_issue permission
323
    @request.session[:user_id] = 2
324
    get :new, :project_id => 1, :tracker_id => 1
325
    assert_response :success
326
    assert_template 'new'
327
    assert_tag :input, :attributes => { :name => 'issue[private]'}
328
  end
329

  
330
  def test_get_new_developer
331
    @request.session[:user_id] = 3
332
    get :new, :project_id => 1, :tracker_id => 1
333
    assert_response :success
334
    assert_template 'new'
335
    # Developer can't change issue type
336
    assert_no_tag :input, :attributes => { :name => 'issue[private]'}
337
  end
338

  
339
  def test_get_new_reporter
340
    @request.session[:user_id] = 7
341
    get :new, :project_id => 1, :tracker_id => 1
342
    assert_response :success
343
    assert_template 'new'
344
    # Reporter can add private issue
345
    assert_tag :input, :attributes => { :name => 'issue[private]'}
346
  end
347

  
348
  def test_get_new_admin
349
    @request.session[:user_id] = 1
350
    get :new, :project_id => 1, :tracker_id => 1
351
    assert_response :success
352
    assert_template 'new'
353
    assert_tag :input, :attributes => { :name => 'issue[private]'}
354
  end
355

  
273 356
  def test_get_new_without_tracker_id
274 357
    @request.session[:user_id] = 2
275 358
    get :new, :project_id => 1
test/functional/projects_controller_test.rb
247 247
                 }
248 248
               }
249 249
  end
250

  
251
  #private issue are not visible for Anonymous user in global Activity
252
  def test_private_issue_global_activity_for_anonymous
253
    get :activity
254
    assert_response :success
255
    assert_template 'activity'
256
    assert_not_nil assigns(:events_by_day)
257

  
258
    assert_no_tag :tag => "h3",
259
               :content => /#{5.day.ago.to_date.day}/,
260
               :sibling => { :tag => "dl",
261
                 :child => { :tag => "dt",
262
                   :attributes => { :class => /issue/ },
263
                   :child => { :tag => "a",
264
                     :content => /#{Issue.find(8).subject}/,
265
                   }
266
                 }
267
               }
268
  end
269

  
270
  def test_private_issue_global_activity_for_manager
271
    @request.session[:user_id] = 2 # manager
272
    get :activity
273
    assert_response :success
274
    assert_template 'activity'
275
    assert_not_nil assigns(:events_by_day)
276

  
277
    assert_tag :tag => "h3",
278
               :content => /#{5.day.ago.to_date.day}/,
279
               :sibling => { :tag => "dl",
280
                 :child => { :tag => "dt",
281
                   :attributes => { :class => /issue/ },
282
                   :child => { :tag => "a",
283
                     :content => /#{Issue.find(8).subject}/,
284
                   }
285
                 }
286
               }
287
  end
250 288
  
289
  def test_private_issue_global_activity_for_developer
290
    @request.session[:user_id] = 3 # developer
291
    get :activity
292
    assert_response :success
293
    assert_template 'activity'
294
    assert_not_nil assigns(:events_by_day)
295

  
296
    assert_tag :tag => "h3",
297
               :content => /#{5.day.ago.to_date.day}/,
298
               :sibling => { :tag => "dl",
299
                 :child => { :tag => "dt",
300
                   :attributes => { :class => /issue/ },
301
                   :child => { :tag => "a",
302
                     :content => /#{Issue.find(8).subject}/,
303
                   }
304
                 }
305
               }
306
  end
307

  
308
  def test_private_issue_global_activity_for_non_member
309
    @request.session[:user_id] = 4 # does not have any role in project #1
310
    get :activity
311
    assert_response :success
312
    assert_template 'activity'
313
    assert_not_nil assigns(:events_by_day)
314

  
315
    assert_no_tag :tag => "h3",
316
               :content => /#{5.day.ago.to_date.day}/,
317
               :sibling => { :tag => "dl",
318
                 :child => { :tag => "dt",
319
                   :attributes => { :class => /issue/ },
320
                   :child => { :tag => "a",
321
                     :content => /#{Issue.find(8).subject}/,
322
                   }
323
                 }
324
               }
325
  end
326

  
251 327
  def test_user_activity
252 328
    get :activity, :user_id => 2
253 329
    assert_response :success
test/unit/issue_test.rb
203 203
    assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
204 204
    assert !Issue.new(:due_date => nil).overdue?
205 205
  end
206

  
207
  def test_visible
208
    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 2, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'test_private_create', :description => 'IssueTest#test_private_create', :estimated_hours => '5:30', :private => true)
209
    assert issue.save
210
    issue.reload
211
    assert_equal true, issue.private
212
    # Test fixtures contain "add_private_issues" and "view_private_issues"
213
    # permissions for Manager role and only "view_private_issues" for Developer.
214
    # User with id #3 in project with id #1 has Developer role
215
    assert_equal true, issue.visible?(User.find(3), Project.find(1))
216
    # User with id #2 in project with id #1 has Manager role
217
    assert_equal true, issue.visible?(User.find(2), Project.find(1))
218
    # User with id #6 has Anonymous role
219
    assert_equal false, issue.visible?(User.find(6), Project.find(1))
220
    # User with id #4 does not have any role in project #1
221
    assert_equal false, issue.visible?(User.find(4), Project.find(1))
222
  end
206 223
end
test/unit/project_test.rb
81 81
  
82 82
  def test_destroy
83 83
    # 2 active members
84
    assert_equal 2, @ecookbook.members.size
84
    assert_equal 3, @ecookbook.members.size
85 85
    # and 1 is locked
86
    assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
86
    assert_equal 4, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
87 87
    # some boards
88 88
    assert @ecookbook.boards.any?
89 89
    
(5-5/17)