0001-Generalize-issues-imports.patch

updated version (v3) - Gregor Schmidt, 2018-03-12 09:21

Download (22.6 KB)

View differences:

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)} --" : "&nbsp;".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
1
<p>
2
  <label for="import_mapping_project_id"><%= l(:label_project) %></label>
3
  <%= select_tag 'import_settings[mapping][project_id]',
4
        options_for_select(project_tree_options_for_select(@import.allowed_target_projects, :selected => @import.project)),
5
        :id => 'import_mapping_project_id' %>
6
</p>
7
<p>
8
  <label for="import_mapping_tracker"><%= l(:label_tracker) %></label>
9
  <%= mapping_select_tag @import, 'tracker', :required => true,
10
        :values => @import.allowed_target_trackers.sorted.map {|t| [t.name, t.id]} %>
11
</p>
12
<p>
13
  <label for="import_mapping_status"><%= l(:field_status) %></label>
14
  <%= mapping_select_tag @import, 'status' %>
15
</p>
16

  
17
<div class="splitcontent">
18
<div class="splitcontentleft">
19
<p>
20
  <label for="import_mapping_subject"><%= l(:field_subject) %></label>
21
  <%= mapping_select_tag @import, 'subject', :required => true %>
22
</p>
23
<p>
24
  <label for="import_mapping_description"><%= l(:field_description) %></label>
25
  <%= mapping_select_tag @import, 'description' %>
26
</p>
27
<p>
28
  <label for="import_mapping_priority"><%= l(:field_priority) %></label>
29
  <%= mapping_select_tag @import, 'priority' %>
30
</p>
31
<p>
32
  <label for="import_mapping_category"><%= l(:field_category) %></label>
33
  <%= mapping_select_tag @import, 'category' %>
34
  <% if User.current.allowed_to?(:manage_categories, @import.project) %>
35
    <label class="block">
36
      <%= check_box_tag 'import_settings[mapping][create_categories]', '1', @import.create_categories? %>
37
      <%= l(:label_create_missing_values) %>
38
    </label>
39
  <% end %>
40
</p>
41
<p>
42
  <label for="import_mapping_assigned_to"><%= l(:field_assigned_to) %></label>
43
  <%= mapping_select_tag @import, 'assigned_to' %>
44
</p>
45
<p>
46
  <label for="import_mapping_fixed_version"><%= l(:field_fixed_version) %></label>
47
  <%= mapping_select_tag @import, 'fixed_version' %>
48
  <% if User.current.allowed_to?(:manage_versions, @import.project) %>
49
    <label class="block">
50
      <%= check_box_tag 'import_settings[mapping][create_versions]', '1', @import.create_versions? %>
51
      <%= l(:label_create_missing_values) %>
52
    </label>
53
  <% end %>
54
</p>
55
<% @custom_fields.each do |field| %>
56
  <p>
57
    <label for="import_mapping_cf_<% field.id %>"><%= field.name %></label>
58
    <%= mapping_select_tag @import, "cf_#{field.id}" %>
59
  </p>
60
<% end %>
61
</div>
62

  
63
<div class="splitcontentright">
64
<p>
65
  <label for="import_mapping_is_private"><%= l(:field_is_private) %></label>
66
  <%= mapping_select_tag @import, 'is_private' %>
67
</p>
68
<p>
69
  <label for="import_mapping_parent_issue_id"><%= l(:field_parent_issue) %></label>
70
  <%= mapping_select_tag @import, 'parent_issue_id' %>
71
</p>
72
<p>
73
  <label for="import_mapping_start_date"><%= l(:field_start_date) %></label>
74
  <%= mapping_select_tag @import, 'start_date' %>
75
</p>
76
<p>
77
  <label for="import_mapping_due_date"><%= l(:field_due_date) %></label>
78
  <%= mapping_select_tag @import, 'due_date' %>
79
</p>
80
<p>
81
  <label for="import_mapping_estimated_hours"><%= l(:field_estimated_hours) %></label>
82
  <%= mapping_select_tag @import, 'estimated_hours' %>
83
</p>
84
<p>
85
  <label for="import_mapping_done_ratio"><%= l(:field_done_ratio) %></label>
86
  <%= mapping_select_tag @import, 'done_ratio' %>
