eco-helpers 2.6.4 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +95 -0
  4. data/CHANGELOG.md +128 -2
  5. data/Rakefile +13 -7
  6. data/eco-helpers.gemspec +2 -2
  7. data/lib/eco/api/common/loaders/base.rb +2 -2
  8. data/lib/eco/api/common/loaders/case_base.rb +1 -1
  9. data/lib/eco/api/common/loaders/config/workflow/mailer.rb +5 -5
  10. data/lib/eco/api/common/loaders/error_handler.rb +8 -5
  11. data/lib/eco/api/common/loaders/parser.rb +44 -22
  12. data/lib/eco/api/common/loaders/policy.rb +6 -4
  13. data/lib/eco/api/common/loaders/use_case.rb +13 -7
  14. data/lib/eco/api/common/people/base_parser.rb +0 -2
  15. data/lib/eco/api/common/people/default_parsers/boolean_parser.rb +0 -1
  16. data/lib/eco/api/common/people/default_parsers/csv_parser.rb +1 -1
  17. data/lib/eco/api/common/people/default_parsers/date_parser.rb +64 -12
  18. data/lib/eco/api/common/people/default_parsers/freemium_parser.rb +0 -1
  19. data/lib/eco/api/common/people/default_parsers/login_providers_parser.rb +13 -5
  20. data/lib/eco/api/common/people/default_parsers/multi_parser.rb +0 -1
  21. data/lib/eco/api/common/people/default_parsers/numeric_parser.rb +18 -5
  22. data/lib/eco/api/common/people/default_parsers/policy_groups_parser.rb +8 -8
  23. data/lib/eco/api/common/people/default_parsers/select_parser.rb +50 -26
  24. data/lib/eco/api/common/people/default_parsers/send_invites_parser.rb +6 -6
  25. data/lib/eco/api/common/people/default_parsers/xls_parser.rb +9 -12
  26. data/lib/eco/api/common/people/default_parsers.rb +1 -12
  27. data/lib/eco/api/common/people/entries.rb +13 -13
  28. data/lib/eco/api/common/people/entry_factory.rb +76 -45
  29. data/lib/eco/api/common/people/person_attribute_parser.rb +8 -12
  30. data/lib/eco/api/common/people/person_entry.rb +86 -75
  31. data/lib/eco/api/common/people/person_entry_attribute_mapper.rb +60 -44
  32. data/lib/eco/api/common/people/person_factory.rb +30 -22
  33. data/lib/eco/api/common/people/person_modifier.rb +11 -13
  34. data/lib/eco/api/common/people/person_parser.rb +101 -39
  35. data/lib/eco/api/common/people/supervisor_helpers.rb +25 -26
  36. data/lib/eco/api/common/session/base_session.rb +9 -9
  37. data/lib/eco/api/common/session/environment.rb +7 -5
  38. data/lib/eco/api/common/session/sftp.rb +59 -32
  39. data/lib/eco/api/common/version_patches/exception.rb +11 -13
  40. data/lib/eco/api/error.rb +32 -20
  41. data/lib/eco/api/organization/node_classifications.rb +82 -0
  42. data/lib/eco/api/organization/policy_groups.rb +4 -6
  43. data/lib/eco/api/organization/tag_tree.rb +169 -93
  44. data/lib/eco/api/organization.rb +1 -0
  45. data/lib/eco/api/session/batch/job.rb +1 -1
  46. data/lib/eco/api/session/config/tagtree.rb +41 -23
  47. data/lib/eco/api/session/config/workflow.rb +113 -88
  48. data/lib/eco/api/session/config.rb +6 -0
  49. data/lib/eco/api/session.rb +51 -29
  50. data/lib/eco/api/usecases/base_io.rb +28 -25
  51. data/lib/eco/api/usecases/default/locations/cli/tagtree_extract_cli.rb +7 -2
  52. data/lib/eco/api/usecases/default/locations/cli/tagtree_upload_cli.rb +21 -0
  53. data/lib/eco/api/usecases/default/locations/csv_to_tree_case.rb +3 -3
  54. data/lib/eco/api/usecases/default/locations/tagtree_extract_case.rb +54 -23
  55. data/lib/eco/api/usecases/default/locations/tagtree_upload_case.rb +87 -0
  56. data/lib/eco/api/usecases/default/locations.rb +1 -0
  57. data/lib/eco/api/usecases/default/people/analyse_people_case.rb +60 -56
  58. data/lib/eco/api/usecases/default/people/change_email_case.rb +8 -9
  59. data/lib/eco/api/usecases/default/people/clean_unknown_tags_case.rb +13 -11
  60. data/lib/eco/api/usecases/default/people/clear_abilities_case.rb +2 -2
  61. data/lib/eco/api/usecases/default/people/org_data_convert_case.rb +25 -27
  62. data/lib/eco/api/usecases/default/people/refresh_case.rb +2 -2
  63. data/lib/eco/api/usecases/default/people/reinvite_trans_case.rb +1 -1
  64. data/lib/eco/api/usecases/default/people/reinvite_trans_cli.rb +0 -1
  65. data/lib/eco/api/usecases/default/people/restore_db_case.rb +39 -34
  66. data/lib/eco/api/usecases/default/people/set_default_tag_case.rb +19 -15
  67. data/lib/eco/api/usecases/default/people/supers_cyclic_identify_case.rb +16 -12
  68. data/lib/eco/api/usecases/default_cases/hris_case.rb +17 -15
  69. data/lib/eco/api/usecases/default_cases/samples/sftp_case.rb +30 -16
  70. data/lib/eco/api/usecases/graphql/base.rb +5 -3
  71. data/lib/eco/api/usecases/graphql/helpers/base/case_env.rb +4 -1
  72. data/lib/eco/api/usecases/graphql/helpers/base/graphql_env.rb +14 -0
  73. data/lib/eco/api/usecases/graphql/helpers/base.rb +5 -4
  74. data/lib/eco/api/usecases/graphql/helpers/location/base/classifications_parser.rb +60 -0
  75. data/lib/eco/api/usecases/graphql/helpers/location/base/tree_tracking.rb +72 -0
  76. data/lib/eco/api/usecases/graphql/helpers/location/base.rb +25 -59
  77. data/lib/eco/api/usecases/graphql/helpers/location/command/diff/as_update.rb +59 -0
  78. data/lib/eco/api/usecases/graphql/helpers/location/command/diff/compare.rb +49 -0
  79. data/lib/eco/api/usecases/graphql/helpers/location/command/diff.rb +11 -0
  80. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/commandable.rb +46 -0
  81. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_archive.rb +23 -0
  82. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_unarchive.rb +65 -0
  83. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable.rb +49 -0
  84. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable/relation_safe_sort.rb +119 -0
  85. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable.rb +59 -0
  86. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages.rb +82 -0
  87. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs.rb +20 -0
  88. data/lib/eco/api/usecases/graphql/helpers/location/command/optimizations.rb +84 -0
  89. data/lib/eco/api/usecases/graphql/helpers/location/command/result.rb +4 -4
  90. data/lib/eco/api/usecases/graphql/helpers/location/command/results.rb +24 -12
  91. data/lib/eco/api/usecases/graphql/helpers/location/command.rb +21 -24
  92. data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_map.rb +1 -1
  93. data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_set.rb +10 -11
  94. data/lib/eco/api/usecases/graphql/helpers/location/tags_remap.rb +8 -9
  95. data/lib/eco/api/usecases/graphql/samples/location/command/dsl.rb +41 -12
  96. data/lib/eco/api/usecases/graphql/samples/location/command/results.rb +11 -80
  97. data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +89 -0
  98. data/lib/eco/api/usecases/graphql/samples/location/command/service.rb +6 -0
  99. data/lib/eco/api/usecases/graphql/samples/location/command/track_changed_ids.rb +89 -0
  100. data/lib/eco/api/usecases/graphql/samples/location/command.rb +3 -0
  101. data/lib/eco/api/usecases/graphql/samples/location/service/base.rb +9 -0
  102. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/heading.rb +18 -0
  103. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/inputable.rb +53 -0
  104. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing/classifications.rb +34 -0
  105. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing/helpers.rb +28 -0
  106. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing.rb +46 -0
  107. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible.rb +38 -0
  108. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff.rb +105 -0
  109. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/discarded.rb +16 -0
  110. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/input.rb +15 -0
  111. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/node_attr_maps.rb +22 -0
  112. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/parser.rb +45 -0
  113. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter.rb +36 -0
  114. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/output.rb +56 -0
  115. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list.rb +41 -0
  116. data/lib/eco/api/usecases/graphql/samples/location/service.rb +8 -0
  117. data/lib/eco/api/usecases/graphql/samples/location.rb +1 -0
  118. data/lib/eco/api/usecases/graphql/utils/sftp.rb +96 -36
  119. data/lib/eco/api/usecases/ooze_cases/export_register_case.rb +8 -6
  120. data/lib/eco/api/usecases/ooze_samples/helpers/creatable.rb +4 -3
  121. data/lib/eco/api/usecases/ooze_samples/helpers/exportable_ooze.rb +39 -25
  122. data/lib/eco/api/usecases/ooze_samples/helpers/exportable_register.rb +13 -15
  123. data/lib/eco/api/usecases/ooze_samples/helpers/filters.rb +50 -21
  124. data/lib/eco/api/usecases/ooze_samples/helpers/ooze_handlers.rb +21 -11
  125. data/lib/eco/api/usecases/ooze_samples/helpers/rescuable.rb +2 -0
  126. data/lib/eco/api/usecases/ooze_samples/helpers/shortcuts.rb +49 -43
  127. data/lib/eco/api/usecases/ooze_samples/ooze_base_case.rb +17 -19
  128. data/lib/eco/api/usecases/ooze_samples/register_export_case.rb +48 -43
  129. data/lib/eco/api/usecases/ooze_samples/register_update_case.rb +33 -34
  130. data/lib/eco/api/usecases/ooze_samples/target_oozes_update_case.rb +8 -10
  131. data/lib/eco/api/usecases.rb +0 -1
  132. data/lib/eco/cli/config/use_cases.rb +31 -29
  133. data/lib/eco/cli_default/workflow.rb +13 -14
  134. data/lib/eco/csv/table.rb +34 -25
  135. data/lib/eco/data/hashes/array_diff.rb +24 -35
  136. data/lib/eco/data/hashes/diff_result/meta.rb +131 -0
  137. data/lib/eco/data/hashes/diff_result.rb +65 -57
  138. data/lib/eco/data/hashes/sanke_camel_indifferent_access.rb +278 -0
  139. data/lib/eco/data/hashes.rb +1 -1
  140. data/lib/eco/data/locations/convert.rb +1 -1
  141. data/lib/eco/data/locations/node_base/csv_convert.rb +19 -9
  142. data/lib/eco/data/locations/node_base/parsing.rb +4 -2
  143. data/lib/eco/data/locations/node_base/treeify.rb +149 -132
  144. data/lib/eco/data/locations/node_base.rb +15 -4
  145. data/lib/eco/data/locations/node_diff/accessors.rb +13 -5
  146. data/lib/eco/data/locations/node_diff/nodes_diff/clustered_treeify.rb +101 -0
  147. data/lib/eco/data/locations/node_diff/nodes_diff/diffs_tree.rb +99 -0
  148. data/lib/eco/data/locations/node_diff/{selectors.rb → nodes_diff/selectors.rb} +1 -1
  149. data/lib/eco/data/locations/node_diff/nodes_diff.rb +50 -35
  150. data/lib/eco/data/locations/node_diff.rb +45 -17
  151. data/lib/eco/data/locations/node_level/parsing.rb +15 -21
  152. data/lib/eco/data/locations/node_level.rb +66 -22
  153. data/lib/eco/data/locations/node_plain/parsing.rb +1 -1
  154. data/lib/eco/data/locations/node_plain.rb +60 -7
  155. data/lib/eco/data/strings/camel_case.rb +35 -0
  156. data/lib/eco/data/strings/snake_case.rb +18 -0
  157. data/lib/eco/data/strings.rb +8 -0
  158. data/lib/eco/data.rb +1 -0
  159. data/lib/eco/language/methods/call_detector.rb +11 -0
  160. data/lib/eco/language/methods/dsl_able.rb +7 -1
  161. data/lib/eco/language/methods.rb +2 -1
  162. data/lib/eco/language/models/collection.rb +23 -25
  163. data/lib/eco/language/models/parser_serializer.rb +24 -5
  164. data/lib/eco/version.rb +1 -1
  165. data/lib/eco-helpers.rb +0 -1
  166. metadata +52 -7
  167. data/lib/eco/data/hashes/diff_meta.rb +0 -52
