dradis-projects 4.6.0 → 4.8.0

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 (26) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/app/controllers/dradis/plugins/projects/packages_controller.rb +6 -1
  4. data/app/controllers/dradis/plugins/projects/templates_controller.rb +6 -1
  5. data/app/views/dradis/plugins/projects/export/_index-content.html.erb +1 -1
  6. data/dradis-projects.gemspec +2 -2
  7. data/lib/dradis/plugins/projects/engine.rb +2 -2
  8. data/lib/dradis/plugins/projects/export/template.rb +1 -3
  9. data/lib/dradis/plugins/projects/export/v1/template.rb +8 -0
  10. data/lib/dradis/plugins/projects/export/v2/template.rb +8 -0
  11. data/lib/dradis/plugins/projects/export/v3/template.rb +8 -0
  12. data/lib/dradis/plugins/projects/export/v4/template.rb +207 -0
  13. data/lib/dradis/plugins/projects/gem_version.rb +1 -1
  14. data/lib/dradis/plugins/projects/upload/template.rb +2 -4
  15. data/lib/dradis/plugins/projects/upload/v1/template.rb +8 -0
  16. data/lib/dradis/plugins/projects/upload/v2/template.rb +8 -0
  17. data/lib/dradis/plugins/projects/upload/v3/template.rb +8 -0
  18. data/lib/dradis/plugins/projects/upload/v4/template.rb +651 -0
  19. data/spec/fixtures/files/with_invalid_states.xml +111 -0
  20. data/spec/fixtures/files/with_states.xml +111 -0
  21. data/spec/lib/dradis/plugins/projects/export/v2/template_spec.rb +9 -1
  22. data/spec/lib/dradis/plugins/projects/export/v4/template_spec.rb +84 -0
  23. data/spec/lib/dradis/plugins/projects/upload/v1/template_spec.rb +8 -0
  24. data/spec/lib/dradis/plugins/projects/upload/v2/template_spec.rb +9 -1
  25. data/spec/lib/dradis/plugins/projects/upload/v4/template_spec.rb +168 -0
  26. metadata +20 -10
