Feature #28234 » 0001-Generalize-issues-imports.patch
app/controllers/imports_controller.rb | ||
---|---|---|
18 | 18 |
require 'csv' |
19 | 19 | |
20 | 20 |
class ImportsController < ApplicationController |
21 |
menu_item :issues |
|
22 | ||
23 | 21 |
before_action :find_import, :only => [:show, :settings, :mapping, :run] |
24 |
before_action :authorize_global |
|
22 |
before_action :authorize_import |
|
23 | ||
24 |
layout :import_layout |
|
25 | 25 | |
26 | 26 |
helper :issues |
27 | 27 |
helper :queries |
28 | 28 | |
29 | 29 |
def new |
30 |
@import = import_type.new |
|
30 | 31 |
end |
31 | 32 | |
32 | 33 |
def create |
33 |
@import = IssueImport.new
|
|
34 |
@import = import_type.new
|
|
34 | 35 |
@import.user = User.current |
35 | 36 |
@import.file = params[:file] |
36 | 37 |
@import.set_default_settings |
... | ... | |
94 | 95 |
end |
95 | 96 |
end |
96 | 97 | |
98 |
def current_menu(project) |
|
99 |
if import_layout == 'admin' |
|
100 |
nil |
|
101 |
else |
|
102 |
:application_menu |
|
103 |
end |
|
104 |
end |
|
105 | ||
97 | 106 |
private |
98 | 107 | |
99 | 108 |
def find_import |
... | ... | |
119 | 128 |
def max_items_per_request |
120 | 129 |
5 |
121 | 130 |
end |
131 | ||
132 |
def import_layout |
|
133 |
import_type && import_type.layout || 'base' |
|
134 |
end |
|
135 | ||
136 |
def menu_items |
|
137 |
menu_item = import_type ? import_type.menu_item : nil |
|
138 | ||
139 |
{ self.controller_name.to_sym => { :actions => {}, :default => menu_item } } |
|
140 |
end |
|
141 | ||
142 |
def authorize_import |
|
143 |
return render_404 unless import_type |
|
144 |
return render_403 unless import_type.authorized?(User.current) |
|
145 |
end |
|
146 | ||
147 |
def import_type |
|
148 |
return @import_type if defined? @import_type |
|
149 | ||
150 |
@import_type = |
|
151 |
if @import |
|
152 |
@import.class |
|
153 |
else |
|
154 |
type = Object.const_get(params[:type]) rescue nil |
|
155 |
type && type < Import ? type : nil |
|
156 |
end |
|
157 |
end |
|
122 | 158 |
end |
app/helpers/imports_helper.rb | ||
---|---|---|
18 | 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | 19 | |
20 | 20 |
module ImportsHelper |
21 |
def import_title |
|
22 |
l(:"label_import_#{import_partial_prefix}") |
|
23 |
end |
|
24 | ||
25 |
def import_partial_prefix |
|
26 |
@import.class.name.sub('Import', '').underscore.pluralize |
|
27 |
end |
|
28 | ||
21 | 29 |
def options_for_mapping_select(import, field, options={}) |
22 | 30 |
tags = "".html_safe |
23 | 31 |
blank_text = options[:required] ? "-- #{l(:actionview_instancetag_blank_option)} --" : " ".html_safe |
app/models/import.rb | ||
---|---|---|
35 | 35 |
'%d-%m-%Y' |
36 | 36 |
] |
37 | 37 | |
38 |
def self.menu_item |
|
39 |
nil |
|
40 |
end |
|
41 | ||
42 |
def self.layout |
|
43 |
'base' |
|
44 |
end |
|
45 | ||
46 |
def self.authorized?(user) |
|
47 |
user.admin? |
|
48 |
end |
|
49 | ||
38 | 50 |
def initialize(*args) |
39 | 51 |
super |
40 | 52 |
self.settings ||= {} |
app/models/issue_import.rb | ||
---|---|---|
16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | 17 | |
18 | 18 |
class IssueImport < Import |
19 |
def self.menu_item |
|
20 |
:issues |
|
21 |
end |
|
22 | ||
23 |
def self.authorized?(user) |
|
24 |
user.allowed_to?(:import_issues, nil, :global => true) |
|
25 |
end |
|
19 | 26 | |
20 | 27 |
# Returns the objects that were imported |
21 | 28 |
def saved_objects |
app/views/imports/_fields_mapping.html.erb → app/views/imports/_issues_fields_mapping.html.erb | ||
---|---|---|
54 | 54 |
</p> |
55 | 55 |
<% @custom_fields.each do |field| %> |
56 | 56 |
<p> |
57 |
<label for="import_mapping_cf_<% field.id %>"><%= field.name %></label> |
|
57 |
<label for="import_mapping_cf_<%= field.id %>"><%= field.name %></label>
|
|
58 | 58 |
<%= mapping_select_tag @import, "cf_#{field.id}" %> |
59 | 59 |
</p> |
60 | 60 |
<% end %> |
app/views/imports/_issues_mapping.html.erb | ||
---|---|---|
1 |
<fieldset class="box tabular"> |
|
2 |
<legend><%= l(:label_fields_mapping) %></legend> |
|
3 |
<div id="fields-mapping"> |
|
4 |
<%= render :partial => 'issues_fields_mapping' %> |
|
5 |
</div> |
|
6 |
</fieldset> |
|
7 | ||
8 |
<%= javascript_tag do %> |
|
9 |
$('#fields-mapping').on('change', '#import_mapping_project_id, #import_mapping_tracker', function(){ |
|
10 |
$.ajax({ |
|
11 |
url: '<%= import_mapping_path(@import, :format => 'js') %>', |
|
12 |
type: 'post', |
|
13 |
data: $('#import-form').serialize() |
|
14 |
}); |
|
15 |
}); |
|
16 |
<% end %> |
app/views/imports/_issues_mapping.js.erb | ||
---|---|---|
1 |
$('#fields-mapping').html('<%= escape_javascript(render :partial => 'issues_fields_mapping') %>'); |
app/views/imports/_issues_saved_objects.html.erb | ||
---|---|---|
1 |
<ul id="saved-items"> |
|
2 |
<% saved_objects.each do |issue| %> |
|
3 |
<li><%= link_to_issue issue %></li> |
|
4 |
<% end %> |
|
5 |
</ul> |
|
6 | ||
7 |
<p><%= link_to l(:label_issue_view_all), issues_path(:set_filter => 1, :status_id => '*', :issue_id => saved_objects.map(&:id).join(',')) %></p> |
app/views/imports/_issues_sidebar.html.erb | ||
---|---|---|
1 |
<% content_for :sidebar do %> |
|
2 |
<%= render :partial => 'issues/sidebar' %> |
|
3 |
<% end %> |
app/views/imports/mapping.html.erb | ||
---|---|---|
1 |
<h2><%= l(:label_import_issues) %></h2>
|
|
1 |
<h2><%= import_title %></h2>
|
|
2 | 2 | |
3 | 3 |
<%= form_tag(import_mapping_path(@import), :id => "import-form") do %> |
4 |
<fieldset class="box tabular"> |
|
5 |
<legend><%= l(:label_fields_mapping) %></legend> |
|
6 |
<div id="fields-mapping"> |
|
7 |
<%= render :partial => 'fields_mapping' %> |
|
8 |
</div> |
|
9 |
</fieldset> |
|
4 |
<%= render :partial => "#{import_partial_prefix}_mapping" %> |
|
10 | 5 | |
11 | 6 |
<div class="autoscroll"> |
12 | 7 |
<fieldset class="box"> |
... | ... | |
28 | 23 |
</p> |
29 | 24 |
<% end %> |
30 | 25 | |
31 |
<% content_for :sidebar do %> |
|
32 |
<%= render :partial => 'issues/sidebar' %> |
|
33 |
<% end %> |
|
34 | ||
26 |
<%= render :partial => "#{import_partial_prefix}_sidebar" %> |
|
35 | 27 | |
36 | 28 |
<%= javascript_tag do %> |
37 | 29 |
$(document).ready(function() { |
38 |
$('#fields-mapping').on('change', '#import_mapping_project_id, #import_mapping_tracker', function(){ |
|
39 |
$.ajax({ |
|
40 |
url: '<%= import_mapping_path(@import, :format => 'js') %>', |
|
41 |
type: 'post', |
|
42 |
data: $('#import-form').serialize() |
|
43 |
}); |
|
44 |
}); |
|
45 | ||
46 | 30 |
$('#import-form').submit(function(){ |
47 | 31 |
$('#import-details').show().addClass('ajax-loading'); |
48 | 32 |
$('#import-progress').progressbar({value: 0, max: <%= @import.total_items || 0 %>}); |
49 | 33 |
}); |
50 | ||
51 | 34 |
}); |
52 | 35 |
<% end %> |
app/views/imports/mapping.js.erb | ||
---|---|---|
1 |
$('#fields-mapping').html('<%= escape_javascript(render :partial => 'fields_mapping') %>'); |
|
1 |
<%= render :partial => "#{import_partial_prefix}_mapping" %> |
app/views/imports/new.html.erb | ||
---|---|---|
1 |
<h2><%= l(:label_import_issues) %></h2>
|
|
1 |
<h2><%= import_title %></h2>
|
|
2 | 2 | |
3 | 3 |
<%= form_tag(imports_path, :multipart => true) do %> |
4 |
<%= hidden_field_tag 'type', @import.type %> |
|
4 | 5 |
<fieldset class="box"> |
5 | 6 |
<legend><%= l(:label_select_file_to_import) %> (CSV)</legend> |
6 | 7 |
<p> |
... | ... | |
10 | 11 |
<p><%= submit_tag l(:label_next).html_safe + " »".html_safe, :name => nil %></p> |
11 | 12 |
<% end %> |
12 | 13 | |
13 |
<% content_for :sidebar do %> |
|
14 |
<%= render :partial => 'issues/sidebar' %> |
|
15 |
<% end %> |
|
14 |
<%= render :partial => "#{import_partial_prefix}_sidebar" %> |
app/views/imports/run.html.erb | ||
---|---|---|
1 |
<h2><%= l(:label_import_issues) %></h2>
|
|
1 |
<h2><%= import_title %></h2>
|
|
2 | 2 | |
3 | 3 |
<div id="import-details"> |
4 | 4 |
<div id="import-progress"><div id="progress-label">0 / <%= @import.total_items.to_i %></div></div> |
5 | 5 |
</div> |
6 | 6 | |
7 |
<% content_for :sidebar do %> |
|
8 |
<%= render :partial => 'issues/sidebar' %> |
|
9 |
<% end %> |
|
7 |
<%= render :partial => "#{import_partial_prefix}_sidebar" %> |
|
10 | 8 | |
11 | 9 |
<%= javascript_tag do %> |
12 | 10 |
$(document).ready(function() { |
app/views/imports/settings.html.erb | ||
---|---|---|
1 |
<h2><%= l(:label_import_issues) %></h2>
|
|
1 |
<h2><%= import_title %></h2>
|
|
2 | 2 | |
3 | 3 |
<%= form_tag(import_settings_path(@import), :id => "import-form") do %> |
4 | 4 |
<fieldset class="box tabular"> |
... | ... | |
25 | 25 |
<p><%= submit_tag l(:label_next).html_safe + " »".html_safe, :name => nil %></p> |
26 | 26 |
<% end %> |
27 | 27 | |
28 |
<% content_for :sidebar do %> |
|
29 |
<%= render :partial => 'issues/sidebar' %> |
|
30 |
<% end %> |
|
28 |
<%= render :partial => "#{import_partial_prefix}_sidebar" %> |
app/views/imports/show.html.erb | ||
---|---|---|
1 |
<h2><%= l(:label_import_issues) %></h2>
|
|
1 |
<h2><%= import_title %></h2>
|
|
2 | 2 | |
3 | 3 |
<% if @import.saved_items.count > 0 %> |
4 | 4 |
<p><%= l(:notice_import_finished, :count => @import.saved_items.count) %>:</p> |
5 | 5 | |
6 |
<ul id="saved-items"> |
|
7 |
<% @import.saved_objects.each do |issue| %> |
|
8 |
<li><%= link_to_issue issue %></li> |
|
9 |
<% end %> |
|
10 |
</ul> |
|
11 | ||
12 |
<p><%= link_to l(:label_issue_view_all), issues_path(:set_filter => 1, :status_id => '*', :issue_id => @import.saved_objects.map(&:id).join(',')) %></p> |
|
6 |
<%= render :partial => "#{import_partial_prefix}_saved_objects", :locals => { saved_objects: @import.saved_objects } %> |
|
13 | 7 |
<% end %> |
14 | 8 | |
15 | 9 |
<% if @import.unsaved_items.count > 0 %> |
... | ... | |
33 | 27 |
</table> |
34 | 28 |
<% end %> |
35 | 29 | |
36 |
<% content_for :sidebar do %> |
|
37 |
<%= render :partial => 'issues/sidebar' %> |
|
38 |
<% end %> |
|
30 |
<%= render :partial => "#{import_partial_prefix}_sidebar" %> |
config/routes.rb | ||
---|---|---|
63 | 63 |
get 'projects/:id/issues/report', :to => 'reports#issue_report', :as => 'project_issues_report' |
64 | 64 |
get 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :as => 'project_issues_report_details' |
65 | 65 | |
66 |
get '/issues/imports/new', :to => 'imports#new', :as => 'new_issues_import' |
|
66 |
get '/issues/imports/new', :to => 'imports#new', :defaults => { :type => 'IssueImport' }, :as => 'new_issues_import'
|
|
67 | 67 |
post '/imports', :to => 'imports#create', :as => 'imports' |
68 | 68 |
get '/imports/:id', :to => 'imports#show', :as => 'import' |
69 | 69 |
match '/imports/:id/settings', :to => 'imports#settings', :via => [:get, :post], :as => 'import_settings' |
lib/redmine.rb | ||
---|---|---|
115 | 115 |
map.permission :view_issue_watchers, {}, :read => true |
116 | 116 |
map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]} |
117 | 117 |
map.permission :delete_issue_watchers, {:watchers => :destroy} |
118 |
map.permission :import_issues, {:imports => [:new, :create, :settings, :mapping, :run, :show]}
|
|
118 |
map.permission :import_issues, {} |
|
119 | 119 |
# Issue categories |
120 | 120 |
map.permission :manage_categories, {:projects => :settings, :issue_categories => [:index, :show, :new, :create, :edit, :update, :destroy]}, :require => :member |
121 | 121 |
end |
test/functional/imports_controller_test.rb | ||
---|---|---|
42 | 42 |
end |
43 | 43 | |
44 | 44 |
def test_new_should_display_the_upload_form |
45 |
get :new |
|
45 |
get :new, :params => { :type => 'IssueImport' }
|
|
46 | 46 |
assert_response :success |
47 | 47 |
assert_select 'input[name=?]', 'file' |
48 | 48 |
end |
... | ... | |
50 | 50 |
def test_create_should_save_the_file |
51 | 51 |
import = new_record(Import) do |
52 | 52 |
post :create, :params => { |
53 |
:type => 'IssueImport', |
|
53 | 54 |
:file => uploaded_test_file('import_issues.csv', 'text/csv') |
54 | 55 |
} |
55 | 56 |
assert_response 302 |
test/integration/routing/imports_test.rb | ||
---|---|---|
19 | 19 | |
20 | 20 |
class RoutingImportsTest < Redmine::RoutingTest |
21 | 21 |
def test_imports |
22 |
should_route 'GET /issues/imports/new' => 'imports#new' |
|
22 |
should_route 'GET /issues/imports/new' => 'imports#new', :type => 'IssueImport' |
|
23 | ||
23 | 24 |
should_route 'POST /imports' => 'imports#create' |
24 | 25 | |
25 | 26 |
should_route 'GET /imports/4ae6bc' => 'imports#show', :id => '4ae6bc' |
test/unit/issue_import_test.rb | ||
---|---|---|
38 | 38 |
set_language_if_valid 'en' |
39 | 39 |
end |
40 | 40 | |
41 |
def test_authorized |
|
42 |
assert IssueImport.authorized?(User.find(1)) # admins |
|
43 |
assert IssueImport.authorized?(User.find(2)) # has import_issues permission |
|
44 |
assert !IssueImport.authorized?(User.find(3)) # does not have permission |
|
45 |
end |
|
46 | ||
41 | 47 |
def test_create_versions_should_create_missing_versions |
42 | 48 |
import = generate_import_with_mapping |
43 | 49 |
import.mapping.merge!('fixed_version' => '9', 'create_versions' => '1') |