@@ -0,0 +1,99 @@
1
+ class Eco::Data::Locations::NodeDiff::NodesDiff
2
+ # Helper class to wrap into a tree a set of diffs
3
+ class DiffsTree
4
+ class CyclicHierarchy < StandardError
5
+ end
6
+ class CyclicAncestorsChain < CyclicHierarchy
7
+ end
8
+
9
+ MAX_ITERATIONS = 16
10
+
11
+ attr_reader :id
12
+ attr_reader :diff
13
+ attr_reader :children, :parent
14
+
15
+ def initialize(diff = nil, id:)
16
+ @diff = diff
17
+ @id = id
18
+ @children = []
19
+ end
20
+
21
+ # @note added explicit setter so we can warn
22
+ def diff=(value)
23
+ puts "WARNING (DiffsTree#diff=) was already present (for '#{id}')" if diff
24
+ @diff = value
25
+ end
26
+
27
+ def diff?
28
+ !!diff
29
+ end
30
+
31
+ def parent_present?
32
+ !!parent && parent.diff?
33
+ end
34
+
35
+ # @note that the abscense of `parent_id` (i.e. `nil`) does NOT
36
+ # mean that this node does not have an actual parent. This class
37
+ # supports building partial trees (clusters), where some parents
38
+ # may not have presence.
39
+ def parent_id
40
+ return nil unless parent
41
+ parent.id
42
+ end
43
+
44
+ def add_child(child_tree)
45
+ raise ArgumentError, "Expecting #{self.class}. Given: #{child_tree.class}" unless child_tree.is_a?(self.class)
46
+ prevent_cyclic_chain(child_tree)
47
+ children.push(child_tree)
48
+ child_tree.link_parent(self)
49
+ end
50
+
51
+ def ancestors(iteration: 0)
52
+ msg = "Node '#{id}' can't be the #{iteration}th ancestor of any other node."
53
+ raise CyclicAncestorsChain, msg if max_iterations?(iteration)
54
+
55
+ return [] unless parent
56
+ [parent].concat(parent.ancestors(iteration: iteration + 1))
57
+ end
58
+
59
+ def all_descendants(iteration: 0)
60
+ msg = "Node '#{id}' can't be the #{iteration}th offspring of any other node."
61
+ raise CyclicAncestorsChain, msg if max_iterations?(iteration)
62
+
63
+ children.each_with_object([]) do |child, out|
64
+ out.push(child)
65
+ out.concat(child.all_descendants(iteration: iteration + 1))
66
+ end
67
+ end
68
+
69
+ protected
70
+
71
+ def link_parent(parent_node)
72
+ raise ArgumentError, "Expecting #{self.class}. Given: #{parent_node.class}" unless parent_node.is_a?(self.class)
73
+ return (@parent = parent_node) unless parent
74
+ msg = "Node '#{id}' already has a parent (#{parent.id}). "
75
+ msg << "Can't make it child of '#{parent_node.id}' as well."
76
+ raise CyclicHierarchy, msg
77
+ end
78
+
79
+ private
80
+
81
+ def prevent_cyclic_chain(child)
82
+ msg = "Can't make '#{id}' child of itself."
83
+ raise CyclicHierarchy, msg if child == self
84
+
85
+ msg << " A duplicated node got created somehow (review code)"
86
+ raise CyclicHierarchy, msg if child.id == id
87
+
88
+ return true unless ancestors.include?(child)
89
+
90
+ msg = "Can't add '#{child.id}' as children of '#{id}' "
91
+ msg << "because the child is ancestor"
92
+ raise CyclicHierarchy, msg
93
+ end
94
+
95
+ def max_iterations?(value)
96
+ value >= self.class::MAX_ITERATIONS
97
+ end
98
+ end
99
+ end
@@ -1,4 +1,4 @@
1
- class Eco::Data::Locations::NodeDiff
1
+ class Eco::Data::Locations::NodeDiff::NodesDiff
2
2
  module Selectors
