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