##// END OF EJS Templates
Make Tracker map-able for CSV import (#22951)....
Jean-Philippe Lang -
r15108:e3875ffd5739
parent child
Show More
@@ -1,124 +1,120
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'csv'
19 19
20 20 class ImportsController < ApplicationController
21 21
22 22 before_filter :find_import, :only => [:show, :settings, :mapping, :run]
23 23 before_filter :authorize_global
24 24
25 25 helper :issues
26 26
27 27 def new
28 28 end
29 29
30 30 def create
31 31 @import = IssueImport.new
32 32 @import.user = User.current
33 33 @import.file = params[:file]
34 34 @import.set_default_settings
35 35
36 36 if @import.save
37 37 redirect_to import_settings_path(@import)
38 38 else
39 39 render :action => 'new'
40 40 end
41 41 end
42 42
43 43 def show
44 44 end
45 45
46 46 def settings
47 47 if request.post? && @import.parse_file
48 48 redirect_to import_mapping_path(@import)
49 49 end
50 50
51 51 rescue CSV::MalformedCSVError => e
52 52 flash.now[:error] = l(:error_invalid_csv_file_or_settings)
53 53 rescue ArgumentError, Encoding::InvalidByteSequenceError => e
54 54 flash.now[:error] = l(:error_invalid_file_encoding, :encoding => ERB::Util.h(@import.settings['encoding']))
55 55 rescue SystemCallError => e
56 56 flash.now[:error] = l(:error_can_not_read_import_file)
57 57 end
58 58
59 59 def mapping
60 issue = Issue.new
61 issue.project = @import.project
62 issue.tracker = @import.tracker
63 @attributes = issue.safe_attribute_names
64 @custom_fields = issue.editable_custom_field_values.map(&:custom_field)
60 @custom_fields = @import.mappable_custom_fields
65 61
66 62 if request.post?
67 63 respond_to do |format|
68 64 format.html {
69 65 if params[:previous]
70 66 redirect_to import_settings_path(@import)
71 67 else
72 68 redirect_to import_run_path(@import)
73 69 end
74 70 }
75 71 format.js # updates mapping form on project or tracker change
76 72 end
77 73 end
78 74 end
79 75
80 76 def run
81 77 if request.post?
82 78 @current = @import.run(
83 79 :max_items => max_items_per_request,
84 80 :max_time => 10.seconds
85 81 )
86 82 respond_to do |format|
87 83 format.html {
88 84 if @import.finished?
89 85 redirect_to import_path(@import)
90 86 else
91 87 redirect_to import_run_path(@import)
92 88 end
93 89 }
94 90 format.js
95 91 end
96 92 end
97 93 end
98 94
99 95 private
100 96
101 97 def find_import
102 98 @import = Import.where(:user_id => User.current.id, :filename => params[:id]).first
103 99 if @import.nil?
104 100 render_404
105 101 return
106 102 elsif @import.finished? && action_name != 'show'
107 103 redirect_to import_path(@import)
108 104 return
109 105 end
110 106 update_from_params if request.post?
111 107 end
112 108
113 109 def update_from_params
114 110 if params[:import_settings].is_a?(Hash)
115 111 @import.settings ||= {}
116 112 @import.settings.merge!(params[:import_settings])
117 113 @import.save!
118 114 end
119 115 end
120 116
121 117 def max_items_per_request
122 118 5
123 119 end
124 120 end
@@ -1,43 +1,47
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module ImportsHelper
21 21 def options_for_mapping_select(import, field, options={})
22 22 tags = "".html_safe
23 23 blank_text = options[:required] ? "-- #{l(:actionview_instancetag_blank_option)} --" : "&nbsp;".html_safe
24 24 tags << content_tag('option', blank_text, :value => '')
25 25 tags << options_for_select(import.columns_options, import.mapping[field])
26 if values = options[:values]
27 tags << content_tag('option', '--', :disabled => true)
28 tags << options_for_select(values.map {|text, value| [text, "value:#{value}"]}, import.mapping[field])
29 end
26 30 tags
27 31 end
28 32
29 33 def mapping_select_tag(import, field, options={})
30 34 name = "import_settings[mapping][#{field}]"
31 select_tag name, options_for_mapping_select(import, field, options)
35 select_tag name, options_for_mapping_select(import, field, options), :id => "import_mapping_#{field}"
32 36 end
33 37
34 38 # Returns the options for the date_format setting
35 39 def date_format_options
36 40 Import::DATE_FORMATS.map do |f|
37 41 format = f.gsub('%', '').gsub(/[dmY]/) do
38 42 {'d' => 'DD', 'm' => 'MM', 'Y' => 'YYYY'}[$&]
39 43 end
40 44 [format, f]
41 45 end
42 46 end
43 47 end
@@ -1,154 +1,182
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssueImport < Import
19 19
20 20 # Returns the objects that were imported
21 21 def saved_objects
22 22 object_ids = saved_items.pluck(:obj_id)
23 23 objects = Issue.where(:id => object_ids).order(:id).preload(:tracker, :priority, :status)
24 24 end
25 25
26 26 # Returns a scope of projects that user is allowed to
27 27 # import issue to
28 28 def allowed_target_projects
29 29 Project.allowed_to(user, :import_issues)
30 30 end
31 31
32 32 def project
33 33 project_id = mapping['project_id'].to_i
34 34 allowed_target_projects.find_by_id(project_id) || allowed_target_projects.first
35 35 end
36 36
37 37 # Returns a scope of trackers that user is allowed to
38 38 # import issue to
39 39 def allowed_target_trackers
40 40 Issue.allowed_target_trackers(project, user)
41 41 end
42 42
43 43 def tracker
44 tracker_id = mapping['tracker_id'].to_i
45 allowed_target_trackers.find_by_id(tracker_id) || allowed_target_trackers.first
44 if mapping['tracker'].to_s =~ /\Avalue:(\d+)\z/
45 tracker_id = $1.to_i
46 allowed_target_trackers.find_by_id(tracker_id)
47 end
46 48 end
47 49
48 50 # Returns true if missing categories should be created during the import
49 51 def create_categories?
50 52 user.allowed_to?(:manage_categories, project) &&
51 53 mapping['create_categories'] == '1'
52 54 end
53 55
54 56 # Returns true if missing versions should be created during the import
55 57 def create_versions?
56 58 user.allowed_to?(:manage_versions, project) &&
57 59 mapping['create_versions'] == '1'
58 60 end
59 61
62 def mappable_custom_fields
63 if tracker
64 issue = Issue.new
65 issue.project = project
66 issue.tracker = tracker
67 issue.editable_custom_field_values(user).map(&:custom_field)
68 elsif project
69 project.all_issue_custom_fields
70 else
71 []
72 end
73 end
74
60 75 private
61 76
62 77 def build_object(row)
63 78 issue = Issue.new
64 79 issue.author = user
65 80 issue.notify = false
66 81
82 tracker_id = nil
83 if tracker
84 tracker_id = tracker.id
85 elsif tracker_name = row_value(row, 'tracker')
86 tracker_id = allowed_target_trackers.named(tracker_name).first.try(:id)
87 end
88
67 89 attributes = {
68 90 'project_id' => mapping['project_id'],
69 'tracker_id' => mapping['tracker_id'],
91 'tracker_id' => tracker_id,
70 92 'subject' => row_value(row, 'subject'),
71 93 'description' => row_value(row, 'description')
72 94 }
95 attributes
73 96 issue.send :safe_attributes=, attributes, user
74 97
75 98 attributes = {}
76 99 if priority_name = row_value(row, 'priority')
77 100 if priority_id = IssuePriority.active.named(priority_name).first.try(:id)
78 101 attributes['priority_id'] = priority_id
79 102 end
80 103 end
81 104 if issue.project && category_name = row_value(row, 'category')
82 105 if category = issue.project.issue_categories.named(category_name).first
83 106 attributes['category_id'] = category.id
84 107 elsif create_categories?
85 108 category = issue.project.issue_categories.build
86 109 category.name = category_name
87 110 if category.save
88 111 attributes['category_id'] = category.id
89 112 end
90 113 end
91 114 end
92 115 if assignee_name = row_value(row, 'assigned_to')
93 116 if assignee = Principal.detect_by_keyword(issue.assignable_users, assignee_name)
94 117 attributes['assigned_to_id'] = assignee.id
95 118 end
96 119 end
97 120 if issue.project && version_name = row_value(row, 'fixed_version')
98 121 if version = issue.project.versions.named(version_name).first
99 122 attributes['fixed_version_id'] = version.id
100 123 elsif create_versions?
101 124 version = issue.project.versions.build
102 125 version.name = version_name
103 126 if version.save
104 127 attributes['fixed_version_id'] = version.id
105 128 end
106 129 end
107 130 end
108 131 if is_private = row_value(row, 'is_private')
109 132 if yes?(is_private)
110 133 attributes['is_private'] = '1'
111 134 end
112 135 end
113 136 if parent_issue_id = row_value(row, 'parent_issue_id')
114 137 if parent_issue_id =~ /\A(#)?(\d+)\z/
115 138 parent_issue_id = $2
116 139 if $1
117 140 attributes['parent_issue_id'] = parent_issue_id
118 141 elsif issue_id = items.where(:position => parent_issue_id).first.try(:obj_id)
119 142 attributes['parent_issue_id'] = issue_id
120 143 end
121 144 else
122 145 attributes['parent_issue_id'] = parent_issue_id
123 146 end
124 147 end
125 148 if start_date = row_date(row, 'start_date')
126 149 attributes['start_date'] = start_date
127 150 end
128 151 if due_date = row_date(row, 'due_date')
129 152 attributes['due_date'] = due_date
130 153 end
131 154 if estimated_hours = row_value(row, 'estimated_hours')
132 155 attributes['estimated_hours'] = estimated_hours
133 156 end
134 157 if done_ratio = row_value(row, 'done_ratio')
135 158 attributes['done_ratio'] = done_ratio
136 159 end
137 160
138 161 attributes['custom_field_values'] = issue.custom_field_values.inject({}) do |h, v|
139 162 value = case v.custom_field.field_format
140 163 when 'date'
141 164 row_date(row, "cf_#{v.custom_field.id}")
142 165 else
143 166 row_value(row, "cf_#{v.custom_field.id}")
144 167 end
145 168 if value
146 169 h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(value, issue)
147 170 end
148 171 h
149 172 end
150 173
151 174 issue.send :safe_attributes=, attributes, user
175
176 if issue.tracker_id != tracker_id
177 issue.tracker_id = nil
178 end
179
152 180 issue
153 181 end
154 182 end
@@ -1,86 +1,86
1 <div class="splitcontent">
2 <div class="splitcontentleft">
3 1 <p>
4 2 <label><%= l(:label_project) %></label>
5 3 <%= select_tag 'import_settings[mapping][project_id]',
6 4 options_for_select(project_tree_options_for_select(@import.allowed_target_projects, :selected => @import.project)),
7 :id => 'issue_project_id' %>
5 :id => 'import_mapping_project_id' %>
8 6 </p>
9 7 <p>
10 8 <label><%= l(:label_tracker) %></label>
11 <%= select_tag 'import_settings[mapping][tracker_id]',
12 options_for_select(@import.allowed_target_trackers.sorted.map {|t| [t.name, t.id]}, @import.tracker.try(:id)),
13 :id => 'issue_tracker_id' %>
9 <%= mapping_select_tag @import, 'tracker', :required => true,
10 :values => @import.allowed_target_trackers.sorted.map {|t| [t.name, t.id]} %>
14 11 </p>
12
13 <div class="splitcontent">
14 <div class="splitcontentleft">
15 15 <p>
16 16 <label><%= l(:field_subject) %></label>
17 17 <%= mapping_select_tag @import, 'subject', :required => true %>
18 18 </p>
19 19 <p>
20 20 <label><%= l(:field_description) %></label>
21 21 <%= mapping_select_tag @import, 'description' %>
22 22 </p>
23 23 <p>
24 24 <label><%= l(:field_priority) %></label>
25 25 <%= mapping_select_tag @import, 'priority' %>
26 26 </p>
27 27 <p>
28 28 <label><%= l(:field_category) %></label>
29 29 <%= mapping_select_tag @import, 'category' %>
30 30 <% if User.current.allowed_to?(:manage_categories, @import.project) %>
31 31 <label class="block">
32 32 <%= check_box_tag 'import_settings[mapping][create_categories]', '1', @import.create_categories? %>
33 33 <%= l(:label_create_missing_values) %>
34 34 </label>
35 35 <% end %>
36 36 </p>
37 37 <p>
38 38 <label><%= l(:field_assigned_to) %></label>
39 39 <%= mapping_select_tag @import, 'assigned_to' %>
40 40 </p>
41 41 <p>
42 42 <label><%= l(:field_fixed_version) %></label>
43 43 <%= mapping_select_tag @import, 'fixed_version' %>
44 44 <% if User.current.allowed_to?(:manage_versions, @import.project) %>
45 45 <label class="block">
46 46 <%= check_box_tag 'import_settings[mapping][create_versions]', '1', @import.create_versions? %>
47 47 <%= l(:label_create_missing_values) %>
48 48 </label>
49 49 <% end %>
50 50 </p>
51 51 <% @custom_fields.each do |field| %>
52 52 <p>
53 53 <label><%= field.name %></label>
54 54 <%= mapping_select_tag @import, "cf_#{field.id}" %>
55 55 </p>
56 56 <% end %>
57 57 </div>
58 58
59 59 <div class="splitcontentright">
60 60 <p>
61 61 <label><%= l(:field_is_private) %></label>
62 62 <%= mapping_select_tag @import, 'is_private' %>
63 63 </p>
64 64 <p>
65 65 <label><%= l(:field_parent_issue) %></label>
66 66 <%= mapping_select_tag @import, 'parent_issue_id' %>
67 67 </p>
68 68 <p>
69 69 <label><%= l(:field_start_date) %></label>
70 70 <%= mapping_select_tag @import, 'start_date' %>
71 71 </p>
72 72 <p>
73 73 <label><%= l(:field_due_date) %></label>
74 74 <%= mapping_select_tag @import, 'due_date' %>
75 75 </p>
76 76 <p>
77 77 <label><%= l(:field_estimated_hours) %></label>
78 78 <%= mapping_select_tag @import, 'estimated_hours' %>
79 79 </p>
80 80 <p>
81 81 <label><%= l(:field_done_ratio) %></label>
82 82 <%= mapping_select_tag @import, 'done_ratio' %>
83 83 </p>
84 84 </div>
85 85 </div>
86 86
@@ -1,52 +1,52
1 1 <h2><%= l(:label_import_issues) %></h2>
2 2
3 3 <%= form_tag(import_mapping_path(@import), :id => "import-form") do %>
4 4 <fieldset class="box tabular">
5 5 <legend><%= l(:label_fields_mapping) %></legend>
6 6 <div id="fields-mapping">
7 7 <%= render :partial => 'fields_mapping' %>
8 8 </div>
9 9 </fieldset>
10 10
11 11 <div class="autoscroll">
12 12 <fieldset class="box">
13 13 <legend><%= l(:label_file_content_preview) %></legend>
14 14
15 15 <table class="sample-data">
16 16 <% @import.first_rows.each do |row| %>
17 17 <tr>
18 18 <%= row.map {|c| content_tag 'td', truncate(c.to_s, :length => 50) }.join("").html_safe %>
19 19 </tr>
20 20 <% end %>
21 21 </table>
22 22 </fieldset>
23 23 </div>
24 24
25 25 <p>
26 26 <%= button_tag("\xc2\xab " + l(:label_previous), :name => 'previous') %>
27 27 <%= submit_tag l(:button_import) %>
28 28 </p>
29 29 <% end %>
30 30
31 31 <% content_for :sidebar do %>
32 32 <%= render :partial => 'issues/sidebar' %>
33 33 <% end %>
34 34
35 35
36 36 <%= javascript_tag do %>
37 37 $(document).ready(function() {
38 $('#fields-mapping').on('change', '#issue_project_id, #issue_tracker_id', function(){
38 $('#fields-mapping').on('change', '#import_mapping_project_id, #import_mapping_tracker', function(){
39 39 $.ajax({
40 40 url: '<%= import_mapping_path(@import, :format => 'js') %>',
41 41 type: 'post',
42 42 data: $('#import-form').serialize()
43 43 });
44 44 });
45 45
46 46 $('#import-form').submit(function(){
47 47 $('#import-details').show().addClass('ajax-loading');
48 48 $('#import-progress').progressbar({value: 0, max: <%= @import.total_items || 0 %>});
49 49 });
50 50
51 51 });
52 52 <% end %>
@@ -1,4 +1,4
1 priority;subject;description;start_date;due_date;parent;private;progress;custom;version;category;user;estimated_hours
2 High;First;First description;2015-07-08;2015-08-25;;no;;PostgreSQL;;New category;dlopper;1
3 Normal;Child 1;Child description;;;1;yes;10;MySQL;2.0;New category;;2
4 Normal;Child of existing issue;Child description;;;#2;no;20;;2.1;Printing;;3
1 priority;subject;description;start_date;due_date;parent;private;progress;custom;version;category;user;estimated_hours;tracker
2 High;First;First description;2015-07-08;2015-08-25;;no;;PostgreSQL;;New category;dlopper;1;bug
3 Normal;Child 1;Child description;;;1;yes;10;MySQL;2.0;New category;;2;feature request
4 Normal;Child of existing issue;Child description;;;#2;no;20;;2.1;Printing;;3;bug
@@ -1,276 +1,276
1 1 module ObjectHelpers
2 2 def User.generate!(attributes={})
3 3 @generated_user_login ||= 'user0'
4 4 @generated_user_login.succ!
5 5 user = User.new(attributes)
6 6 user.login = @generated_user_login.dup if user.login.blank?
7 7 user.mail = "#{@generated_user_login}@example.com" if user.mail.blank?
8 8 user.firstname = "Bob" if user.firstname.blank?
9 9 user.lastname = "Doe" if user.lastname.blank?
10 10 yield user if block_given?
11 11 user.save!
12 12 user
13 13 end
14 14
15 15 def User.add_to_project(user, project, roles=nil)
16 16 roles = Role.find(1) if roles.nil?
17 17 roles = [roles] if roles.is_a?(Role)
18 18 Member.create!(:principal => user, :project => project, :roles => roles)
19 19 end
20 20
21 21 def Group.generate!(attributes={})
22 22 @generated_group_name ||= 'Group 0'
23 23 @generated_group_name.succ!
24 24 group = Group.new(attributes)
25 25 group.name = @generated_group_name.dup if group.name.blank?
26 26 yield group if block_given?
27 27 group.save!
28 28 group
29 29 end
30 30
31 31 def Project.generate!(attributes={})
32 32 @generated_project_identifier ||= 'project-0000'
33 33 @generated_project_identifier.succ!
34 34 project = Project.new(attributes)
35 35 project.name = @generated_project_identifier.dup if project.name.blank?
36 36 project.identifier = @generated_project_identifier.dup if project.identifier.blank?
37 37 yield project if block_given?
38 38 project.save!
39 39 project
40 40 end
41 41
42 42 def Project.generate_with_parent!(*args)
43 43 attributes = args.last.is_a?(Hash) ? args.pop : {}
44 44 parent = args.size > 0 ? args.first : Project.generate!
45 45
46 46 project = Project.generate!(attributes) do |p|
47 47 p.parent = parent
48 48 end
49 49 parent.reload if parent
50 50 project
51 51 end
52 52
53 53 def IssueStatus.generate!(attributes={})
54 54 @generated_status_name ||= 'Status 0'
55 55 @generated_status_name.succ!
56 56 status = IssueStatus.new(attributes)
57 57 status.name = @generated_status_name.dup if status.name.blank?
58 58 yield status if block_given?
59 59 status.save!
60 60 status
61 61 end
62 62
63 63 def Tracker.generate(attributes={})
64 64 @generated_tracker_name ||= 'Tracker 0'
65 65 @generated_tracker_name.succ!
66 66 tracker = Tracker.new(attributes)
67 67 tracker.name = @generated_tracker_name.dup if tracker.name.blank?
68 68 tracker.default_status ||= IssueStatus.order('position').first || IssueStatus.generate!
69 69 yield tracker if block_given?
70 70 tracker
71 71 end
72 72
73 73 def Tracker.generate!(attributes={}, &block)
74 74 tracker = Tracker.generate(attributes, &block)
75 75 tracker.save!
76 76 tracker
77 77 end
78 78
79 79 def Role.generate!(attributes={})
80 80 @generated_role_name ||= 'Role 0'
81 81 @generated_role_name.succ!
82 82 role = Role.new(attributes)
83 83 role.name = @generated_role_name.dup if role.name.blank?
84 84 yield role if block_given?
85 85 role.save!
86 86 role
87 87 end
88 88
89 89 # Generates an unsaved Issue
90 90 def Issue.generate(attributes={})
91 91 issue = Issue.new(attributes)
92 92 issue.project ||= Project.find(1)
93 93 issue.tracker ||= issue.project.trackers.first
94 94 issue.subject = 'Generated' if issue.subject.blank?
95 95 issue.author ||= User.find(2)
96 96 yield issue if block_given?
97 97 issue
98 98 end
99 99
100 100 # Generates a saved Issue
101 101 def Issue.generate!(attributes={}, &block)
102 102 issue = Issue.generate(attributes, &block)
103 103 issue.save!
104 104 issue
105 105 end
106 106
107 107 # Generates an issue with 2 children and a grandchild
108 108 def Issue.generate_with_descendants!(attributes={})
109 109 issue = Issue.generate!(attributes)
110 110 child = Issue.generate!(:project => issue.project, :subject => 'Child1', :parent_issue_id => issue.id)
111 111 Issue.generate!(:project => issue.project, :subject => 'Child2', :parent_issue_id => issue.id)
112 112 Issue.generate!(:project => issue.project, :subject => 'Child11', :parent_issue_id => child.id)
113 113 issue.reload
114 114 end
115 115
116 116 def Issue.generate_with_child!(attributes={})
117 117 issue = Issue.generate!(attributes)
118 118 Issue.generate!(:parent_issue_id => issue.id)
119 119 issue.reload
120 120 end
121 121
122 122 def Journal.generate!(attributes={})
123 123 journal = Journal.new(attributes)
124 124 journal.user ||= User.first
125 125 journal.journalized ||= Issue.first
126 126 yield journal if block_given?
127 127 journal.save!
128 128 journal
129 129 end
130 130
131 131 def Version.generate!(attributes={})
132 132 @generated_version_name ||= 'Version 0'
133 133 @generated_version_name.succ!
134 134 version = Version.new(attributes)
135 135 version.name = @generated_version_name.dup if version.name.blank?
136 136 version.project ||= Project.find(1)
137 137 yield version if block_given?
138 138 version.save!
139 139 version
140 140 end
141 141
142 142 def TimeEntry.generate!(attributes={})
143 143 entry = TimeEntry.new(attributes)
144 144 entry.user ||= User.find(2)
145 145 entry.issue ||= Issue.find(1) unless entry.project
146 146 entry.project ||= entry.issue.project
147 147 entry.activity ||= TimeEntryActivity.first
148 148 entry.spent_on ||= Date.today
149 149 entry.hours ||= 1.0
150 150 entry.save!
151 151 entry
152 152 end
153 153
154 154 def AuthSource.generate!(attributes={})
155 155 @generated_auth_source_name ||= 'Auth 0'
156 156 @generated_auth_source_name.succ!
157 157 source = AuthSource.new(attributes)
158 158 source.name = @generated_auth_source_name.dup if source.name.blank?
159 159 yield source if block_given?
160 160 source.save!
161 161 source
162 162 end
163 163
164 164 def Board.generate!(attributes={})
165 165 @generated_board_name ||= 'Forum 0'
166 166 @generated_board_name.succ!
167 167 board = Board.new(attributes)
168 168 board.name = @generated_board_name.dup if board.name.blank?
169 169 board.description = @generated_board_name.dup if board.description.blank?
170 170 yield board if block_given?
171 171 board.save!
172 172 board
173 173 end
174 174
175 175 def Attachment.generate!(attributes={})
176 176 @generated_filename ||= 'testfile0'
177 177 @generated_filename.succ!
178 178 attributes = attributes.dup
179 179 attachment = Attachment.new(attributes)
180 180 attachment.container ||= Issue.find(1)
181 181 attachment.author ||= User.find(2)
182 182 attachment.filename = @generated_filename.dup if attachment.filename.blank?
183 183 attachment.save!
184 184 attachment
185 185 end
186 186
187 187 def CustomField.generate!(attributes={})
188 188 @generated_custom_field_name ||= 'Custom field 0'
189 189 @generated_custom_field_name.succ!
190 190 field = new(attributes)
191 191 field.name = @generated_custom_field_name.dup if field.name.blank?
192 192 field.field_format = 'string' if field.field_format.blank?
193 193 yield field if block_given?
194 194 field.save!
195 195 field
196 196 end
197 197
198 198 def IssueCustomField.generate!(attributes={})
199 199 super do |field|
200 200 field.is_for_all = true unless attributes.key?(:is_for_all)
201 201 field.tracker_ids = Tracker.all.ids unless attributes.key?(:tracker_ids) || attributes.key?(:trackers)
202 202 end
203 203 end
204 204
205 205 def Changeset.generate!(attributes={})
206 206 @generated_changeset_rev ||= '123456'
207 207 @generated_changeset_rev.succ!
208 208 changeset = new(attributes)
209 209 changeset.repository ||= Project.find(1).repository
210 210 changeset.revision ||= @generated_changeset_rev
211 211 changeset.committed_on ||= Time.now
212 212 yield changeset if block_given?
213 213 changeset.save!
214 214 changeset
215 215 end
216 216
217 217 def Query.generate!(attributes={})
218 218 query = new(attributes)
219 219 query.name = "Generated query" if query.name.blank?
220 220 query.user ||= User.find(1)
221 221 query.save!
222 222 query
223 223 end
224 224
225 225 def generate_import(fixture_name='import_issues.csv')
226 226 import = IssueImport.new
227 227 import.user_id = 2
228 228 import.file = uploaded_test_file(fixture_name, 'text/csv')
229 229 import.save!
230 230 import
231 231 end
232 232
233 233 def generate_import_with_mapping(fixture_name='import_issues.csv')
234 234 import = generate_import(fixture_name)
235 235
236 236 import.settings = {
237 237 'separator' => ";", 'wrapper' => '"', 'encoding' => "UTF-8",
238 'mapping' => {'project_id' => '1', 'tracker_id' => '2', 'subject' => '1'}
238 'mapping' => {'project_id' => '1', 'tracker' => '13', 'subject' => '1'}
239 239 }
240 240 import.save!
241 241 import
242 242 end
243 243 end
244 244
245 245 module TrackerObjectHelpers
246 246 def generate_transitions!(*args)
247 247 options = args.last.is_a?(Hash) ? args.pop : {}
248 248 if args.size == 1
249 249 args << args.first
250 250 end
251 251 if options[:clear]
252 252 WorkflowTransition.where(:tracker_id => id).delete_all
253 253 end
254 254 args.each_cons(2) do |old_status_id, new_status_id|
255 255 WorkflowTransition.create!(
256 256 :tracker => self,
257 257 :role_id => (options[:role_id] || 1),
258 258 :old_status_id => old_status_id,
259 259 :new_status_id => new_status_id
260 260 )
261 261 end
262 262 end
263 263 end
264 264 Tracker.send :include, TrackerObjectHelpers
265 265
266 266 module IssueObjectHelpers
267 267 def close!
268 268 self.status = IssueStatus.where(:is_closed => true).first
269 269 save!
270 270 end
271 271
272 272 def generate_child!(attributes={})
273 273 Issue.generate!(attributes.merge(:parent_issue_id => self.id))
274 274 end
275 275 end
276 276 Issue.send :include, IssueObjectHelpers
@@ -1,132 +1,163
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class IssueImportTest < ActiveSupport::TestCase
21 21 fixtures :projects, :enabled_modules,
22 22 :users, :email_addresses,
23 23 :roles, :members, :member_roles,
24 24 :issues, :issue_statuses,
25 25 :trackers, :projects_trackers,
26 26 :versions,
27 27 :issue_categories,
28 28 :enumerations,
29 29 :workflows,
30 30 :custom_fields,
31 31 :custom_values,
32 32 :custom_fields_projects,
33 33 :custom_fields_trackers
34 34
35 35 def test_create_versions_should_create_missing_versions
36 36 import = generate_import_with_mapping
37 37 import.mapping.merge!('fixed_version' => '9', 'create_versions' => '1')
38 38 import.save!
39 39
40 40 version = new_record(Version) do
41 41 assert_difference 'Issue.count', 3 do
42 42 import.run
43 43 end
44 44 end
45 45 assert_equal '2.1', version.name
46 46 end
47 47
48 48 def test_create_categories_should_create_missing_categories
49 49 import = generate_import_with_mapping
50 50 import.mapping.merge!('category' => '10', 'create_categories' => '1')
51 51 import.save!
52 52
53 53 category = new_record(IssueCategory) do
54 54 assert_difference 'Issue.count', 3 do
55 55 import.run
56 56 end
57 57 end
58 58 assert_equal 'New category', category.name
59 59 end
60 60
61 def test_mapping_with_fixed_tracker
62 import = generate_import_with_mapping
63 import.mapping.merge!('tracker' => 'value:2')
64 import.save!
65
66 issues = new_records(Issue, 3) { import.run }
67 assert_equal [2], issues.map(&:tracker_id).uniq
68 end
69
70 def test_mapping_with_mapped_tracker
71 import = generate_import_with_mapping
72 import.mapping.merge!('tracker' => '13')
73 import.save!
74
75 issues = new_records(Issue, 3) { import.run }
76 assert_equal [1, 2, 1], issues.map(&:tracker_id)
77 end
78
79 def test_should_not_import_with_default_tracker_when_tracker_is_invalid
80 Tracker.find_by_name('Feature request').update!(:name => 'Feature')
81
82 import = generate_import_with_mapping
83 import.mapping.merge!('tracker' => '13')
84 import.save!
85 import.run
86
87 assert_equal 1, import.unsaved_items.count
88 item = import.unsaved_items.first
89 assert_include "Tracker cannot be blank", item.message
90 end
91
61 92 def test_parent_should_be_set
62 93 import = generate_import_with_mapping
63 94 import.mapping.merge!('parent_issue_id' => '5')
64 95 import.save!
65 96
66 97 issues = new_records(Issue, 3) { import.run }
67 98 assert_nil issues[0].parent
68 99 assert_equal issues[0].id, issues[1].parent_id
69 100 assert_equal 2, issues[2].parent_id
70 101 end
71 102
72 103 def test_assignee_should_be_set
73 104 import = generate_import_with_mapping
74 105 import.mapping.merge!('assigned_to' => '11')
75 106 import.save!
76 107
77 108 issues = new_records(Issue, 3) { import.run }
78 109 assert_equal [User.find(3), nil, nil], issues.map(&:assigned_to)
79 110 end
80 111
81 112 def test_user_custom_field_should_be_set
82 113 field = IssueCustomField.generate!(:field_format => 'user', :is_for_all => true, :trackers => Tracker.all)
83 114 import = generate_import_with_mapping
84 115 import.mapping.merge!("cf_#{field.id}" => '11')
85 116 import.save!
86 117
87 118 issues = new_records(Issue, 3) { import.run }
88 119 assert_equal '3', issues.first.custom_field_value(field)
89 120 end
90 121
91 122 def test_is_private_should_be_set_based_on_user_locale
92 123 import = generate_import_with_mapping
93 124 import.mapping.merge!('is_private' => '6')
94 125 import.save!
95 126
96 127 issues = new_records(Issue, 3) { import.run }
97 128 assert_equal [false, true, false], issues.map(&:is_private)
98 129 end
99 130
100 131 def test_dates_should_be_parsed_using_date_format_setting
101 132 field = IssueCustomField.generate!(:field_format => 'date', :is_for_all => true, :trackers => Tracker.all)
102 133 import = generate_import_with_mapping('import_dates.csv')
103 134 import.settings.merge!('date_format' => Import::DATE_FORMATS[1])
104 import.mapping.merge!('subject' => '0', 'start_date' => '1', 'due_date' => '2', "cf_#{field.id}" => '3')
135 import.mapping.merge!('tracker' => 'value:1', 'subject' => '0', 'start_date' => '1', 'due_date' => '2', "cf_#{field.id}" => '3')
105 136 import.save!
106 137
107 138 issue = new_record(Issue) { import.run } # only 1 valid issue
108 139 assert_equal "Valid dates", issue.subject
109 140 assert_equal Date.parse('2015-07-10'), issue.start_date
110 141 assert_equal Date.parse('2015-08-12'), issue.due_date
111 142 assert_equal '2015-07-14', issue.custom_field_value(field)
112 143 end
113 144
114 145 def test_date_format_should_default_to_user_language
115 146 user = User.generate!(:language => 'fr')
116 147 import = Import.new
117 148 import.user = user
118 149 assert_nil import.settings['date_format']
119 150
120 151 import.set_default_settings
121 152 assert_equal '%d/%m/%Y', import.settings['date_format']
122 153 end
123 154
124 155 def test_run_should_remove_the_file
125 156 import = generate_import_with_mapping
126 157 file_path = import.filepath
127 158 assert File.exists?(file_path)
128 159
129 160 import.run
130 161 assert !File.exists?(file_path)
131 162 end
132 163 end
General Comments 0
You need to be logged in to leave comments. Login now