3
3
  # Creates selector methods on `diffs` (nodes that changed)
4
4
  # It also creates a method with question mark that evaluates true if **any** diff matches.
@@ -1,10 +1,19 @@
1
1
  class Eco::Data::Locations::NodeDiff
2
2
  # Adjusts ArrayDiff for Nodes diffs in a location structure
3
3
  class NodesDiff < Eco::Data::Hashes::ArrayDiff
4
- extend Eco::Data::Locations::NodeDiff::Selectors
4
+ require_relative 'nodes_diff/selectors'
5
+ require_relative 'nodes_diff/diffs_tree'
6
+ require_relative 'nodes_diff/clustered_treeify'
5
7
 
6
- SELECTORS = %i[id name id_name insert unarchive update move archive]
7
- selector *SELECTORS
8
+ extend Eco::Data::Locations::NodeDiff::NodesDiff::Selectors
9
+
10
+ SELECTORS = %i[
11
+ id name id_name classifications
12
+ insert unarchive
13
+ update move
14
+ archive
15
+ ].freeze
16
+ selector(*SELECTORS)
8
17
 
9
18
  class_resolver :diff_result_class, Eco::Data::Locations::NodeDiff
10
19
  attr_reader :original_tree
@@ -15,78 +24,84 @@ class Eco::Data::Locations::NodeDiff
15
24
  end
