redmine_version_priorities 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYRIGHT.txt +18 -0
- data/CREDITS.txt +4 -0
- data/GPL.txt +339 -0
- data/README.rdoc +34 -0
- data/Rakefile +39 -0
- data/VERSION +1 -0
- data/app/controllers/version_priorities_controller.rb +25 -0
- data/app/views/version_priorities/_prioritized_versions.html.erb +9 -0
- data/app/views/version_priorities/_version.html.erb +8 -0
- data/app/views/version_priorities/show.html.erb +30 -0
- data/assets/javascripts/jquery-1.4.2.min.js +154 -0
- data/assets/javascripts/jquery-ui-1.8.1.custom.min.js +241 -0
- data/assets/javascripts/jquery.ajax_queue.js +59 -0
- data/assets/javascripts/redmine_version_priorities.js +63 -0
- data/assets/stylesheets/redmine_version_priorities.css +17 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +3 -0
- data/init.rb +38 -0
- data/lang/en.yml +2 -0
- data/lib/redmine_version_priorities/patches/issue_patch.rb +27 -0
- data/lib/redmine_version_priorities/patches/query_patch.rb +37 -0
- data/lib/redmine_version_priorities/patches/version_patch.rb +50 -0
- data/test/functional/version_priorities_controller.rb +4 -0
- data/test/integration/edit_version_priorities_test.rb +79 -0
- data/test/integration/view_version_priorities_test.rb +90 -0
- data/test/test_helper.rb +52 -0
- data/test/unit/lib/redmine_version_priorities/patches/issue_patch_test.rb +39 -0
- data/test/unit/lib/redmine_version_priorities/patches/query_patch_test.rb +17 -0
- data/test/unit/lib/redmine_version_priorities/patches/version_patch_test.rb +17 -0
- metadata +96 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
jQuery(function($) {
|
2
|
+
$("#ajax-indicator").ajaxStart(function(){ $(this).show(); });
|
3
|
+
$("#ajax-indicator").ajaxStop(function(){ $(this).hide(); });
|
4
|
+
|
5
|
+
attachSortables = function() {
|
6
|
+
if (window.admin != true) {
|
7
|
+
return false;
|
8
|
+
}
|
9
|
+
|
10
|
+
attachPrioritizedSortables();
|
11
|
+
attachUnPrioritizedSortables();
|
12
|
+
},
|
13
|
+
|
14
|
+
attachPrioritizedSortables = function() {
|
15
|
+
$('#prioritized-versions ol').sortable({
|
16
|
+
cancel: 'a',
|
17
|
+
connectWith: ["#unprioritized-versions ul"],
|
18
|
+
placeholder: 'drop-accepted',
|
19
|
+
dropOnEmpty: true,
|
20
|
+
// Allow drag and drop inside the list
|
21
|
+
update: function (event, ui) {
|
22
|
+
if (ui.sender == null && event.target == this) {
|
23
|
+
saveOrder();
|
24
|
+
}
|
25
|
+
},
|
26
|
+
receive: function (event, ui) {
|
27
|
+
saveOrder();
|
28
|
+
}
|
29
|
+
});
|
30
|
+
},
|
31
|
+
|
32
|
+
attachUnPrioritizedSortables = function() {
|
33
|
+
$('#unprioritized-versions ul').sortable({
|
34
|
+
cancel: 'a',
|
35
|
+
connectWith: ["#prioritized-versions ol"],
|
36
|
+
placeholder: 'drop-accepted',
|
37
|
+
dropOnEmpty: true
|
38
|
+
});
|
39
|
+
|
40
|
+
},
|
41
|
+
|
42
|
+
saveOrder = function() {
|
43
|
+
var data = $('#prioritized-versions ol').sortable('serialize')
|
44
|
+
|
45
|
+
$.ajaxQueue.put('version_priorities.js', {
|
46
|
+
data: addAuthenticityToken(data),
|
47
|
+
success: function(response) {
|
48
|
+
$('#prioritized-versions').html(response);
|
49
|
+
attachPrioritizedSortables();
|
50
|
+
},
|
51
|
+
error: function(response) {
|
52
|
+
$("div.error").html("Error saving lists. Please refresh the page and try again.").show();
|
53
|
+
|
54
|
+
}
|
55
|
+
});
|
56
|
+
},
|
57
|
+
|
58
|
+
addAuthenticityToken = function(data) {
|
59
|
+
return data + '&authenticity_token=' + encodeURIComponent(window._token);
|
60
|
+
},
|
61
|
+
|
62
|
+
attachSortables();
|
63
|
+
});
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
.version {}
|
3
|
+
li.version { clear: both; }
|
4
|
+
.ui-sortable {cursor: move;}
|
5
|
+
|
6
|
+
#prioritized-versions ol { padding-left: 35px; }
|
7
|
+
#unprioritized-versions ul { list-style: none; padding-left: 15px;}
|
8
|
+
|
9
|
+
/* item elements */
|
10
|
+
li.version span.due_date { font-size: smaller; }
|
11
|
+
li.version table.progress { display: inline; position: relative; float: right; }
|
12
|
+
li.version p.pourcent { display: inline; position: relative; float: right; }
|
13
|
+
|
14
|
+
h1.version-priority-title { margin-bottom: 15px; }
|
15
|
+
|
16
|
+
|
17
|
+
.drop-accepted { background-color:#FFFFEE; height: 16px; margin-bottom:5px; border: 1px dashed #000000; }
|
data/config/routes.rb
ADDED
data/init.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'redmine'
|
2
|
+
|
3
|
+
Redmine::Plugin.register :redmine_version_priorities do
|
4
|
+
name 'Version Priorities'
|
5
|
+
author 'Eric Davis'
|
6
|
+
url 'https://projects.littlestreamsoftware.com/projects/version-priorities'
|
7
|
+
author_url 'http://www.littlestreamsoftware.com'
|
8
|
+
description 'Allows versions to be prioritized'
|
9
|
+
version '0.1.0'
|
10
|
+
|
11
|
+
requires_redmine :version_or_higher => '0.9.0'
|
12
|
+
|
13
|
+
menu(:top_menu,
|
14
|
+
:version_priorities,
|
15
|
+
{:controller => 'version_priorities', :action => 'show'},
|
16
|
+
:caption => :version_priorities_title,
|
17
|
+
:if => Proc.new {
|
18
|
+
User.current.logged?
|
19
|
+
})
|
20
|
+
|
21
|
+
permission(:view_version_priorities, {:version_priorities => [:show]}, :public => true)
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'dispatcher'
|
26
|
+
Dispatcher.to_prepare :redmine_version_priorities do
|
27
|
+
|
28
|
+
require_dependency 'issue'
|
29
|
+
Issue.send(:include, RedmineVersionPriorities::Patches::IssuePatch)
|
30
|
+
|
31
|
+
require_dependency 'query'
|
32
|
+
unless Query.included_modules.include?(RedmineVersionPriorities::Patches::QueryPatch)
|
33
|
+
Query.send(:include, RedmineVersionPriorities::Patches::QueryPatch)
|
34
|
+
end
|
35
|
+
|
36
|
+
require_dependency 'version'
|
37
|
+
Version.send(:include, RedmineVersionPriorities::Patches::VersionPatch)
|
38
|
+
end
|
data/lang/en.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module RedmineVersionPriorities
|
2
|
+
module Patches
|
3
|
+
module IssuePatch
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
base.class_eval do
|
9
|
+
unloadable
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
module InstanceMethods
|
17
|
+
def fixed_version_priority
|
18
|
+
if fixed_version && fixed_version.priority.present?
|
19
|
+
fixed_version
|
20
|
+
else
|
21
|
+
l(:label_none)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RedmineVersionPriorities
|
2
|
+
module Patches
|
3
|
+
module QueryPatch
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
base.class_eval do
|
9
|
+
unloadable
|
10
|
+
|
11
|
+
add_available_column(QueryColumn.new(:fixed_version_priority,
|
12
|
+
:sortable => ["#{Version.table_name}.priority IS NULL",
|
13
|
+
"#{Version.table_name}.priority",
|
14
|
+
"#{Version.table_name}.effective_date",
|
15
|
+
"#{Version.table_name}.name"],
|
16
|
+
:default_order => 'asc'))
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
# Setter for +available_columns+ that isn't provided by the core.
|
23
|
+
def available_columns=(v)
|
24
|
+
self.available_columns = (v)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Method to add a column to the +available_columns+ that isn't provided by the core.
|
28
|
+
def add_available_column(column)
|
29
|
+
self.available_columns << (column)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module InstanceMethods
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RedmineVersionPriorities
|
2
|
+
module Patches
|
3
|
+
module VersionPatch
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
base.class_eval do
|
9
|
+
unloadable
|
10
|
+
acts_as_list :column => 'priority'
|
11
|
+
|
12
|
+
named_scope :prioritized, :conditions => ["priority != 0 AND priority IS NOT NULL"], :order => 'priority ASC'
|
13
|
+
named_scope :unprioritized, :conditions => ["priority = 0 OR priority IS NULL"]
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Override acts_as_list to allow nil priorities
|
18
|
+
def add_to_list_bottom
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def reprioritize(order)
|
26
|
+
ordered_ids = order.collect(&:to_i) if order.present?
|
27
|
+
ordered_ids ||= []
|
28
|
+
|
29
|
+
# Removed versions
|
30
|
+
Version.visible.prioritized.each do |version|
|
31
|
+
unless ordered_ids.include?(version.id)
|
32
|
+
version.remove_from_list
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Sort
|
37
|
+
ordered_ids.each_with_index do |version_id, index|
|
38
|
+
version = Version.find_by_id(version_id)
|
39
|
+
version.insert_at( index + 1) if version
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
module InstanceMethods
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class EditVersionPrioritiesTest < ActionController::IntegrationTest
|
4
|
+
context "as a logged in user" do
|
5
|
+
setup do
|
6
|
+
@user = login_as_user
|
7
|
+
end
|
8
|
+
|
9
|
+
should_be_unauthorized("as a logged in user") {
|
10
|
+
put "/version_priorities.js"
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
context "as an administrator" do
|
15
|
+
setup do
|
16
|
+
@user = login_as_user
|
17
|
+
@user.update_attribute(:admin, true)
|
18
|
+
|
19
|
+
@project1 = Project.generate!
|
20
|
+
@project2 = Project.generate!
|
21
|
+
@version1 = Version.generate!(:project => @project1)
|
22
|
+
@version2 = Version.generate!(:project => @project2)
|
23
|
+
end
|
24
|
+
|
25
|
+
should "reprioritize the versions with a :html request" do
|
26
|
+
put "/version_priorities", :version => [@version2.id.to_s, @version1.id.to_s]
|
27
|
+
|
28
|
+
assert_response :redirect
|
29
|
+
follow_redirect!
|
30
|
+
assert_template 'version_priorities/show'
|
31
|
+
|
32
|
+
assert_equal 2, @version1.reload.priority
|
33
|
+
assert_equal 1, @version2.reload.priority
|
34
|
+
end
|
35
|
+
|
36
|
+
should "reprioritize the versions with a :js request" do
|
37
|
+
put "/version_priorities.js", :version => [@version2.id.to_s, @version1.id.to_s]
|
38
|
+
assert_response :success
|
39
|
+
|
40
|
+
assert_equal 2, @version1.reload.priority
|
41
|
+
assert_equal 1, @version2.reload.priority
|
42
|
+
end
|
43
|
+
|
44
|
+
should "remove versions not in the list anymore" do
|
45
|
+
@version2.insert_at(1)
|
46
|
+
assert_equal 1, @version2.reload.priority
|
47
|
+
|
48
|
+
put "/version_priorities.js", :version => [@version1.id.to_s]
|
49
|
+
assert_response :success
|
50
|
+
|
51
|
+
assert_equal nil, @version2.reload.priority
|
52
|
+
end
|
53
|
+
|
54
|
+
should "remove all versions when the list is empty" do
|
55
|
+
@version2.insert_at(1)
|
56
|
+
assert_equal 1, @version2.reload.priority
|
57
|
+
@version1.insert_at(2)
|
58
|
+
assert_equal 2, @version1.reload.priority
|
59
|
+
|
60
|
+
put "/version_priorities.js"
|
61
|
+
assert_response :success
|
62
|
+
|
63
|
+
assert_equal nil, @version1.reload.priority
|
64
|
+
assert_equal nil, @version2.reload.priority
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def login_as_user
|
69
|
+
user = User.generate!(:login => 'existing', :password => 'existing', :password_confirmation => 'existing', :admin => false)
|
70
|
+
visit "/login"
|
71
|
+
fill_in 'Login', :with => 'existing'
|
72
|
+
fill_in 'Password', :with => 'existing'
|
73
|
+
click_button 'login'
|
74
|
+
assert_response :success
|
75
|
+
assert User.current.logged?
|
76
|
+
|
77
|
+
user
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ViewVersionPrioritiesTest < ActionController::IntegrationTest
|
4
|
+
should "not show the menu item to users not logged in" do
|
5
|
+
visit "/"
|
6
|
+
assert_response :success
|
7
|
+
assert_select "#top-menu a.version-priorities", :count => 0
|
8
|
+
end
|
9
|
+
|
10
|
+
should "show the menu item to logged in users" do
|
11
|
+
login_as_user
|
12
|
+
|
13
|
+
visit "/"
|
14
|
+
assert_response :success
|
15
|
+
assert_select "#top-menu a.version-priorities", :count => 1
|
16
|
+
end
|
17
|
+
|
18
|
+
should "allow all logged in users to visit the prioritized versions panel" do
|
19
|
+
login_as_user
|
20
|
+
|
21
|
+
visit '/'
|
22
|
+
click_link "Version Priorities"
|
23
|
+
assert_response :success
|
24
|
+
assert_equal "/version_priorities", current_url
|
25
|
+
end
|
26
|
+
|
27
|
+
should "allow all logged in users to see the unprioritized versions" do
|
28
|
+
@user = login_as_user
|
29
|
+
@project1 = Project.generate!
|
30
|
+
@project2 = Project.generate!
|
31
|
+
@version1 = Version.generate!(:project => @project1)
|
32
|
+
@version2 = Version.generate!(:project => @project2)
|
33
|
+
@role = Role.generate!(:permissions => [:view_issues])
|
34
|
+
Member.generate!(:principal => @user, :roles => [@role], :project => @project1)
|
35
|
+
Member.generate!(:principal => @user, :roles => [@role], :project => @project2)
|
36
|
+
|
37
|
+
visit "/version_priorities"
|
38
|
+
|
39
|
+
assert_select "#unprioritized-versions" do
|
40
|
+
assert_select "li" do
|
41
|
+
assert_select "a", :text => /#{@version1.name}/
|
42
|
+
assert_select "span", :text => /#{@version1.description}/
|
43
|
+
end
|
44
|
+
|
45
|
+
assert_select "li" do
|
46
|
+
assert_select "a", :text => /#{@version2.name}/
|
47
|
+
assert_select "span", :text => /#{@version2.description}/
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
should "allow all logged in users to see the prioritized versions" do
|
54
|
+
@user = login_as_user
|
55
|
+
@project1 = Project.generate!
|
56
|
+
@project2 = Project.generate!
|
57
|
+
@version1 = Version.generate!(:project => @project1, :priority => 1)
|
58
|
+
@version2 = Version.generate!(:project => @project2, :priority => 2)
|
59
|
+
@role = Role.generate!(:permissions => [:view_issues])
|
60
|
+
Member.generate!(:principal => @user, :roles => [@role], :project => @project1)
|
61
|
+
Member.generate!(:principal => @user, :roles => [@role], :project => @project2)
|
62
|
+
|
63
|
+
visit "/version_priorities"
|
64
|
+
|
65
|
+
assert_select "#prioritized-versions" do
|
66
|
+
assert_select "li" do
|
67
|
+
assert_select "a", :text => /#{@version1.name}/
|
68
|
+
assert_select "span", :text => /#{@version1.description}/
|
69
|
+
end
|
70
|
+
|
71
|
+
assert_select "li" do
|
72
|
+
assert_select "a", :text => /#{@version2.name}/
|
73
|
+
assert_select "span", :text => /#{@version2.description}/
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
def login_as_user
|
80
|
+
user = User.generate!(:login => 'existing', :password => 'existing', :password_confirmation => 'existing', :admin => false)
|
81
|
+
visit "/login"
|
82
|
+
fill_in 'Login', :with => 'existing'
|
83
|
+
fill_in 'Password', :with => 'existing'
|
84
|
+
click_button 'login'
|
85
|
+
assert_response :success
|
86
|
+
assert User.current.logged?
|
87
|
+
|
88
|
+
user
|
89
|
+
end
|
90
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# Load the normal Rails helper
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
|
3
|
+
|
4
|
+
# Ensure that we are using the temporary fixture path
|
5
|
+
Engines::Testing.set_fixture_path
|
6
|
+
|
7
|
+
require "webrat"
|
8
|
+
|
9
|
+
Webrat.configure do |config|
|
10
|
+
config.mode = :rails
|
11
|
+
end
|
12
|
+
|
13
|
+
class Test::Unit::TestCase
|
14
|
+
def setup
|
15
|
+
setup_anonymous_role
|
16
|
+
setup_non_member_role
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup_anonymous_role
|
20
|
+
begin
|
21
|
+
@anon_role = Role.anonymous
|
22
|
+
rescue
|
23
|
+
@anon_role = Role.generate!
|
24
|
+
@anon_role.update_attribute(:builtin, Role::BUILTIN_ANONYMOUS)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def setup_non_member_role
|
29
|
+
begin
|
30
|
+
@anon_role = Role.non_member
|
31
|
+
rescue
|
32
|
+
@non_member_role = Role.generate!
|
33
|
+
@non_member_role.update_attribute(:builtin, Role::BUILTIN_NON_MEMBER)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.should_be_unauthorized(description, &block)
|
38
|
+
context description do
|
39
|
+
should "render the unauthorized template" do
|
40
|
+
instance_eval &block
|
41
|
+
assert_template 'common/403'
|
42
|
+
end
|
43
|
+
|
44
|
+
should "respond with unauthorized" do
|
45
|
+
instance_eval &block
|
46
|
+
assert_response 403
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|