@@ -0,0 +1,651 @@
1
+ module Dradis::Plugins::Projects::Upload::V4
2
+ module Template
3
+ class Importer < Dradis::Plugins::Projects::Upload::Template::Importer
4
+
5
+ attr_accessor :attachment_notes, :logger, :pending_changes, :users
6
+
7
+ ATTACHMENT_URL = %r{^!(/[a-z]+)?/(?:projects/\d+/)?nodes/(\d+)/attachments/(.+)!$}
8
+
9
+ def post_initialize(args={})
10
+ @lookup_table = {
11
+ categories: {},
12
+ nodes: {},
13
+ issues: {}
14
+ }
15
+
16
+ @pending_changes = {
17
+ # If the note has an attachment screenshot (i.e. !.../nodes/i/attachments/...!)
18
+ # we will fix the URL to point to the new Node ID.
19
+ #
20
+ # WARNING: we need a lookup table because one note may be referencing a
21
+ # different (yet unprocessed) node's attachments.
22
+ attachment_notes: [],
23
+
24
+ # evidence is parsed when nodes are parsed, but cannot be saved until
25
+ # issues have been created. Therefore, parse evidence into arrays until
26
+ # time for creation
27
+ evidence: [],
28
+
29
+ # likewise we also need to hold on to the XML about evidence activities
30
+ # and comments until after the evidence has been saved
31
+ evidence_activity: [],
32
+ evidence_comments: [],
33
+
34
+ # all children nodes, we will need to find the ID of their new parents.
35
+ orphan_nodes: []
36
+ }
37
+ end
38
+
39
+ private
40
+
41
+ def create_comments(commentable, xml_comments)
42
+ return true if xml_comments.empty?
43
+
44
+ xml_comments.each do |xml_comment|
45
+ author_email = xml_comment.at_xpath('author').text
46
+ comment = Comment.new(
47
+ commentable_id: commentable.id,
48
+ commentable_type: commentable.class.to_s,
49
+ content: xml_comment.at_xpath('content').text,
50
+ created_at: Time.at(xml_comment.at_xpath('created_at').text.to_i),
51
+ user_id: users[author_email]
52
+ )
53
+
54
+ if comment.user.nil?
55
+ comment.content = comment.content +
56
+ "\n\nOriginal author not available in this Dradis instance: "\
57
+ "#{author_email}."
58
+ end
59
+
60
+ unless validate_and_save(comment)
61
+ logger.info { "comment errors: #{comment.inspect}" }
62
+ return false
63
+ end
64
+ end
65
+ end
66
+
67
+ def create_activities(trackable, xml_trackable)
68
+ xml_trackable.xpath('activities/activity').each do |xml_activity|
69
+ # if 'validate_and_save(activity)' returns false, it needs
70
+ # to bubble up to the 'import' method so we can stop execution
71
+ return false unless create_activity(trackable, xml_activity)
72
+ end
73
+ end
74
+
75
+ def create_activity(trackable, xml_activity)
76
+ activity = trackable.activities.new(
77
+ action: xml_activity.at_xpath("action").text,
78
+ created_at: Time.at(xml_activity.at_xpath("created_at").text.to_i)
79
+ )
80
+
81
+ activity.project_id = project.id if activity.respond_to?(:project)
82
+
83
+ set_activity_user(activity, xml_activity.at_xpath("user_email").text)
84
+
85
+ validate_and_save(activity)
86
+ end
87
+
88
+ def create_issue(issue, xml_issue)
89
+ # TODO: Need to find some way of checking for dups
90
+ # May be combination of text, category_id and created_at
91
+ issue.author = xml_issue.at_xpath('author').text.strip
92
+ issue.state = xml_issue.at_xpath('state')&.text || :published
93
+ issue.text = xml_issue.at_xpath('text').text
94
+ issue.node = project.issue_library
95
+ issue.category = Category.issue
96
+
97
+ return false unless validate_and_save(issue)
98
+
99
+ return false unless create_activities(issue, xml_issue)
100
+
101
+ return false unless create_comments(issue, xml_issue.xpath('comments/comment'))
102
+
103
+ true
104
+ end
105
+
106
+ def finalize(template)
107
+ logger.info { 'Wrapping up...' }
108
+
109
+ finalize_nodes()
110
+ finalize_evidence()
111
+ finalize_attachments()
112
+
113
+ logger.info { 'Done.' }
114
+ end
115
+
116
+ def finalize_attachments
117
+ # Adjust attachment URLs for new Node IDs
118
+ pending_changes[:attachment_notes].each do |item|
119
+ text_attr =
120
+ if defined?(ContentBlock) && item.is_a?(ContentBlock)
121
+ :content
122
+ else
123
+ :text
124
+ end
125
+
126
+ logger.info { "Adjusting screenshot URLs: #{item.class.name} ##{item.id}" }
127
+
128
+ new_text = update_attachment_references(item.send(text_attr))
129
+ item.send(text_attr.to_s + "=", new_text)
130
+
131
+ raise "Couldn't save note attachment URL for #{item.class.name} ##{item.id}" unless validate_and_save(item)
132
+ end
133
+ end
134
+
135
+ # Save the Evidence instance to the DB now that we have populated the
136
+ # original issues.
137
+ def finalize_evidence
138
+ pending_changes[:evidence].each_with_index do |evidence, i|
139
+ logger.info { "Setting issue_id for evidence" }
140
+ evidence.issue_id = lookup_table[:issues][evidence.issue_id]
141
+
142
+ evidence.content = update_attachment_references(evidence.content)
143
+
144
+ raise "Couldn't save Evidence :issue_id / attachment URL Evidence ##{evidence.id}" unless validate_and_save(evidence)
145
+
146
+ pending_changes[:evidence_activity][i].each do |xml_activity|
147
+ raise "Couldn't create activity for Evidence ##{evidence.id}" unless create_activity(evidence, xml_activity)
148
+ end
149
+
150
+ xml_comments = pending_changes[:evidence_comments][i]
151
+ raise "Couldn't create comments for Evidence ##{evidence.id}" unless create_comments(evidence, xml_comments)
152
+ end
153
+ end
154
+
155
+ # Fix relationships between nodes to ensure parents and childrens match
156
+ # with the new assigned :ids
157
+ def finalize_nodes
158
+ pending_changes[:orphan_nodes].each do |node|
159
+ logger.info { "Finding parent for orphaned node: #{node.label}. Former parent was #{node.parent_id}" }
160
+ node.parent_id = lookup_table[:nodes][node.parent_id]
161
+ raise "Couldn't save node parent for Node ##{node.id}" unless validate_and_save(node)
162
+ end
163
+ end
164
+
165
+ # Go through the categories, keep a translation table between the old
166
+ # category id and the new ones so we know to which category we should
167
+ # assign our notes
168
+ def parse_categories(template)
169
+ logger.info { 'Processing Categories...' }
170
+
171
+ template.xpath('dradis-template/categories/category').each do |xml_category|
172
+ old_id = Integer(xml_category.at_xpath('id').text.strip)
173
+ name = xml_category.at_xpath('name').text.strip
174
+ category = nil
175
+
176
+ # Prevent creating duplicate categories
177
+ logger.info { "Looking for category: #{name}" }
178
+ category = Category.find_or_create_by!(name: name)
179
+ lookup_table[:categories][old_id] = category.id
180
+ end
181
+
182
+ logger.info { 'Done.' }
183
+ end
184
+
185
+ # Go through the issues, keep a translation table between the old
186
+ # issue id and the new ones. This is important for importing evidence
187
+ # Will need to adjust node ID after generating node structure
188
+ def parse_issues(template)
189
+ issue = nil
190
+
191
+ logger.info { 'Processing Issues...' }
192
+
193
+ template.xpath('dradis-template/issues/issue').each do |xml_issue|
194
+ issue = Issue.new
195
+
196
+ return false unless create_issue(issue, xml_issue)
197
+
198
+ if issue.text =~ %r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}
199
+ pending_changes[:attachment_notes] << issue
200
+ end
201
+
202
+ old_id = Integer(xml_issue.at_xpath('id').text.strip)
203
+ lookup_table[:issues][old_id] = issue.id
204
+ logger.info{ "New issue detected: #{issue.title}" }
205
+ end
206
+
207
+ logger.info { 'Done.' }
208
+
209
+ end
210
+
211
+ def parse_methodologies(template)
212
+ methodology_category = Category.default
213
+ methodology_library = project.methodology_library
214
+
215
+ logger.info { 'Processing Methodologies...' }
216
+
217
+ template.xpath('dradis-template/methodologies/methodology').each do |xml_methodology|
218
+ # FIXME: this is wrong in a few levels, we should be able to save a
219
+ # Methodology instance calling .save() but the current implementation
220
+ # of the model would consider this a 'methodology template' and not an
221
+ # instance.
222
+ #
223
+ # Also, methodology notes don't have a valid author, see
224
+ # MethodologiesController#create action (i.e. 'methodology builder' is
225
+ # used).
226
+ Note.create!(
227
+ author: 'methodology importer',
228
+ node_id: methodology_library.id,
229
+ category_id: methodology_category.id,
230
+ text: xml_methodology.at_xpath('text').text
231
+ )
232
+ end
233
+
234
+ logger.info { 'Done.' }
235
+ end
236
+
237
+ def parse_node(xml_node)
238
+ element = xml_node.at_xpath('type-id')
239
+ type_id = element.text.nil? ? nil : element.text.strip
240
+ label = xml_node.at_xpath('label').text.strip
241
+ element = xml_node.at_xpath('parent-id')
242
+ parent_id = element.text.blank? ? nil : element.text.strip
243
+
244
+ # Node positions
245
+ element = xml_node.at_xpath('position')
246
+ position = (element && !element.text.nil?) ? element.text.strip : nil
247
+
248
+ # Node properties
249
+ element = xml_node.at_xpath('properties')
250
+ properties = (element && !element.text.blank?) ? element.text.strip : nil
251
+
252
+ created_at = xml_node.at_xpath('created-at')
253
+ updated_at = xml_node.at_xpath('updated-at')
254
+
255
+ logger.info { "New node detected: #{label}, parent_id: #{parent_id}, type_id: #{type_id}" }
256
+
257
+ # There are exceptions to the rule, when it does not make sense to have
258
+ # more than one of this nodes, in any given tree:
259
+ # - the Configuration.uploadsNode node (detected by its label)
260
+ # - any nodes with type different from DEFAULT or HOST
261
+ if label == Configuration.plugin_uploads_node
262
+ node = project.nodes.create_with(type_id: type_id, parent_id: parent_id)
263
+ .find_or_create_by!(label: label)
264
+ elsif Node::Types::USER_TYPES.exclude?(type_id.to_i)
265
+ node = project.nodes.create_with(label: label)
266
+ .find_or_create_by!(type_id: type_id)
267
+ else
268
+ # We don't want to validate child nodes here yet since they always
269
+ # have invalid parent id's. They'll eventually be validated in the
270
+ # finalize_nodes method.
271
+ has_nil_parent = !parent_id
272
+ node =
273
+ project.nodes.new(
274
+ type_id: type_id,
275
+ label: label,
276
+ parent_id: parent_id,
277
+ position: position
278
+ )
279
+ node.save!(validate: has_nil_parent)
280
+ pending_changes[:orphan_nodes] << node if parent_id
281
+ end
282
+
283
+ if properties
284
+ node.raw_properties = properties
285
+ node.save!(validate: has_nil_parent)
286
+ end
287
+
288
+ node.update_attribute(:created_at, created_at.text.strip) if created_at
289
+ node.update_attribute(:updated_at, updated_at.text.strip) if updated_at
290
+
291
+ raise "Couldn't create activities for Node ##{node.id}" unless create_activities(node, xml_node)
292
+
293
+ parse_node_notes(node, xml_node)
294
+ parse_node_evidence(node, xml_node)
295
+
296
+ node
297
+ end
298
+
299
+ def parse_nodes(template)
300
+ logger.info { 'Processing Nodes...' }
301
+
302
+ # Re generate the Node tree structure
303
+ template.xpath('dradis-template/nodes/node').each do |xml_node|
304
+
305
+ node = parse_node(xml_node)
306
+
307
+ # keep track of reassigned ids
308
+ # Convert the id to an integer as it has no place being a string, or
309
+ # directory path. We later use this ID to build a directory structure
310
+ # to place attachments and without validation opens the potential for
311
+ # path traversal.
312
+ node_original_id = Integer(xml_node.at_xpath('id').text.strip)
313
+ lookup_table[:nodes][node_original_id] = node.id
314
+ end
315
+
316
+ logger.info { 'Done.' }
317
+ end
318
+
319
+ # Create array of evidence from xml input. Cannot store in DB until we
320
+ # have a new issue id
321
+ def parse_node_evidence(node, xml_node)
322
+ xml_node.xpath('evidence/evidence').each do |xml_evidence|
323
+ if xml_evidence.at_xpath('author') != nil
324
+ created_at = xml_evidence.at_xpath('created-at')
325
+ updated_at = xml_evidence.at_xpath('updated-at')
326
+
327
+ evidence = Evidence.new(
328
+ author: xml_evidence.at_xpath('author').text.strip,
329
+ node_id: node.id,
330
+ content: xml_evidence.at_xpath('content').text,
331
+ issue_id: xml_evidence.at_xpath('issue-id').text.strip
332
+ )
333
+
334
+ evidence.update_attribute(:created_at, created_at.text.strip) if created_at
335
+ evidence.update_attribute(:updated_at, updated_at.text.strip) if updated_at
336
+
337
+ pending_changes[:evidence] << evidence
338
+ pending_changes[:evidence_activity] << xml_evidence.xpath('activities/activity')
339
+ pending_changes[:evidence_comments] << xml_evidence.xpath('comments/comment')
340
+
341
+ logger.info { "\tNew evidence added." }
342
+ end
343
+ end
344
+ end
345
+
346
+ def parse_node_notes(node, xml_node)
347
+ xml_node.xpath('notes/note').each do |xml_note|
348
+
349
+ if xml_note.at_xpath('author') != nil
350
+ old_id = Integer(xml_note.at_xpath('category-id').text.strip)
351
+ new_id = lookup_table[:categories][old_id]
352
+
353
+ created_at = xml_note.at_xpath('created-at')
354
+ updated_at = xml_note.at_xpath('updated-at')
355
+
356
+ logger.info { "Note category rewrite, used to be #{old_id}, now is #{new_id}" }
357
+ note = Note.create!(
358
+ author: xml_note.at_xpath('author').text.strip,
359
+ node_id: node.id,
360
+ category_id: new_id,
361
+ text: xml_note.at_xpath('text').text
362
+ )
363
+
364
+ note.update_attribute(:created_at, created_at.text.strip) if created_at
365
+ note.update_attribute(:updated_at, updated_at.text.strip) if updated_at
366
+
367
+ raise "Couldn't save Note" unless validate_and_save(note)
368
+
369
+ if note.text =~ %r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}
370
+ pending_changes[:attachment_notes] << note
371
+ end
372
+
373
+ raise "Couldn't create activities for Note ##{note.id}" unless create_activities(note, xml_note)
374
+ raise "Couldn't create comments for Note ##{note.id}" unless create_comments(note, xml_note.xpath('comments/comment'))
375
+
376
+ logger.info { "\tNew note added." }
377
+ end
378
+ end
379
+ end
380
+
381
+ def parse_report_content(template); end
382
+
383
+ def parse_tags(template)
384
+ logger.info { 'Processing Tags...' }
385
+
386
+ template.xpath('dradis-template/tags/tag').each do |xml_tag|
387
+ name = xml_tag.at_xpath('name').text()
388
+ tag_params = { name: name }
389
+ tag_params[:project_id] = project.id if Tag.has_attribute?(:project_id)
390
+ tag = Tag.where(tag_params).first_or_create
391
+ logger.info { "New tag detected: #{name}" }
392
+
393
+ xml_tag.xpath('./taggings/tagging').each do |xml_tagging|
394
+ old_taggable_id = Integer(xml_tagging.at_xpath('taggable-id').text())
395
+ taggable_type = xml_tagging.at_xpath('taggable-type').text()
396
+
397
+ new_taggable_id = case taggable_type
398
+ when 'Note'
399
+ lookup_table[:issues][old_taggable_id]
400
+ end
401
+
402
+ Tagging.create! tag: tag,
403
+ taggable_id: new_taggable_id, taggable_type: taggable_type
404
+ end
405
+ end
406
+
407
+ logger.info { 'Done.' }
408
+ end
409
+
410
+ def set_activity_user(activity, email)
411
+ if Activity.column_names.include?('user')
412
+ activity.user = email
413
+ else
414
+ activity.user_id = user_id_for_email(email)
415
+ end
416
+ end
417
+
418
+ def update_attachment_references(string)
419
+ string.gsub(ATTACHMENT_URL) do |attachment|
420
+ node_id = lookup_table[:nodes][$2.to_i]
421
+ if node_id
422
+ "!%s/projects/%d/nodes/%d/attachments/%s!" % [$1, project.id, node_id, $3]
423
+ else
424
+ logger.error { "The attachment wasn't included in the package: #{attachment}" }
425
+ attachment
426
+ end
427
+ end
428
+ end
429
+
430
+ def user_id_for_email(email)
431
+ users[email] || @default_user_id
432
+ end
433
+
434
+ # Cache users to cut down on excess SQL requests
435
+ def users
436
+ @users ||= begin
437
+ User.select([:id, :email]).all.each_with_object({}) do |user, hash|
438
+ hash[user.email] = user.id
439
+ end
440
+ end
441
+ end
442
+
443
+ def validate_and_save(instance)
444
+ if instance.save
445
+ return true
446
+ else
447
+ @logger.info{ "Malformed #{ instance.class.name } detected: #{ instance.errors.full_messages }" }
448
+ return false
449
+ end
450
+ end
451
+
452
+ # Private: Given a XML node contianing assignee information this method
453
+ # tries to recreate the assignment in the new project.
454
+ #
455
+ # * If the user exists in this instance: assign the card to that user
456
+ # (no matter if the user is not a project author).
457
+ # * If the user doesn't exist, don't creat an assiment and add a note
458
+ # inside the card's description.
459
+ #
460
+ # card - the Card object we're creating assignments for.
461
+ # xml_assignee - the Nokogiri::XML::Node that contains node assignment
462
+ # information.
463
+ #
464
+ # Returns nothing, but creates a new Assignee for this card.
465
+ def create_assignee(card, xml_assignee)
466
+ email = xml_assignee.text()
467
+ user_id = user_id_for_email(email)
468
+
469
+ if user_id == -1
470
+ old_assignee_field = card.fields['FormerAssignees'] || ''
471
+ card.set_field 'FormerAssignees', old_assignee_field << "* #{email}\n"
472
+ else
473
+ old_assignee_ids = card.assignee_ids
474
+ card.assignee_ids = old_assignee_ids + [user_id]
475
+ end
476
+ end
477
+
478
+ # Private: Reassign cross-references once all the objects in the project
479
+ # have been recreated.
480
+ #
481
+ # No arguments received, but the methods relies on :lookup_table and
482
+ # :pending_changes provided by dradis-projects.
483
+ #
484
+ # Returns nothing.
485
+ def finalize_cards
486
+ logger.info { 'Reassigning card positions...' }
487
+
488
+ # Fix the :previous_id with the new card IDs
489
+ pending_changes[:cards].each do |card|
490
+ card.previous_id = lookup_table[:cards][card.previous_id]
491
+ raise "Couldn't save card's position" unless validate_and_save(card)
492
+ end
493
+
494
+ logger.info { 'Done.' }
495
+ end
496
+
497
+ # Private: Reassign the List's :previous_id now that we know what are the
498
+ # new IDs that correspond to all List objects in the import.
499
+ #
500
+ # No arguments received, but the method relies on :lookup_table and
501
+ # :pending_changes provided by dradis-projects.
502
+ #
503
+ # Returns nothing.
504
+ def finalize_lists
505
+ logger.info { 'Reassigning list positions...' }
506
+
507
+ # Fix the :previous_id with the new card IDs
508
+ pending_changes[:lists].each do |list|
509
+ list.previous_id = lookup_table[:lists][list.previous_id]
510
+ raise "Couldn't save list's position" unless validate_and_save(list)
511
+ end
512
+
513
+ logger.info { 'Done.' }
514
+ end
515
+
516
+ # Private: Restore Board, List and Card information from the project
517
+ # template.
518
+ def parse_methodologies(template)
519
+ if template_version == 1
520
+ # Restore Board from old xml methodology format
521
+ process_v1_methodologies(template)
522
+ else
523
+ process_v2_methodologies(template)
524
+ end
525
+ end
526
+
527
+ # Private: For each XML card block, we're creating a new Card instance,
528
+ # restoring the card's Activities and Assignments.
529
+ #
530
+ # list - the List instance that will hold this Card.
531
+ # xml_card - the Nokogiri::XML node containing the card's data.
532
+ #
533
+ # Returns nothing, but makes use of the :lookup_table and :pending_changes
534
+ # variables to store information that will be used during the
535
+ # :finalize_cards method.
536
+ def process_card(list, xml_card)
537
+ due_date = xml_card.at_xpath('due_date').text
538
+ due_date = Date.iso8601(due_date) unless due_date.empty?
539
+
540
+ card = list.cards.create name: xml_card.at_xpath('name').text,
541
+ description: xml_card.at_xpath('description').text,
542
+ due_date: due_date,
543
+ previous_id: xml_card.at_xpath('previous_id').text&.to_i
544
+
545
+ xml_card.xpath('activities/activity').each do |xml_activity|
546
+ raise "Couldn't create activity for Card ##{card.id}" unless create_activity(card, xml_activity)
547
+ end
548
+
549
+ xml_card.xpath('assignees/assignee').each do |xml_assignee|
550
+ raise "Couldn't create assignment for Card ##{card.id}" unless create_assignee(card, xml_assignee)
551
+ end
552
+
553
+ raise "Couldn't create comments for Card ##{card.id}" unless create_comments(card, xml_card.xpath('comments/comment'))
554
+
555
+ xml_id = Integer(xml_card.at_xpath('id').text)
556
+ lookup_table[:cards][xml_id] = card.id
557
+ pending_changes[:cards] << card
558
+ end
559
+
560
+ # Private: Initial pass over ./methodologies/ section of the tempalte
561
+ # document to extract Board, List and Card information. Some of the
562
+ # objects will contain invalid references (e.g. the former :previous_id
563
+ # of a card will need to be reassigned) that we will fix at a later stage.
564
+ #
565
+ # template - A Nokogiri::XML document containing the project template
566
+ # data.
567
+ #
568
+ # Returns nothing.
569
+ def process_methodologies(template)
570
+ logger.info { 'Processing Methodologies...' }
571
+
572
+ lookup_table[:cards] = {}
573
+ lookup_table[:lists] = {}
574
+ pending_changes[:cards] = []
575
+ pending_changes[:lists] = []
576
+
577
+ template.xpath('dradis-template/methodologies/board').each do |xml_board|
578
+ xml_node_id = xml_board.at_xpath('node_id').try(:text)
579
+ node_id =
580
+ if xml_node_id.present?
581
+ lookup_table[:nodes][xml_node_id.to_i]
582
+ else
583
+ project.methodology_library.id
584
+ end
585
+
586
+ board = content_service.create_board(
587
+ name: xml_board.at_xpath('name').text,
588
+ node_id: node_id
589
+ )
590
+
591
+ xml_board.xpath('./list').each do |xml_list|
592
+ list = board.lists.create name: xml_list.at_xpath('name').text,
593
+ previous_id: xml_list.at_xpath('previous_id').text&.to_i
594
+ xml_id = Integer(xml_list.at_xpath('id').text)
595
+
596
+ lookup_table[:lists][xml_id] = list.id
597
+ pending_changes[:lists] << list
598
+
599
+ xml_list.xpath('./card').each do |xml_card|
600
+ process_card(list, xml_card)
601
+ end
602
+ end
603
+ end
604
+
605
+ logger.info { 'Done.' }
606
+ end
607
+
608
+ # Private: Pass over old ./methodologies/ sections of the template
609
+ # document to extract Board, List and Card information.
610
+ #
611
+ # template - A Nokogiri::XML document containing the project template
612
+ # data.
613
+ #
614
+ # Returns nothing.
615
+ def process_v1_methodologies(template)
616
+ xml_methodologies = template.xpath('dradis-template/methodologies/methodology')
617
+ return if xml_methodologies.empty?
618
+
619
+ logger.info { 'Processing V1 Methodologies...' }
620
+
621
+ migration = MethodologyMigrationService.new(project.id)
622
+
623
+ xml_methodologies.each do |xml_methodology|
624
+ migration.migrate(
625
+ Methodology.new(content: xml_methodology.at_xpath('text').text)
626
+ )
627
+ end
628
+
629
+ logger.info { 'Done.' }
630
+ end
631
+
632
+ # Private: Pass over new ./methodologies/ sections of the template
633
+ # document to extract Board, List and Card information.
634
+ #
635
+ # template - A Nokogiri::XML document containing the project template
636
+ # data.
637
+ #
638
+ # Returns nothing.
639
+ def process_v2_methodologies(template)
640
+ # Restore Board
641
+ process_methodologies(template)
642
+
643
+ # Reassign Card's :previous_id and :assginees
644
+ finalize_cards()
645
+
646
+ # Reassign List's :previous id
647
+ finalize_lists()
648
+ end
649
+ end
650
+ end
651
+ end