16
25
 
17
26
  def diffs
18
- @diffs ||= super.select do |res|
19
- res.unarchive? || res.id_name? || res.insert? || res.move? || res.archive?
27
+ @diffs ||= super.select do |dff|
28
+ # discard entries that are to be inserted and archived at the same time
29
+ next false if dff.insert? && dff.archive?(validate: false)
30
+ dff.unarchive? || dff.id_name? || dff.insert? ||
31
+ dff.move? || dff.archive?
20
32
  end
21
33
  end
22
34
 
23
- def diffs_details
35
+ def diffs_details # rubocop:disable Metrics/AbcSize
24
36
  section = '#' * 10
25
- msg = ''
37
+ msg = ''
38
+
26
39
  if insert?
27
40
  msg << " #{section} I N S E R T S #{section}\n"
28
- msg << insert.map {|d| d.diff_hash.pretty_inspect}.join('')
41
+ msg << insert.map {|dff| dff.diff_hash.pretty_inspect}.join
29
42
  end
30
43
 
31
44
  if update?
32
45
  msg << "\n #{section} U P D A T E S #{section}\n"
33
- update.each do |d|
46
+ update.each do |dff|
34
47
  flags = ''
35
- #flags << 'i' if d.id?
36
- flags << 'n' if d.diff_name?
37
- flags << 'm' if d.move?
38
- flags << 'u' if d.unarchive?
48
+ flags << 'i' if dff.id?
49
+ flags << 'n' if dff.name?
50
+ flags << 'c' if dff.classifications?
51
+ flags << 'm' if dff.move?
52
+ flags << 'u' if dff.unarchive?
39
53
  msg << "<< #{flags} >> "
40
- msg << d.diff_hash.pretty_inspect
54
+ msg << dff.diff_hash.pretty_inspect
41
55
  end
42
56
  end
43
57
 
44
58
  if archive?
45
59
  msg << "\n #{section} A R C H I V E S #{section}\n"