87
</p>
88
</div>
89
</div>
90

  
app/views/imports/_issues_fields_mapping.html.erb
1
<p>
2
  <label for="import_mapping_project_id"><%= l(:label_project) %></label>
3
  <%= select_tag 'import_settings[mapping][project_id]',
4
        options_for_select(project_tree_options_for_select(@import.allowed_target_projects, :selected => @import.project)),
5
        :id => 'import_mapping_project_id' %>
6
</p>
7
<p>
8
  <label for="import_mapping_tracker"><%= l(:label_tracker) %></label>
9
  <%= mapping_select_tag @import, 'tracker', :required => true,
10
        :values => @import.allowed_target_trackers.sorted.map {|t| [t.name, t.id]} %>
11
</p>
12
<p>
13
  <label for="import_mapping_status"><%= l(:field_status) %></label>
14
  <%= mapping_select_tag @import, 'status' %>
15
</p>
16

  
17
<div class="splitcontent">
18
<div class="splitcontentleft">
19
<p>
20
  <label for="import_mapping_subject"><%= l(:field_subject) %></label>
21
  <%= mapping_select_tag @import, 'subject', :required => true %>
22
</p>
23
<p>
24
  <label for="import_mapping_description"><%= l(:field_description) %></label>
25
  <%= mapping_select_tag @import, 'description' %>
26
</p>
27
<p>
28
  <label for="import_mapping_priority"><%= l(:field_priority) %></label>
29
  <%= mapping_select_tag @import, 'priority' %>
30
</p>
31
<p>
32
  <label for="import_mapping_category"><%= l(:field_category) %></label>
33
  <%= mapping_select_tag @import, 'category' %>
34
  <% if User.current.allowed_to?(:manage_categories, @import.project) %>
35
    <label class="block">
36
      <%= check_box_tag 'import_settings[mapping][create_categories]', '1', @import.create_categories? %>
37
      <%= l(:label_create_missing_values) %>
38
    </label>
39
  <% end %>
40
</p>
41
<p>
42
  <label for="import_mapping_assigned_to"><%= l(:field_assigned_to) %></label>
43
  <%= mapping_select_tag @import, 'assigned_to' %>
44
</p>
45
<p>
46
  <label for="import_mapping_fixed_version"><%= l(:field_fixed_version) %></label>
47
  <%= mapping_select_tag @import, 'fixed_version' %>
48
  <% if User.current.allowed_to?(:manage_versions, @import.project) %>
49
    <label class="block">
50
      <%= check_box_tag 'import_settings[mapping][create_versions]', '1', @import.create_versions? %>
51
      <%= l(:label_create_missing_values) %>
52
    </label>
53
  <% end %>
54
</p>
55
<% @custom_fields.each do |field| %>
56
  <p>
57
    <label for="import_mapping_cf_<%= field.id %>"><%= field.name %></label>
58
    <%= mapping_select_tag @import, "cf_#{field.id}" %>
59
  </p>
60
<% end %>
61
</div>
62

  
63
<div class="splitcontentright">
64
<p>
65
  <label for="import_mapping_is_private"><%= l(:field_is_private) %></label>
66
  <%= mapping_select_tag @import, 'is_private' %>
67
</p>
68
<p>
69
  <label for="import_mapping_parent_issue_id"><%= l(:field_parent_issue) %></label>
70
  <%= mapping_select_tag @import, 'parent_issue_id' %>
71
</p>
72
<p>
73
  <label for="import_mapping_start_date"><%= l(:field_start_date) %></label>
74
  <%= mapping_select_tag @import, 'start_date' %>
75
</p>
76
<p>
77
  <label for="import_mapping_due_date"><%= l(:field_due_date) %></label>
78
  <%= mapping_select_tag @import, 'due_date' %>
79
</p>
80
<p>
81
  <label for="import_mapping_estimated_hours"><%= l(:field_estimated_hours) %></label>
82
  <%= mapping_select_tag @import, 'estimated_hours' %>
83
</p>
84
<p>
85
  <label for="import_mapping_done_ratio"><%= l(:field_done_ratio) %></label>
86
  <%= mapping_select_tag @import, 'done_ratio' %>
87
</p>
88
</div>
89
</div>
90

  
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 + " &#187;".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 + " &#187;".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')
44
-