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
@@ -12,7 +12,9 @@ module Eco::Data::Locations::NodeBase
12
12
  raise ArgumentError, "The input csv does not have the required format to read a locations structure."
13
13
  end
14
14
 
15
- # @yield [Node] optional custom serializer
15
+ # @yield [node, json] optional custom serializer
16
+ # @yieldparam node [Node] the node that is being serialized
17
+ # @yieldparam json [Hash] the default serialization
16
18
  # @yieldreturn [Hash] the serialized Node
17
19
  # @return [Array<Hash>] a hierarchical tree of nested Hashes via `nodes` key.
18
20
  def hash_tree_from_csv(csv, &block)
@@ -24,7 +26,7 @@ module Eco::Data::Locations::NodeBase
24
26
  # @param filename [String] the csv file.
25
27
  # @return [Array<NodePlain>, Array<NodeLevel>] with integrity issues resolved.
26
28
  def csv_nodes_from(filename, encoding: 'utf-8')
27
- nodes_from_csv(csv_from(filename, encoding: 'utf-8'))
29
+ nodes_from_csv(csv_from(filename, encoding: encoding))
28
30
  end
29
31
  end
30
32
  end
@@ -7,6 +7,20 @@ module Eco::Data::Locations::NodeBase
7
7
  module Treeify
8
8
  include Eco::Language::AuxiliarLogger
9
9
 
10
+ # @yield [node, json] optional custom serializer
11
+ # @yieldparam node [Node] the node that is being serialized
12
+ # @yieldparam json [Hash] the default serialization
13
+ # @yieldreturn [Hash] the serialized Node
14
+ def serialize_node(node, parent_id: :unused)
15
+ msg = "Expecting Eco::Data::Locations::NodeBase. Given: #{node.class}"
16
+ raise ArgumentError, msg unless node.is_a?(Eco::Data::Locations::NodeBase)
17
+
18
+ node.node_hash.tap do |json|
19
+ json.merge!({"parent_id" => parent_id}) unless parent_id == :unused
20
+ json.merge!(yield(node, json)) if block_given?
21
+ end
22
+ end
23
+
10
24
  # @note if block is no given, it auto-detects the `serializer` **block**.
11
25
  # @yield [NodeBase] for each included node
12
26
  # @yieldreturn [Hash] custom hash model when treeifying (allows to set more keys/properties).
@@ -14,20 +28,20 @@ module Eco::Data::Locations::NodeBase
14
28
  # @return [Array<Hash>] a hierarchical tree of nested Hashes via `nodes` key.
15
29
  def treeify(nodes, skipped: [], unlinked_trees: [], &block)
16
30
  return [] if nodes.empty?
17
- block ||= nodes.first.class.serializer
31
+ block ||= nodes.first.class.serializer
18
32
  done_ids = {}
19
33
  warns = []
20
34
  parents = parents_hash(nodes)
21
- get_children(nil, parents, done_ids: done_ids, skipped: skipped, warns: warns, &block).tap do |tree|
35
+ get_children(
36
+ nil, parents,
37
+ done_ids: done_ids, skipped: skipped,
38
+ warns: warns, &block
39
+ ).tap do |tree|
22
40
  check_results(
23
- tree,
24
- nodes,
25
- parents,
26
- done_ids: done_ids,
27
- skipped: skipped,
28
- unlinked_trees: unlinked_trees,
29
- warns: warns,
30
- &block
41
+ tree, nodes,
42
+ parents, done_ids: done_ids,
43
+ skipped: skipped, unlinked_trees: unlinked_trees,
44
+ warns: warns, &block
31
45
  )
32
46
  log(:warn) { warns.join("\n") } unless warns.empty?
33
47
  end
@@ -49,56 +63,44 @@ module Eco::Data::Locations::NodeBase
49
63
  # 3. The above can translate into some
50
64
  # @yield [node]
51
65
  # @yieldreturn [Hash] custom hash model when treeifying
52
- def get_children(node_id, parents, parent: nil, level: 0, done_ids: {}, skipped: [], warns: [], &block)
66
+ def get_children(
67
+ node_id, parents,
68
+ parent: nil, level: 0,
69
+ done_ids: {}, skipped: [],
70
+ warns: [], &block
71
+ )
53
72
  level_ids = []
54
73
  (parents[node_id] ||= []).each_with_object([]) do |child, results|