46
- msg << archive.map {|d| d.diff_hash.pretty_inspect}.join('')
60
+ msg << archive.map {|dff| dff.diff_hash.pretty_inspect}.join
47
61
  end
48
62
 
49
63
  msg
50
64
  end
51
65
 
52
- def diffs_summary
53
- comp = "(#{source2.count} vs #{source1.count} nodes)"
66
+ def diffs_summary # rubocop:disable Metrics/AbcSize
67
+ comp = "(#{source_2.count} input nodes VS #{source_1.count} live nodes)"
54
68
  return "There were no differences identified #{comp}" if diffs.empty?
55
69
  msg = []
56
- msg << "Identified #{diffs.count} differences #{comp}:"
57
- msg << when_present(insert, '') do |count|
70
+ msg << "Identified #{diffs.count} differences #{comp}:"
71
+ msg << when_present(insert) do |count|
58
72
  " • #{count} nodes to insert"
59
73
  end
60
- msg << when_present(update, '') do |count|
74
+ msg << when_present(update) do |count|
61
75
  " • #{count} nodes to update"
62
76
  end
63
- # msg << when_present(id, '') do |count|
64
- # " • #{count} nodes to change id\n"
65
- # end
66
- msg << when_present(name, '') do |count|
77
+ msg << when_present(unarchive) do |count|
78
+ " • #{count} nodes to unarchive (includes ancestors of target nodes)"
79
+ end
80
+ msg << when_present(id) do |count|
81
+ " • #{count} nodes to change id\n"
82
+ end
83
+ msg << when_present(name) do |count|
67
84
  " • #{count} nodes to change name"
68
85
  end
69
- msg << when_present(move, '') do |count|
70
- " • #{count} nodes to move"
86
+ msg << when_present(classifications) do |count|
87
+ " • #{count} nodes to change classifications"
71
88
  end
72
- msg << when_present(unarchive, '') do |count|
73
- " • #{count} nodes to unarchive"
89
+ msg << when_present(move) do |count|
90
+ " • #{count} nodes to move"
74
91
  end
75
- msg << when_present(archive, '') do |count|
92
+ msg << when_present(archive) do |count|
76
93
  " • #{count} nodes to archive"
77
94
  end
78
- msg.join("\n")
95
+ msg.compact.join("\n")
79
96
  end
80
97
 
81
98
  private
82
99
 
83
100
  def when_present(list, default = nil)
101
+ raise ArgumentError, "Expecting block but not given" unless block_given?
84
102
  count = list.count
85
- if count > 0
86
- yield(count)
87
- else
88
- default
89
- end
103
+ return yield(count) if count&.positive?
104
+ default
90
105
  end
91
106
  end
92
107
  end
@@ -7,49 +7,77 @@ module Eco::Data::Locations
7
7
  # - `archived`
8
8
  # @note other properties can be part of the `Hash` although
9
9
  # they may not influence the results.
10
+ # @note that for special `exposed` methods question mark `?` on a
11
+ # property of the model checks if that property changed
12
+ # (it has nothing to do with checking the boolean value of that property)
10
13
  class NodeDiff < Eco::Data::Hashes::DiffResult
11
14
  require_relative 'node_diff/accessors'
12
- require_relative 'node_diff/selectors'
13
15
  require_relative 'node_diff/nodes_diff'
14
16
 
15
17
  include Eco::Data::Locations::NodeDiff::Accessors
16
18
 
17
19
  key :nodeId
18
- compare :parentId, :name, :archived
19
- case_sensitive false
20
+ # it also adds the snake_methods
21
+ # i.e. `parent_id`, `parent_id_prev`, `parent_id?` (to check if updated)
22
+ attr_expose :nodeId, :name, :parentId, :archived, :archivedToken, :classifications
20
23
 
21
- attr_expose :nodeId, :name, :parentId, :archived
24
+ compare :parentId, :name, :archived
25
+ compare :classifications #, when_present: true
22
26
 
23
- alias_method :insert?, :new?
27
+ case_sensitive false
24
28
 
25
- alias_method :diff_name_src?, :diff_name?
29
+ # move method, to redefine it
30
+ alias_method :name_src?, :name?
26
31
  # Has the property `name` changed?
27
- def diff_name?
28
- diff_name_src? && update?
32
+ # @note should not be part of an insert
33
+ def name?
34
+ name_src? && update?
29
35
  end
30
- alias_method :name?, :diff_name?
31
- alias_method :id? , :diff_name? # currently a change of name is a change of id (tag)
32
- #alias_method :id? , :key?
33
- #alias_method :nodeId? , :id?
36
+
37
+ alias_method :classifications_src?, :classifications?
38
+ # @note should not be part of an insert
39
+ def classifications?
40
+ classifications_src? && update?
41
+ end
42
+
43
+ alias_method :insert?, :new?
44
+ # node id diff? check should be performed as a `key`
45
+ alias_method :id?, :key?
46
+ alias_method :nodeId?, :id?
47
+ alias_method :node_id?, :nodeId?
34
48
 
