redmine_extensions 0.1.14 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/redmine_extensions/redmine_extensions.js.orig +648 -0
  3. data/app/views/easy_entity_assignments/_query_index.html.erb.orig +16 -0
  4. data/config/locales/cs.yml +4 -1
  5. data/config/locales/en.yml +1 -0
  6. data/lib/generators/redmine_extensions/entity/templates/context_menu.html.erb.erb +14 -1
  7. data/lib/generators/redmine_extensions/entity/templates/controller.rb.erb +40 -6
  8. data/lib/generators/redmine_extensions/entity/templates/edit.js.erb.erb +5 -5
  9. data/lib/generators/redmine_extensions/entity/templates/index.js.erb.erb +3 -3
  10. data/lib/generators/redmine_extensions/entity/templates/new.js.erb.erb +5 -5
  11. data/lib/generators/redmine_extensions/entity/templates/show.js.erb.erb +3 -3
  12. data/lib/redmine_extensions/easy_query_helpers/outputs.rb +18 -2
  13. data/lib/redmine_extensions/query_output.rb +30 -11
  14. data/lib/redmine_extensions/redmine_patches/controllers/issues_controller_patch.rb +9 -0
  15. data/lib/redmine_extensions/version.rb +1 -1
  16. data/lib/redmine_extensions/version.rb.orig +7 -0
  17. data/spec/redmine/app/controllers/custom_field_enumerations_controller.rb +75 -0
  18. data/spec/redmine/app/controllers/imports_controller.rb +121 -0
  19. data/spec/redmine/app/helpers/imports_helper.rb +47 -0
  20. data/spec/redmine/app/models/custom_field_enumeration.rb +90 -0
  21. data/spec/redmine/app/models/import.rb +249 -0
  22. data/spec/redmine/app/models/import_item.rb +22 -0
  23. data/spec/redmine/app/models/issue_import.rb +186 -0
  24. data/spec/redmine/app/views/attachments/image.html.erb +3 -0
  25. data/spec/redmine/app/views/attachments/other.html.erb +3 -0
  26. data/spec/redmine/app/views/common/_image.html.erb +1 -0
  27. data/spec/redmine/app/views/common/_other.html.erb +1 -0
  28. data/spec/redmine/app/views/custom_field_enumerations/create.js.erb +2 -0
  29. data/spec/redmine/app/views/custom_field_enumerations/destroy.html.erb +14 -0
  30. data/spec/redmine/app/views/custom_field_enumerations/index.html.erb +47 -0
  31. data/spec/redmine/app/views/custom_fields/formats/_enumeration.erb +12 -0
  32. data/spec/redmine/app/views/imports/_fields_mapping.html.erb +90 -0
  33. data/spec/redmine/app/views/imports/mapping.html.erb +52 -0
  34. data/spec/redmine/app/views/imports/mapping.js.erb +1 -0
  35. data/spec/redmine/app/views/imports/new.html.erb +15 -0
  36. data/spec/redmine/app/views/imports/run.html.erb +20 -0
  37. data/spec/redmine/app/views/imports/run.js.erb +11 -0
  38. data/spec/redmine/app/views/imports/settings.html.erb +30 -0
  39. data/spec/redmine/app/views/imports/show.html.erb +38 -0
  40. data/spec/redmine/app/views/layouts/_file.html.erb +15 -0
  41. data/spec/redmine/app/views/mailer/security_notification.html.erb +13 -0
  42. data/spec/redmine/app/views/mailer/security_notification.text.erb +8 -0
  43. data/spec/redmine/app/views/mailer/settings_updated.html.erb +14 -0
  44. data/spec/redmine/app/views/mailer/settings_updated.text.erb +12 -0
  45. data/spec/redmine/app/views/queries/_query_form.html.erb +62 -0
  46. data/spec/redmine/app/views/repositories/_changeset.html.erb +40 -0
  47. data/spec/redmine/app/views/search/index.api.rsb +12 -0
  48. data/spec/redmine/app/views/settings/_api.html.erb +10 -0
  49. data/spec/redmine/app/views/settings/_attachments.html.erb +21 -0
  50. data/spec/redmine/app/views/wiki/_new_modal.html.erb +21 -0
  51. data/spec/redmine/app/views/wiki/new.html.erb +17 -0
  52. data/spec/redmine/app/views/wiki/new.js.erb +2 -0
  53. data/spec/redmine/appveyor.yml +36 -0
  54. data/spec/redmine/db/migrate/20150725112753_insert_allowed_statuses_for_new_issues.rb +23 -0
  55. data/spec/redmine/db/migrate/20150730122707_create_imports.rb +13 -0
  56. data/spec/redmine/db/migrate/20150730122735_create_import_items.rb +10 -0
  57. data/spec/redmine/db/migrate/20150921204850_change_time_entries_comments_limit_to_1024.rb +9 -0
  58. data/spec/redmine/db/migrate/20150921210243_change_wiki_contents_comments_limit_to_1024.rb +11 -0
  59. data/spec/redmine/db/migrate/20151020182334_change_attachments_filesize_limit_to_8.rb +9 -0
  60. data/spec/redmine/db/migrate/20151020182731_fix_comma_in_user_format_setting_value.rb +13 -0
  61. data/spec/redmine/db/migrate/20151021184614_change_issue_categories_name_limit_to_60.rb +9 -0
  62. data/spec/redmine/db/migrate/20151021185456_change_auth_sources_filter_to_text.rb +9 -0
  63. data/spec/redmine/db/migrate/20151021190616_change_user_preferences_hide_mail_default_to_true.rb +9 -0
  64. data/spec/redmine/db/migrate/20151024082034_add_tokens_updated_on.rb +10 -0
  65. data/spec/redmine/db/migrate/20151025072118_create_custom_field_enumerations.rb +10 -0
  66. data/spec/redmine/db/migrate/20151031095005_add_projects_default_version_id.rb +12 -0
  67. data/spec/redmine/db/migrate/20160404080304_force_password_reset_during_setup.rb +9 -0
  68. data/spec/redmine/db/migrate/20160416072926_remove_position_defaults.rb +13 -0
  69. data/spec/redmine/db/migrate/20160529063352_add_roles_settings.rb +5 -0
  70. data/spec/redmine/lib/redmine/acts/positioned.rb +118 -0
  71. data/spec/redmine/lib/redmine/helpers/url.rb +35 -0
  72. data/spec/redmine/lib/redmine/hook/listener.rb +32 -0
  73. data/spec/redmine/lib/redmine/hook/view_listener.rb +78 -0
  74. data/spec/redmine/log/test.log +2 -0
  75. data/spec/redmine/plugins/dummy_plugin/Gemfile +1 -0
  76. data/spec/redmine/plugins/dummy_plugin/app/controllers/dummy_autocompletes_controller.rb +4 -0
  77. data/spec/redmine/plugins/dummy_plugin/app/views/dummy_autocompletes/index.html.erb +3 -0
  78. data/spec/redmine/plugins/dummy_plugin/config/locales/en.yml +2 -0
  79. data/spec/redmine/plugins/dummy_plugin/config/routes.rb +1 -0
  80. data/spec/redmine/plugins/dummy_plugin/init.rb +34 -0
  81. data/spec/redmine/plugins/dummy_plugin/lib/dummy_plugin/easy_patch/redmine/controllers/issues_controller_patch.example +30 -0
  82. data/spec/redmine/plugins/dummy_plugin/lib/dummy_plugin/easy_patch/redmine/helpers/issues_helper_patch.example +30 -0
  83. data/spec/redmine/plugins/dummy_plugin/lib/dummy_plugin/easy_patch/redmine/models/issue_patch.example +30 -0
  84. data/spec/redmine/plugins/dummy_plugin/lib/dummy_plugin/hooks.rb +5 -0
  85. data/spec/redmine/plugins/dummy_plugin/lib/dummy_plugin/internals.rb +4 -0
  86. data/spec/redmine/public/images/jstoolbar/bt_precode.png +0 -0
  87. data/spec/redmine/public/images/reorder.png +0 -0
  88. data/spec/redmine/public/javascripts/jquery-1.11.1-ui-1.11.0-ujs-3.1.4.js +21 -0
  89. data/spec/redmine/public/javascripts/redmine_extensions/application.js +13 -0
  90. data/spec/redmine/public/javascripts/redmine_extensions/easy_togglers.js +58 -0
  91. data/spec/redmine/public/javascripts/redmine_extensions/jquery.entityarray.js +130 -0
  92. data/spec/redmine/public/javascripts/redmine_extensions/redmine_extensions.js +642 -0
  93. data/spec/redmine/public/javascripts/responsive.js +83 -0
  94. data/spec/redmine/public/stylesheets/responsive.css +799 -0
  95. data/spec/redmine/test/fixtures/files/import_dates.csv +4 -0
  96. data/spec/redmine/test/fixtures/files/import_iso8859-1.csv +3 -0
  97. data/spec/redmine/test/fixtures/files/import_issues.csv +4 -0
  98. data/spec/redmine/test/fixtures/ldap/slapd.centos6.conf +24 -0
  99. data/spec/redmine/test/fixtures/ldap/slapd.ubuntu.12.04.conf +23 -0
  100. data/spec/redmine/test/fixtures/mail_handler/issue_update_with_cc.eml +19 -0
  101. data/spec/redmine/test/fixtures/mail_handler/ticket_on_project_given_by_to_header.eml +60 -0
  102. data/spec/redmine/test/fixtures/mail_handler/ticket_with_text_attachment_iso-8859-2.eml +31 -0
  103. data/spec/redmine/test/functional/custom_field_enumerations_controller_test.rb +115 -0
  104. data/spec/redmine/test/functional/imports_controller_test.rb +205 -0
  105. data/spec/redmine/test/functional/sessions_controller_test.rb +138 -0
  106. data/spec/redmine/test/integration/api_test/search_test.rb +92 -0
  107. data/spec/redmine/test/integration/routing/imports_test.rb +36 -0
  108. data/spec/redmine/test/integration/sessions_test.rb +97 -0
  109. data/spec/redmine/test/ui/custom_fields_test_ui.rb +38 -0
  110. data/spec/redmine/test/unit/attachment_transaction_test.rb +76 -0
  111. data/spec/redmine/test/unit/helpers/journals_helper_test.rb +48 -0
  112. data/spec/redmine/test/unit/helpers/wiki_helper_test.rb +45 -0
  113. data/spec/redmine/test/unit/issue_import_test.rb +178 -0
  114. data/spec/redmine/test/unit/lib/redmine/acts/positioned_with_scope_test.rb +53 -0
  115. data/spec/redmine/test/unit/lib/redmine/acts/positioned_without_scope_test.rb +55 -0
  116. data/spec/redmine/test/unit/lib/redmine/field_format/enumeration_format_test.rb +91 -0
  117. data/spec/support/easy_queries_helpers.rb +0 -0
  118. metadata +216 -10
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Redmine - project management software
4
+ # Copyright (C) 2006-2016 Jean-Philippe Lang
5
+ #
6
+ # This program is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; either version 2
9
+ # of the License, or (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
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
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
+
20
+ module ImportsHelper
21
+ def options_for_mapping_select(import, field, options={})
22
+ tags = "".html_safe
23
+ blank_text = options[:required] ? "-- #{l(:actionview_instancetag_blank_option)} --" : " ".html_safe
24
+ tags << content_tag('option', blank_text, :value => '')
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
30
+ tags
31
+ end
32
+
33
+ def mapping_select_tag(import, field, options={})
34
+ name = "import_settings[mapping][#{field}]"
35
+ select_tag name, options_for_mapping_select(import, field, options), :id => "import_mapping_#{field}"
36
+ end
37
+
38
+ # Returns the options for the date_format setting
39
+ def date_format_options
40
+ Import::DATE_FORMATS.map do |f|
41
+ format = f.gsub('%', '').gsub(/[dmY]/) do
42
+ {'d' => 'DD', 'm' => 'MM', 'Y' => 'YYYY'}[$&]
43
+ end
44
+ [format, f]
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,90 @@
1
+ # Redmine - project management software
2
+ # Copyright (C) 2006-2016 Jean-Philippe Lang
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
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
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ class CustomFieldEnumeration < ActiveRecord::Base
19
+ include Redmine::SafeAttributes
20
+
21
+ belongs_to :custom_field
22
+ attr_accessible :name, :active, :position
23
+
24
+ validates_presence_of :name, :position, :custom_field_id
25
+ validates_length_of :name, :maximum => 60
26
+ validates_numericality_of :position, :only_integer => true
27
+ before_create :set_position
28
+
29
+ scope :active, lambda { where(:active => true) }
30
+
31
+ safe_attributes 'name',
32
+ 'active',
33
+ 'position'
34
+
35
+ def to_s
36
+ name.to_s
37
+ end
38
+
39
+ def objects_count
40
+ custom_values.count
41
+ end
42
+
43
+ def in_use?
44
+ objects_count > 0
45
+ end
46
+
47
+ alias :destroy_without_reassign :destroy
48
+ def destroy(reassign_to=nil)
49
+ if reassign_to
50
+ custom_values.update_all(:value => reassign_to.id.to_s)
51
+ end
52
+ destroy_without_reassign
53
+ end
54
+
55
+ def custom_values
56
+ custom_field.custom_values.where(:value => id.to_s)
57
+ end
58
+
59
+ def self.update_each(custom_field, attributes)
60
+ return unless attributes.is_a?(Hash)
61
+ transaction do
62
+ attributes.each do |enumeration_id, enumeration_attributes|
63
+ enumeration = custom_field.enumerations.find_by_id(enumeration_id)
64
+ if enumeration
65
+ if block_given?
66
+ yield enumeration, enumeration_attributes
67
+ else
68
+ enumeration.attributes = enumeration_attributes
69
+ end
70
+ unless enumeration.save
71
+ raise ActiveRecord::Rollback
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def self.fields_for_order_statement(table=nil)
79
+ table ||= table_name
80
+ columns = ['position']
81
+ columns.uniq.map {|field| "#{table}.#{field}"}
82
+ end
83
+
84
+ private
85
+
86
+ def set_position
87
+ max = self.class.where(:custom_field_id => custom_field_id).maximum(:position) || 0
88
+ self.position = max + 1
89
+ end
90
+ end
@@ -0,0 +1,249 @@
1
+ # Redmine - project management software
2
+ # Copyright (C) 2006-2016 Jean-Philippe Lang
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
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
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ require 'csv'
19
+
20
+ class Import < ActiveRecord::Base
21
+ has_many :items, :class_name => 'ImportItem', :dependent => :delete_all
22
+ belongs_to :user
23
+ serialize :settings
24
+
25
+ before_destroy :remove_file
26
+
27
+ validates_presence_of :filename, :user_id
28
+ validates_length_of :filename, :maximum => 255
29
+
30
+ DATE_FORMATS = [
31
+ '%Y-%m-%d',
32
+ '%d/%m/%Y',
33
+ '%m/%d/%Y',
34
+ '%d.%m.%Y',
35
+ '%d-%m-%Y'
36
+ ]
37
+
38
+ def initialize(*args)
39
+ super
40
+ self.settings ||= {}
41
+ end
42
+
43
+ def file=(arg)
44
+ return unless arg.present? && arg.size > 0
45
+
46
+ self.filename = generate_filename
47
+ Redmine::Utils.save_upload(arg, filepath)
48
+ end
49
+
50
+ def set_default_settings
51
+ separator = lu(user, :general_csv_separator)
52
+ if file_exists?
53
+ begin
54
+ content = File.read(filepath, 256)
55
+ separator = [',', ';'].sort_by {|sep| content.count(sep) }.last
56
+ rescue Exception => e
57
+ end
58
+ end
59
+ wrapper = '"'
60
+ encoding = lu(user, :general_csv_encoding)
61
+
62
+ date_format = lu(user, "date.formats.default", :default => "foo")
63
+ date_format = DATE_FORMATS.first unless DATE_FORMATS.include?(date_format)
64
+
65
+ self.settings.merge!(
66
+ 'separator' => separator,
67
+ 'wrapper' => wrapper,
68
+ 'encoding' => encoding,
69
+ 'date_format' => date_format
70
+ )
71
+ end
72
+
73
+ def to_param
74
+ filename
75
+ end
76
+
77
+ # Returns the full path of the file to import
78
+ # It is stored in tmp/imports with a random hex as filename
79
+ def filepath
80
+ if filename.present? && filename =~ /\A[0-9a-f]+\z/
81
+ File.join(Rails.root, "tmp", "imports", filename)
82
+ else
83
+ nil
84
+ end
85
+ end
86
+
87
+ # Returns true if the file to import exists
88
+ def file_exists?
89
+ filepath.present? && File.exists?(filepath)
90
+ end
91
+
92
+ # Returns the headers as an array that
93
+ # can be used for select options
94
+ def columns_options(default=nil)
95
+ i = -1
96
+ headers.map {|h| [h, i+=1]}
97
+ end
98
+
99
+ # Parses the file to import and updates the total number of items
100
+ def parse_file
101
+ count = 0
102
+ read_items {|row, i| count=i}
103
+ update_attribute :total_items, count
104
+ count
105
+ end
106
+
107
+ # Reads the items to import and yields the given block for each item
108
+ def read_items
109
+ i = 0
110
+ headers = true
111
+ read_rows do |row|
112
+ if i == 0 && headers
113
+ headers = false
114
+ next
115
+ end
116
+ i+= 1
117
+ yield row, i if block_given?
118
+ end
119
+ end
120
+
121
+ # Returns the count first rows of the file (including headers)
122
+ def first_rows(count=4)
123
+ rows = []
124
+ read_rows do |row|
125
+ rows << row
126
+ break if rows.size >= count
127
+ end
128
+ rows
129
+ end
130
+
131
+ # Returns an array of headers
132
+ def headers
133
+ first_rows(1).first || []
134
+ end
135
+
136
+ # Returns the mapping options
137
+ def mapping
138
+ settings['mapping'] || {}
139
+ end
140
+
141
+ # Imports items and returns the position of the last processed item
142
+ def run(options={})
143
+ max_items = options[:max_items]
144
+ max_time = options[:max_time]
145
+ current = 0
146
+ imported = 0
147
+ resume_after = items.maximum(:position) || 0
148
+ interrupted = false
149
+ started_on = Time.now
150
+
151
+ read_items do |row, position|
152
+ if (max_items && imported >= max_items) || (max_time && Time.now >= started_on + max_time)
153
+ interrupted = true
154
+ break
155
+ end
156
+ if position > resume_after
157
+ item = items.build
158
+ item.position = position
159
+
160
+ if object = build_object(row)
161
+ if object.save
162
+ item.obj_id = object.id
163
+ else
164
+ item.message = object.errors.full_messages.join("\n")
165
+ end
166
+ end
167
+
168
+ item.save!
169
+ imported += 1
170
+ end
171
+ current = position
172
+ end
173
+
174
+ if imported == 0 || interrupted == false
175
+ if total_items.nil?
176
+ update_attribute :total_items, current
177
+ end
178
+ update_attribute :finished, true
179
+ remove_file
180
+ end
181
+
182
+ current
183
+ end
184
+
185
+ def unsaved_items
186
+ items.where(:obj_id => nil)
187
+ end
188
+
189
+ def saved_items
190
+ items.where("obj_id IS NOT NULL")
191
+ end
192
+
193
+ private
194
+
195
+ def read_rows
196
+ return unless file_exists?
197
+
198
+ csv_options = {:headers => false}
199
+ csv_options[:encoding] = settings['encoding'].to_s.presence || 'UTF-8'
200
+ separator = settings['separator'].to_s
201
+ csv_options[:col_sep] = separator if separator.size == 1
202
+ wrapper = settings['wrapper'].to_s
203
+ csv_options[:quote_char] = wrapper if wrapper.size == 1
204
+
205
+ CSV.foreach(filepath, csv_options) do |row|
206
+ yield row if block_given?
207
+ end
208
+ end
209
+
210
+ def row_value(row, key)
211
+ if index = mapping[key].presence
212
+ row[index.to_i].presence
213
+ end
214
+ end
215
+
216
+ def row_date(row, key)
217
+ if s = row_value(row, key)
218
+ format = settings['date_format']
219
+ format = DATE_FORMATS.first unless DATE_FORMATS.include?(format)
220
+ Date.strptime(s, format) rescue s
221
+ end
222
+ end
223
+
224
+ # Builds a record for the given row and returns it
225
+ # To be implemented by subclasses
226
+ def build_object(row)
227
+ end
228
+
229
+ # Generates a filename used to store the import file
230
+ def generate_filename
231
+ Redmine::Utils.random_hex(16)
232
+ end
233
+
234
+ # Deletes the import file
235
+ def remove_file
236
+ if file_exists?
237
+ begin
238
+ File.delete filepath
239
+ rescue Exception => e
240
+ logger.error "Unable to delete file #{filepath}: #{e.message}" if logger
241
+ end
242
+ end
243
+ end
244
+
245
+ # Returns true if value is a string that represents a true value
246
+ def yes?(value)
247
+ value == lu(user, :general_text_yes) || value == '1'
248
+ end
249
+ end
@@ -0,0 +1,22 @@
1
+ # Redmine - project management software
2
+ # Copyright (C) 2006-2016 Jean-Philippe Lang
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
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
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ class ImportItem < ActiveRecord::Base
19
+ belongs_to :import
20
+
21
+ validates_presence_of :import_id, :position
22
+ end
@@ -0,0 +1,186 @@
1
+ # Redmine - project management software
2
+ # Copyright (C) 2006-2016 Jean-Philippe Lang
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
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
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ class IssueImport < Import
19
+
20
+ # Returns the objects that were imported
21
+ def saved_objects
22
+ object_ids = saved_items.pluck(:obj_id)
23
+ objects = Issue.where(:id => object_ids).order(:id).preload(:tracker, :priority, :status)
24
+ end
25
+
26
+ # Returns a scope of projects that user is allowed to
27
+ # import issue to
28
+ def allowed_target_projects
29
+ Project.allowed_to(user, :import_issues)
30
+ end
31
+
32
+ def project
33
+ project_id = mapping['project_id'].to_i
34
+ allowed_target_projects.find_by_id(project_id) || allowed_target_projects.first
35
+ end
36
+
37
+ # Returns a scope of trackers that user is allowed to
38
+ # import issue to
39
+ def allowed_target_trackers
40
+ Issue.allowed_target_trackers(project, user)
41
+ end
42
+
43
+ def tracker
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
48
+ end
49
+
50
+ # Returns true if missing categories should be created during the import
51
+ def create_categories?
52
+ user.allowed_to?(:manage_categories, project) &&
53
+ mapping['create_categories'] == '1'
54
+ end
55
+
56
+ # Returns true if missing versions should be created during the import
57
+ def create_versions?
58
+ user.allowed_to?(:manage_versions, project) &&
59
+ mapping['create_versions'] == '1'
60
+ end
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
+
75
+ private
76
+
77
+ def build_object(row)
78
+ issue = Issue.new
79
+ issue.author = user
80
+ issue.notify = false
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
+
89
+ attributes = {
90
+ 'project_id' => mapping['project_id'],
91
+ 'tracker_id' => tracker_id,
92
+ 'subject' => row_value(row, 'subject'),
93
+ 'description' => row_value(row, 'description')
94
+ }
95
+ if status_name = row_value(row, 'status')
96
+ if status_id = IssueStatus.named(status_name).first.try(:id)
97
+ attributes['status_id'] = status_id
98
+ end
99
+ end
100
+ issue.send :safe_attributes=, attributes, user
101
+
102
+ attributes = {}
103
+ if priority_name = row_value(row, 'priority')
104
+ if priority_id = IssuePriority.active.named(priority_name).first.try(:id)
105
+ attributes['priority_id'] = priority_id
106
+ end
107
+ end
108
+ if issue.project && category_name = row_value(row, 'category')
109
+ if category = issue.project.issue_categories.named(category_name).first
110
+ attributes['category_id'] = category.id
111
+ elsif create_categories?
112
+ category = issue.project.issue_categories.build
113
+ category.name = category_name
114
+ if category.save
115
+ attributes['category_id'] = category.id
116
+ end
117
+ end
118
+ end
119
+ if assignee_name = row_value(row, 'assigned_to')
120
+ if assignee = Principal.detect_by_keyword(issue.assignable_users, assignee_name)
121
+ attributes['assigned_to_id'] = assignee.id
122
+ end
123
+ end
124
+ if issue.project && version_name = row_value(row, 'fixed_version')
125
+ if version = issue.project.versions.named(version_name).first
126
+ attributes['fixed_version_id'] = version.id
127
+ elsif create_versions?
128
+ version = issue.project.versions.build
129
+ version.name = version_name
130
+ if version.save
131
+ attributes['fixed_version_id'] = version.id
132
+ end
133
+ end
134
+ end
135
+ if is_private = row_value(row, 'is_private')
136
+ if yes?(is_private)
137
+ attributes['is_private'] = '1'
138
+ end
139
+ end
140
+ if parent_issue_id = row_value(row, 'parent_issue_id')
141
+ if parent_issue_id =~ /\A(#)?(\d+)\z/
142
+ parent_issue_id = $2
143
+ if $1
144
+ attributes['parent_issue_id'] = parent_issue_id
145
+ elsif issue_id = items.where(:position => parent_issue_id).first.try(:obj_id)
146
+ attributes['parent_issue_id'] = issue_id
147
+ end
148
+ else
149
+ attributes['parent_issue_id'] = parent_issue_id
150
+ end
151
+ end
152
+ if start_date = row_date(row, 'start_date')
153
+ attributes['start_date'] = start_date
154
+ end
155
+ if due_date = row_date(row, 'due_date')
156
+ attributes['due_date'] = due_date
157
+ end
158
+ if estimated_hours = row_value(row, 'estimated_hours')
159
+ attributes['estimated_hours'] = estimated_hours
160
+ end
161
+ if done_ratio = row_value(row, 'done_ratio')
162
+ attributes['done_ratio'] = done_ratio
163
+ end
164
+
165
+ attributes['custom_field_values'] = issue.custom_field_values.inject({}) do |h, v|
166
+ value = case v.custom_field.field_format
167
+ when 'date'
168
+ row_date(row, "cf_#{v.custom_field.id}")
169
+ else
170
+ row_value(row, "cf_#{v.custom_field.id}")
171
+ end
172
+ if value
173
+ h[v.custom_field.id.to_s] = v.custom_field.value_from_keyword(value, issue)
174
+ end
175
+ h
176
+ end
177
+
178
+ issue.send :safe_attributes=, attributes, user
179
+
180
+ if issue.tracker_id != tracker_id
181
+ issue.tracker_id = nil
182
+ end
183
+
184
+ issue
185
+ end
186
+ end