55
74
  # Skipping done id. Add proper warnings...
56
75
  # => rely on `done_ids` to identify if an `id` has already been done
57
- next report_skipped_node(
58
- child,
59
- parent,
60
- done_ids,
61
- level,
62
- level_ids,
63
- parents,
64
- skipped: skipped,
65
- warns: warns
66
- ) if done_ids[child.id]
76
+ if done_ids[child.id]
77
+ next report_skipped_node(
78
+ child, parent,
79
+ done_ids, level,
80
+ level_ids, parents,
81
+ skipped: skipped, warns: warns
82
+ )
83
+ end
67
84
 
68
85
  # Fill in tracking data
69
86
  child.parent = parent
70
87
  child.tracked_level = level + 1
71
- level_ids << child.id
72
-
73
- node_hash = {
74
- "id" => child.id,
75
- "name" => child.name,
76
- "parent_id" => node_id
77
- }
78
- node_hash.merge(yield(child)) if block_given?
79
- # we must register the `id` before recursing down
80
- done_ids[child.id] = child
81
-
82
- children = get_children(
83
- child.id,
84
- parents,
85
- parent: child,
86
- done_ids: done_ids,
87
- level: level + 1,
88
- skipped: skipped,
89
- warns: warns,
90
- &block
88
+ level_ids << child.id
89
+ done_ids[child.id] = child
90
+
91
+ node_hash = serialize_node(child, parent_id: node_id, &block)
92
+ results << node_hash
93
+ node_hash["nodes"] = get_children(
94
+ child.id, parents,
95
+ parent: child, done_ids: done_ids,
96
+ level: level + 1, skipped: skipped,
97
+ warns: warns, &block
91
98
  ).tap do |desc|
92
- if (nil_count = desc.count(nil)) > 0
93
- log(:debug) {
94
- "get_children gave #{nil_count} nil values for nodes of #{child.id}"
95
- }
96
- end
99
+ next unless (nil_count = desc.count(nil)).positive?
100
+ log(:debug) {
101
+ "get_children gave #{nil_count} nil values for nodes of #{child.id}"
102
+ }
97
103
  end
98
-
99
- results << node_hash.merge({
100
- "nodes" => children.compact
101
- })
102
104
  end
103
105
  end
104
106
 
@@ -111,7 +113,7 @@ module Eco::Data::Locations::NodeBase
111
113
  end
112
114
 
113
115
  def indent(level)
114
- "#{" " * level}"
116
+ " " * level
115
117
  end
116
118
 
117
119
  # Method to ensure the results are consistent
@@ -120,83 +122,103 @@ module Eco::Data::Locations::NodeBase
120
122
  # because otherwise would be part of `done_ids` anyway.
121
123
  # @param unlinked_trees [Array<Hash>] by excluding those done and skipped,
122
124
  # it will treeify the unlinked nodes (the exclusion applies to `parants_hash`)
123
- def check_results(tree, nodes, parents, done_ids: {}, skipped: [], unlinked_trees: [], warns: [], &block)
125
+ def check_results( # rubocop:disable Metrics/AbcSize
126
+ _tree, nodes, parents,
127
+ done_ids: {}, skipped: [], unlinked_trees: [],
128
+ warns: [], &block
129
+ )
124
130
  update_skipped(skipped, parents, done_ids: done_ids) unless skipped.empty?
131
+ return if done_ids.count == nodes.count
125
132
 
126
- if done_ids.count != nodes.count
127
- tracked_nodes = done_ids.values
128
- untracked_nodes = nodes - tracked_nodes - skipped
129
- # skipped keys is inherent, as they were excluded because of id clash with done_ids
130
- unlinked_parent_ids = (parents.keys - done_ids.keys).compact
133
+ tracked_nodes = done_ids.values
134
+ untracked_nodes = nodes - tracked_nodes - skipped
135
+ # skipped keys is inherent, as they were excluded because of id clash with done_ids
136
+ unlinked_parent_ids = (parents.keys - done_ids.keys).compact
131
137
 
138
+
139
+ # The reason of missing nodes in the output tree is unknown!
140
+ if skipped.empty? && unlinked_parent_ids.empty?
132
141
  msg = []
142
+ msg << "BUG in this library (open issue with maintainers)."
143
+ msg << "There were no skipped nodes nor missin referred parents, and yet:"
144
+ msg << " • the tree nodes count: #{done_ids.count} ..."
145
+ msg << " • doesn't match the original nodes count: #{nodes.count}"
146
+ raise msg.join("\n")
147
+ end
133
148
 
134
- # The reason of missing nodes in the output tree is unknown!
135
- if skipped.empty? && unlinked_parent_ids.empty?
136
- msg << "BUG in this library (open issue with maintainers)."
137
- msg << "There were no skipped nodes nor missin referred parents, and yet:"
138
- msg << " • the tree nodes count: #{done_ids.count} ..."
139
- msg << " • doesn't match the original nodes count: #{nodes.count}"
140
- raise msg.join("\n")
141
- end
149
+ unless unlinked_parent_ids.empty?
150
+ str_skipped = (skipped.count < 15 ? " => #{skipped.map(&:id).join(', ')}" : '')
142
151
 
143
- unless unlinked_parent_ids.empty?
144
- msg << "There are #{unlinked_parent_ids.count} referred parent_id's NOT linked to the root:"
145
- msg << " • total_nodes: #{nodes.count}"
146
- msg << " • tracked_nodes: #{tracked_nodes.count}"
147
- msg << " • untracked_nodes: #{untracked_nodes.count}"
148
- msg << " • unlinked_parents: #{unlinked_parent_ids.count}"
149
- msg << " • skipped (repeated) nodes: #{skipped.count}" unless skipped.empty?
150
-
151
- unlinked_nodes = nodes - skipped
152
- unlinked_parents = parents.slice(*unlinked_parent_ids) # doesn'thave skipped ones
153
-
154
- residual_skipped = []
155
- unlinked_trees.concat \
156
- get_unlinked_trees(
157
- unlinked_nodes,
158
- unlinked_parents,
159
- done_ids: done_ids,
160
- skipped: residual_skipped,
161
- warns: warns,
162
- &block
163
- )
164
-
165
- update_skipped(skipped, parents, with: residual_skipped, done_ids: done_ids) unless residual_skipped.empty?
166
-
167
- tracked_nodes = done_ids.values
168
- untracked_nodes = nodes - tracked_nodes - skipped
169
- unlinked_parent_ids = (parents.keys - done_ids.keys).compact
170
-
171
- msg << "After treeifying via the unlinked_parents:"
172
- msg << " • total_nodes: #{nodes.count}"
173
- msg << " • tracked_nodes: #{tracked_nodes.count}"
174
- msg << " • untracked_nodes: #{untracked_nodes.count}"
175
- msg << " • unlinked_parents: #{unlinked_parent_ids.count}"
176
- msg << " • skipped in this step: #{residual_skipped.count}"
177
- end
152
+ msg = []
153
+ msg << "There are #{unlinked_parent_ids.count} referred parent_id's NOT linked to the root:"
154
+ msg << " • total_nodes: #{nodes.count}"
155
+ msg << " • tracked_nodes: #{tracked_nodes.count}"
156
+ msg << " • untracked_nodes: #{untracked_nodes.count}"
157
+ msg << " • unlinked_parents: #{unlinked_parent_ids.count}"
158
+ msg << " • skipped (repeated) nodes: #{skipped.count}#{str_skipped}" unless skipped.empty?
159
+ warns << msg.join("\n")
160
+
161
+ unlinked_nodes = nodes - skipped
162
+ unlinked_parents = parents.slice(*unlinked_parent_ids) # doesn'thave skipped ones
163
+
164
+ residual_skipped = []
165
+ ulnk_trees = get_unlinked_trees(
166
+ unlinked_nodes, unlinked_parents,
167
+ done_ids: done_ids, skipped: residual_skipped,
168
+ warns: warns, &block
169
+ )
170
+ unlinked_trees.concat(ulnk_trees)
171
+
172
+ update_skipped(skipped, parents, with: residual_skipped, done_ids: done_ids) unless residual_skipped.empty?
173
+
174
+ tracked_nodes = done_ids.values
175
+ untracked_nodes = nodes - tracked_nodes - skipped
176
+ unlinked_parent_ids = (parents.keys - done_ids.keys).compact
177
+
178
+ str_unlinked_parents =
179
+ unlinked_parent_ids.count < 35 ? unlinked_parent_ids.join(', ') : unlinked_parent_ids.count
178
180
 
179
- msg << " • total skipped (repeated) nodes: #{skipped.count} !!" unless skipped.empty?
181
+ str_skipped = (residual_skipped.count < 15 ? " => #{residual_skipped.map(&:id).join(', ')}" : '')
182
+
183
+ msg = []
184
+ msg << "After treeifying via the unlinked_parents:"
185
+ msg << " • total_nodes: #{nodes.count}"
186
+ msg << " • tracked_nodes: #{tracked_nodes.count}"
187
+ msg << " • untracked_nodes: #{untracked_nodes.count}"
188
+ msg << " • missing_parents: #{str_unlinked_parents}"
189
+ msg << " • skipped in this step: #{residual_skipped.count}#{str_skipped}" unless residual_skipped.empty?
180
190
  warns << msg.join("\n")
181
- nil
182
191
  end
192
+
193
+ str_skipped = (skipped.count < 15 ? " => #{skipped.map(&:id).join(', ')}" : '')
194
+ warns << " • total skipped (repeated) nodes: #{skipped.count}!!#{str_skipped}" unless skipped.empty?
195
+ nil
183
196
  end
184
197
 
185
198
  # Treeifies the unlinked nodes by scoping existing parent ids.
186
- def get_unlinked_trees(nodes, parents, done_ids: {}, skipped: [], warns: [], &block)
199
+ def get_unlinked_trees(
200
+ nodes, parents,
201
+ done_ids: {}, skipped: [],
202
+ warns: [], &block
203
+ )
187
204
  node_ids = nodes.map(&:id)
188
205
  parent_ids = parents.keys & node_ids
189
206
  missing_parent_ids = parents.keys - parent_ids
190
207
  missing_parents = parents.slice(*missing_parent_ids)
191
- warns << " • missing_parents: #{missing_parents.count}"
192
- nil_parent_nodes = missing_parents.each_with_object([]) do |(id, nodes), mem|
193
- nodes.each {|node| node.parent_id = nil}
194
- mem.concat(nodes)
208
+ warns << " • missing_parents: #{missing_parents.count}"
209
+
210
+ nil_parent_nodes = missing_parents.each_with_object([]) do |(_id, nds), mem|
211
+ nds.each {|node| node.parent_id = nil}
212
+ mem.concat(nds)
195
213
  end
196
214
  rest_parents = parents.slice(*parent_ids).merge({
197
215
  nil => nil_parent_nodes
198
216
  })
199
- get_children(nil, rest_parents, done_ids: done_ids, skipped: skipped, warns: warns, &block)
217
+ get_children(
218
+ nil, rest_parents,
219
+ done_ids: done_ids, skipped: skipped,
220
+ warns: warns, &block
221
+ )
200
222
  end
201
223
 
202
224
  # Same as `get_children` but not performing checks and with
@@ -205,28 +227,18 @@ module Eco::Data::Locations::NodeBase
205
227
  # skipped, because their parent was skipped.
206
228
  def get_tree_nodes_raw(node_id, parents, src_plain: true, &block)
207
229
  (parents[node_id] ||= []).each_with_object([]) do |child, results|
208
- unless src_plain
209
- node_hash = {
210
- "id" => child.id,
211
- "name" => child.name,
212
- "parent_id" => node_id
213
- }
214
- node_hash.merge(yield(child)) if block_given?
215
- end
216
-
230
+ node_hash = serialize_node(child, parent_id: node_id, &block) unless src_plain
217
231
  descendants = get_tree_nodes_raw(child.id, parents, src_plain: src_plain, &block).tap do |desc|
218
- if (nil_count = desc.count(nil)) > 0
219
- puts "get_tree_nodes_raw gave #{nil_count} nil values for nodes of #{child.id}"
220
- end
232
+ next unless (nil_count = desc.count(nil)).positive?
233
+ log(:debug) {
234
+ "get_tree_nodes_raw gave #{nil_count} nil values for nodes of #{child.id}"
235
+ }
221
236
  end
237
+ next results.concat(descendants) if src_plain
222
238
 
223
- if src_plain
224
- results.concat(descendants)
225
- else
226
- results << node_hash.merge({
227
- "nodes" => descendants.compact
228
- })
229
- end
239
+ results << node_hash.merge({
240
+ "nodes" => descendants.compact
241
+ })
230
242
  end
231
243
  end
232
244
 
@@ -244,22 +256,27 @@ module Eco::Data::Locations::NodeBase
244
256
 
245
257
  # With given a skipped `node` (repeated `id`), it gives different warnings,
246
258
  # provided that the context in which the double-up `id` happened is identified.
247
- def report_skipped_node(node, parent, done_ids, level, level_ids, parents, skipped: [], warns: [])
248
- skipped << node
259
+ def report_skipped_node( # rubocop:disable Metrics/AbcSize
260
+ node, parent,
261
+ done_ids, level,
262
+ level_ids, parents,
263
+ skipped: [], warns: []
264
+ )
265
+ skipped << node
249
266
  lev = level + 1
250
267
  done_node = done_ids[node.id]
251
268
  prev_parent = node.parent
252
269
  prev_level = node.tracked_level
253
270
  node_dup = done_node && (done_node != node)
254
271
  lev_dup = level_ids.include?(node.id)
255
- multi_parent = (!prev_parent == !!parent) || (prev_parent && (prev_parent.id != parent.id))
272
+ multi_parent = (!prev_parent == !!parent) || (prev_parent && (prev_parent.id != parent.id)) # rubocop:disable Style/DoubleNegation
256
273
 
257
274
  row_num = node.respond_to?(:row_num) ? node.row_num : nil
258
275
  row_str = row_num ? "(Row: #{row_num}) " : ''
259
276
  node_str = "#{row_str}Node '#{node.id}' #{level_msg(lev)} (#{parent_msg(parent)})"
260
277
 
261
- msg = []
262
- msg << "#{indent(level)}Skipping #{node_str}."
278
+ msg = []
279
+ msg << "#{indent(level)}Skipping #{node_str}."
263
280
 
264
281
  # Implementation integrity guard
265
282
  # => as we don't register in `done_ids` those that are skipped,
@@ -10,16 +10,16 @@ module Eco::Data::Locations
10
10
  require_relative 'node_base/builder'
11
11
  extend Eco::Data::Locations::NodeBase::Builder
12
12
 
13
- ALL_ATTRS = []
13
+ ALL_ATTRS = [].freeze
14
14
 
15
15
  attr_accessor :tracked_level, :parent
16
16
 
17
17
  def copy
18
- self.class.new.set_attrs(**self.to_h)
18
+ self.class.new.set_attrs(**to_h)
19
19
  end
20
20
 
21
21
  def attr(sym)
22
- self.send(sym.to_sym)
22
+ send(sym.to_sym)
23
23
  end
24
24
 
25
25
  def attr?(sym)
@@ -32,13 +32,24 @@ module Eco::Data::Locations
32
32
  end
33
33
 
34
34
  def set_attr(attr, value)
35
- self.send("#{attr}=", value)
35
+ send("#{attr}=", value)
36
36
  end
37
37
 
38
38
  def values_at(*attrs)
39
39
  attrs.map {|a| attr(a)}
40
40
  end
41
41
 
42
+ # @yield [node, json] optional custom serializer
43
+ # @yieldparam node [Node] self
44
+ # @yieldparam json [Hash] the default serialization
45
+ # @yieldreturn [Hash] the serialized Node
46
+ def node_hash(stringify_keys: true)
47
+ json = to_h(:id, :name, :parent_id)
48
+ json.transform_keys!(&:to_s) if stringify_keys
49
+ json.merge!(yield(self, json)) if block_given?
50
+ json
51
+ end
52
+
42
53
  def to_h(*attrs)
43
54
  attrs = self.class::ALL_ATTRS if attrs.empty?
44
55
  attrs.zip(values_at(*attrs)).to_h
@@ -10,30 +10,38 @@ class Eco::Data::Locations::NodeDiff
10
10
  end
11
11
 
12
12
  module ClassMethods
13
+ include Eco::Data::Strings::SnakeCase
14
+
13
15
  # Creates the defined accessor attributes against `NodeDiff`
14
16
  # It also creates a method with question mark that evaluates true if **any** the attr changed.
15
17
  # @note the defined attributes are expected to be the keys within
16
18
  # the source Hashes that are being compared.
17
- # @note accessing `src1` (prev) attributes, will have it's method as `prev_[attrName]`
19
+ # @note accessing `src_1` (prev) attributes, will have it's method as `prev_[attrName]`
18
20
  def attr_expose(*attrs)
19
21
  attrs.each do |attr|
20
22
  meth = attr.to_sym
21
- methp = "prev_#{meth}".to_sym
22
- methq = "diff_#{meth}?".to_sym
23
+ methp = "#{meth}_prev".to_sym
24
+ methq = "#{meth}?".to_sym
23
25
 
26
+ # current value
24
27
  define_method meth do
25
28
  attr(meth)
26
29
  end
30
+ alias_method snake_case(meth), meth
27
31
 
32
+ # prev value
28
33
  define_method methp do
29
34
  attr_prev(meth)
30
35
  end
36
+ alias_method snake_case(methp), methp
31
37
 
32
- exposed_attrs |= [meth]
33
-
38
+ # has it changed?
34
39
  define_method methq do
35
40
  diff_attr?(meth)
36
41
  end
42
+ alias_method snake_case(methq), methq
43
+
44
+ @exposed_attrs = (@exposed_attrs || []) | [meth]
37
45
  end
38
46
  end
39
47
 
@@ -0,0 +1,101 @@
1
+ class Eco::Data::Locations::NodeDiff::NodesDiff
2
+ # treeify helper... with diffs you cannot expect all nodes
3
+ # to be there (as not all generated a difference)
4
+ # This class helps to cluster nodes that are related
5
+ # (then each cluster has as a top only one diff)
6
+ class ClusteredTreeify
7
+ class DataIntegrityError < StandardError
8
+ end
9
+
10
+ class << self
11
+ def mode(value)
12
+ case value
13
+ when :destination, :curr
14
+ :curr
15
+ when :source, :prev
16
+ :prev
17
+ else
18
+ raise ArgumentError, "Unknown mode ':#{value}'"
19
+ end
20
+ end
21
+ end
22
+
23
+ attr_reader :diffs, :mode
24
+
25
+ # @param on [Symbol] if it treeifies based on :prev (`:source`)
26
+ # or `:curr` (`:destination`)
27
+ # - default to `:prev` because it's mostly use to archive
28
+ def initialize(diffs, mode: :prev)
29
+ @diffs = diffs
30
+ @mode = self.class.mode(mode)
31
+ end
32
+
33
+ # @return [Hash] where _key_ is the parent `diff` and _value_ are all
34
+ # descendants thereof
35
+ def clusters
36
+ @clusters = only_present_parents_hash.reject do |_pid, tree|
37
+ all_children_diffs_hash.key?(tree.id)
38
+ end.each_with_object({}) do |(_pid, tree), clus|
39
+ clus[tree.diff] = tree.all_descendants.map(&:diff).uniq
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def all_children_diffs_hash
46
+ @all_children_diffs_hash ||= all_children_diffs.each_with_object({}) do |node, out|
47
+ out[node.id] = node.diff
48
+ end
49
+ end
50
+
51
+ def all_children_diffs
52
+ @all_children_diffs ||= only_present_parents_hash.values.map(&:all_descendants).flatten.uniq
53
+ end
54
+
55
+ # wipes out tree nodes that got there just as a token (diff not actually present)
56
+ def only_present_parents_hash
57
+ @only_present_parents_hash ||= parents_hash.select do |_pid, tree|
58
+ tree.diff?
59
+ end
60
+ end
61
+
62
+ # builds up the hierarchy
63
+ def parents_hash
64
+ @parents_hash ||= diffs.each_with_object({}) do |diff, out|
65
+ id = node_id(diff)
66
+ pid = parent_id(diff)
67
+
68
+ if id == pid
69
+ %i[unarchive? id? id_name? insert? move? archive?].each do |stage|
70
+ puts "Stage #{stage}: #{diff.send(stage)}"
71
+ end
72
+ msg = "Node '#{id}' seems to be parent of itself"
73
+ raise DataIntegrityError, msg
74
+ end
75
+
76
+ out[pid] ||= DiffsTree.new(id: pid)
77
+ out[id] ||= DiffsTree.new(id: id)
78
+ out[pid].add_child(out[id])
79
+ out[id].diff = diff
80
+ end
81
+ end
82
+
83
+ def parent_id(diff)
84
+ case mode
85
+ when :prev
86
+ diff.parent_id_prev
87
+ else
88
+ diff.parent_id
89
+ end&.upcase
90
+ end
91
+
92
+ def node_id(diff)
93
+ case mode
94
+ when :prev
95
+ diff.node_id_prev
96
+ else
97
+ diff.node_id
98
+ end&.upcase
99
+ end
100
+ end
101
+ end