35
49
  # Has any of `id` or `name` properties changed?
36
50
  def id_name?
37
- id? || diff_name?
51
+ return true if id?
52
+ return true if name?
53
+ classifications?
38
54
  end
39
55
 
40
56
  # Has the parent id changed?
41
57
  def move?
42
- update? && diff_parentId?
58
+ return false unless update?
59
+ parent_id?
43
60
  end
44
61
 
45
62
  # Has the `archived` property changed and it was `true`?
46
63
  def unarchive?
47
- !archived && update? && diff_archived?
64
+ return false if archived
65
+ return false unless update?
66
+ archived?
48
67
  end
49
68
 
50
69
  # Has the `archived` property changed and it was `false`?
51
- def archive?
52
- !prev_archived && (del? || archived)
70
+ def archive?(validate: true)
71
+ return false if archived_prev
72
+
73
+ msg = "Value of archived shouldn't be true, "
74
+ msg << "because node '#{node_id}' doesn't exist "
75
+ msg << "(it's being inserted). "
76
+ msg << "It should have been discarded as a diff"
77
+ raise msg if validate && archived && insert?
78
+
79
+ return true if del?
80
+ archived
53
81
  end
54
82
  end
55
83
  end
@@ -28,34 +28,28 @@ class Eco::Data::Locations::NodeLevel
28
28
  def nodes_from_csv(csv)
29
29
  raise ArgumentError, "Expecting CSV::Table. Given: #{csv.class}" unless csv.is_a?(::CSV::Table)
30
30
 
31
- prev_level = nil
32
- prev_node = nil
31
+ possible_classifications = csv.headers
33
32
 
34
33
  # Convert to Eco::CSV::Table for a fresh start
35
34
  csv = Eco::CSV.parse(csv.to_csv).nil_blank_cells.add_index_column(:row_num)
36
35
 
37
- first = true
38
- nodes = csv.each_with_object([]) do |row, out|
36
+ prev_node = nil
37
+ nodes = csv.each_with_object([]) do |row, out|
39
38
  row_num, *values = row.fields
40
- node = node_class.new(row_num, *values)
41
- prev_node ||= node
42
39
 
43
- # If node is nested in prev_node or is a sibling thereof
44
- if prev_node.raw_level <= node.raw_level
45
- # Make sure upper level tags are there (including parent)
46
- # This normalizes input to all upper level tags always filled in
47
- # which allows to node#actual_level to work
48
- node.set_high_levels(prev_node)
49
- else
50
- if node.raw_level == 1
51
- # It is expected not to have parent (as it's top level tag)
52
- elsif prev_node
53
- node.set_high_levels(prev_node)
54
- else
55
- raise "Node '#{node.raw_tag}' (#{node.row_num} row) doesn't have parent"
56
- end
40
+ node = node_class.new(row_num, *values).tap do |nd|
41
+ nd.original_headers = possible_classifications
57
42
  end
58
- out << node
43
+
44
+ out << node
45
+ prev_node ||= node
46
+
47
+ # unless top level
48
+ # Make sure upper level tags are there (including parent)
49
+ # This normalizes input to all upper level tags always filled in
50
+ # which allows to node#actual_level to work
51
+ node.set_high_levels(prev_node) unless node.raw_level == 1
52
+
59
53
  prev_node = node
60
54
  end
61
55
  tidy_nodes(nodes)
@@ -1,5 +1,6 @@
1
+ # rubocop:disable Naming/AccessorMethodName
1
2
  module Eco::Data::Locations
2
- NODE_LEVEL_ATTRS = %i[row_num l1 l2 l3 l4 l5 l6 l7 l8 l9 l10 l11]
3
+ NODE_LEVEL_ATTRS = %i[row_num l1 l2 l3 l4 l5 l6 l7 l8 l9 l10 l11].freeze
3
4
  NodeLevelStruct = Struct.new(*NODE_LEVEL_ATTRS)
4
5
  # Class to treat input csv in a form of levels, where each column is one level,
5
6
  # and children are placed in higher columns right below the parent.
@@ -13,28 +14,69 @@ module Eco::Data::Locations
13
14
  extend Eco::Data::Locations::NodeLevel::Builder
14
15
 
15
16
  ALL_ATTRS = NODE_LEVEL_ATTRS
16
- ADDITIONAL_ATTRS = %i[row_num]
17
- TAGS_ATTRS = ALL_ATTRS - ADDITIONAL_ATTRS
17
+ ADDITIONAL_ATTRS = %i[row_num].freeze
18
+ TAGS_ATTRS = (ALL_ATTRS - ADDITIONAL_ATTRS).freeze
19
+
20
+ # @yield [node, json] optional custom serializer
21
+ # @yieldparam node [Node] self
22
+ # @yieldparam json [Hash] the default serialization
23
+ # @yieldreturn [Hash] the serialized Node
24
+ def node_hash(stringify_keys: true)
25
+ json = to_h(:id, :name, :parent_id, :classifications, :classification_names)
26
+ json.transform_keys!(&:to_s) if stringify_keys
27
+ json.merge!(yield(self, json)) if block_given?
28
+ json
29
+ end
30
+
31
+ def copy
32
+ super.tap do |cp|
33
+ cp.highest_levels_set!
34
+ cp.original_headers = original_headers
35
+ end
36
+ end
18
37
 
19
- attr_accessor :parentId
38
+ # In node level the parent id is set via parsing
39
+ attr_accessor :parentId # rubocop:disable Naming/MethodName
40
+ alias_method :parent_id=, :parentId=
41
+ alias_method :parent_id, :parentId
20
42
 
21
43
  def id
22
44
  tag.upcase
23
45
  end
24
- alias_method :nodeId, :id
46
+ alias_method :nodeId, :id
47
+ alias_method :node_id, :nodeId
25
48
 
26
49
  def name
27
50
  tag
28
51
  end
29
52
 
30
53
  def tag
31
- clean_id(raw_tag, ref: "(Row: #{self.row_num}) ")
54
+ clean_id(raw_tag, ref: "(Row: #{row_num}) ")
32
55
  end
33
56
 
34
57
  def raw_tag
35
58
  values_at(*TAGS_ATTRS.reverse).compact.first
36
59
  end
37
60
 
61
+ attr_writer :original_headers
62
+
63
+ def original_headers
64
+ @original_headers ||= []
65
+ end
66
+
67
+ def original_headers?
68
+ original_headers.any?
69
+ end
70
+
71
+ def classifications
72
+ return [] unless (al = actual_level).positive?
73
+ original_headers[al - 1]
74
+ end
75
+
76
+ def classification_names
77
+ classifications.dup
78
+ end
79
+
38
80
  def level
39
81
  actual_level
40
82
  end
@@ -56,7 +98,7 @@ module Eco::Data::Locations
56
98
 
57
99
  def raw_prev_empty_level?
58
100
  lev = raw_prev_empty_level
59
- lev && lev > 0
101
+ lev&.positive?
60
102
  end
61
103
 
62
104
  def raw_latest_consecutive_top_empty_level
@@ -72,10 +114,11 @@ module Eco::Data::Locations
72
114
  otags_array = other.tags_array.compact
73
115
  stags_array = tags_array.compact
74
116
  raise "Missing lower levels for #{other.id}: #{other.tags_array.pretty_inspect}" unless other.highest_levels_set?
75
- raise "Missing lower levels for #{self.id}: #{tags_array.pretty_inspect}" unless highest_levels_set?
117
+ raise "Missing lower levels for #{id}: #{tags_array.pretty_inspect}" unless highest_levels_set?
118
+
76
119
  otags_array.zip(stags_array).each_with_index do |(otag, stag), idx|
77
- next if otag&.upcase&.strip == stag&.upcase&.strip
78
- return nil if idx == 0
120
+ next if otag&.upcase&.strip == stag&.upcase&.strip
121
+ return nil if idx&.zero?
79
122
  return idx # previous idx, which means prev_idx + 1 (so idx itself)
80
123
  end
81
124
  actual_level
@@ -96,18 +139,12 @@ module Eco::Data::Locations
96
139
 
97
140
  def previous_idx
98
141
  idx = tag_idx - 1
99
- idx < 0 ? nil : idx
142
+ idx&.negative?? nil : idx
100
143
  end
101
144
 
102
145
  def empty_idx
103
146
  tary = tags_array
104
- tary.index(nil) || tary.length + 1
105
- end
106
-
107
- def copy
108
- super.tap do |dup|
109
- dup.highest_levels_set!
110
- end
147
+ tary.index(nil) || (tary.length + 1)
111
148
  end
112
149
 
113
150
  # We got a missing level that is compacted in one row
@@ -120,8 +157,11 @@ module Eco::Data::Locations
120
157
  # must be the last among filled_idxs, so let's use it to verify
121
158
  unless with_info.last == tag_idx
122
159
  # This can only happen when there are repeated nodes
123
- raise "Review this (row #{row_num}; '#{raw_tag}'): tag_idx is #{tag_idx}, while last filled idx is #{with_info.last}"
160
+ msg = "Review this (row #{row_num}; '#{raw_tag}'): "
161
+ msg << "tag_idx is #{tag_idx}, while last filled idx is #{with_info.last}"
162
+ raise msg
124
163
  end
164
+
125
165
  len = with_info.length
126
166
  target_idxs = with_info[len-(num+1)..-2]
127
167
  target_idxs.map do |idx|
@@ -142,8 +182,8 @@ module Eco::Data::Locations
142
182
  end
143
183
 
144
184
  # Sets ancestors
145
- def set_high_levels(node, override: false, compact: true)
146
- update_lower_levels(node.tags_array, override: override)
185
+ def set_high_levels(node)
186
+ update_lower_levels(node.tags_array)
147
187
  self
148
188
  end
149
189
 
@@ -159,6 +199,7 @@ module Eco::Data::Locations
159
199
  return false
160
200
  end
161
201
  return false if target.empty?
202
+
162
203
  target.each do |n|
163
204
  #puts "clearing 'l#{n}': #{attr("l#{n}")}"
164
205
  set_attr("l#{n}", nil)
@@ -169,9 +210,10 @@ module Eco::Data::Locations
169
210
  # Ensures parent is among the upper level tags
170
211
  # It actually ensures all ancestors are there
171
212
  # @param override [Boolean] `false` will only override upmost top consecutive empty levels.
172
- def update_lower_levels(src_tags_array, to_level: self.raw_latest_consecutive_top_empty_level, override: false)
213
+ def update_lower_levels(src_tags_array, to_level: raw_latest_consecutive_top_empty_level)
173
214
  highest_levels_set!
174
215
  return self unless to_level
216
+
175
217
  target_lev = Array(1..to_level)
176
218
  target_tags = src_tags_array[level_to_idx(1)..level_to_idx(to_level)]
177
219
  target_lev.zip(target_tags).each do |(n, tag)|
@@ -214,3 +256,5 @@ module Eco::Data::Locations
214
256
  end
215
257
  end
216
258
  end
259
+
260
+ # rubocop:enable Naming/AccessorMethodName
@@ -8,7 +8,7 @@ class Eco::Data::Locations::NodePlain
8
8
 
9
9
  # Subset of property names expected to be among the headers
10
10
  def basic_headers
11
- @expected_headers ||= %w[id name parent_id]
11
+ @basic_headers ||= %w[id name parent_id]
12
12
  end
13
13
 
14
14
  # Checks if the `basic_headers` to process as `NodePlain` are present.
@@ -1,6 +1,13 @@
1
1
  module Eco::Data::Locations
2
- NODE_PLAIN_ATTRS = %i[row_num id name parent_id weight archived archive_token]
3
- NodePlainStruct = Struct.new(*NODE_PLAIN_ATTRS)
2
+ NODE_PLAIN_ATTRS = %i[
3
+ row_num
4
+ id name parent_id
5
+ weight
6
+ archived archive_token
7
+ classifications
8
+ classification_names
9
+ ].freeze
10
+ NodePlainStruct = Struct.new(*NODE_PLAIN_ATTRS)
4
11
  # Class to treat input csv in a form of a list of nodes, where parent is specified.
5
12
  class NodePlain < NodePlainStruct
6
13
  include Eco::Data::Locations::NodeBase
@@ -11,22 +18,68 @@ module Eco::Data::Locations
11
18
  extend Eco::Data::Locations::NodePlain::Builder
12
19
 
13
20
  ALL_ATTRS = NODE_PLAIN_ATTRS
14
- ADDITIONAL_ATTRS = %i[row_num]
15
- PROP_ATTRS = ALL_ATTRS - ADDITIONAL_ATTRS
21
+ ADDITIONAL_ATTRS = %i[row_num].freeze
22
+ PROP_ATTRS = (ALL_ATTRS - ADDITIONAL_ATTRS).freeze
16
23
 
17
24
  def id
18
- clean_id(super, ref: "(Row: #{self.row_num}) ")
25
+ clean_id(super, ref: "(Row: #{row_num}) ")
19
26
  end
20
27
  # backwards compatibility
21
28
  alias_method :tag, :id
22
29
 
23
30
  def name
24
- super || self.id
31
+ super || id
25
32
  end
26
33
 
27
34
  def parent_id
28
- clean_id(super, notify: false, ref: "(Row: #{self.row_num} - parent_id) ")
35
+ clean_id(super, notify: false, ref: "(Row: #{row_num} - parent_id) ")
29
36
  end
30
37
  alias_method :parentId, :parent_id
38
+
39
+ def archived
40
+ value = super
41
+ return false if value.nil? || value == false
42
+ return true if value == true
43
+ return false if value.to_s.strip.empty?
44
+ return true if %w[yes x true].include?(value.downcase)
45
+ false
46
+ end
47
+
48
+ def classifications
49
+ into_a(super).map do |value|
50
+ treat_classication(value)
51
+ end
52
+ end
53
+
54
+ def classification_names
55
+ into_a(super)
56
+ end
57
+
58
+ # @yield [node, json] optional custom serializer
59
+ # @yieldparam node [Node] self
60
+ # @yieldparam json [Hash] the default serialization
61
+ # @yieldreturn [Hash] the serialized Node
62
+ def node_hash(stringify_keys: true)
63
+ json = to_h.reject {|key, _v| key == :row_num}
64
+ json.transform_keys!(&:to_s) if stringify_keys
65
+ json.merge!(yield(self, json)) if block_given?
66
+ json
67
+ end
68
+
69
+ private
70
+
71
+ def treat_classication(value)
72
+ return value unless value.is_a?(String)
73
+ value.strip.gsub(/\W+/, '').downcase
74
+ end
75
+
76
+ # Helper to convert to array
77
+ def into_a(value)
78
+ if value.is_a?(String)
79
+ value.split('|')
80
+ else
81
+ [value].flatten
82
+ end.compact
83
+ end
31
84
  end
32
85
  end