eco-helpers 2.6.3 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (172) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +95 -0
  4. data/CHANGELOG.md +135 -2
  5. data/Rakefile +13 -7
  6. data/eco-helpers.gemspec +3 -3
  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/ecoportal_api/external_person.rb +10 -6
  40. data/lib/eco/api/common/version_patches/exception.rb +11 -13
  41. data/lib/eco/api/error.rb +32 -20
  42. data/lib/eco/api/microcases/set_supervisor.rb +0 -3
  43. data/lib/eco/api/organization/node_classifications.rb +82 -0
  44. data/lib/eco/api/organization/policy_groups.rb +4 -6
  45. data/lib/eco/api/organization/tag_tree.rb +169 -93
  46. data/lib/eco/api/organization.rb +1 -0
  47. data/lib/eco/api/session/batch/job.rb +1 -1
  48. data/lib/eco/api/session/config/tagtree.rb +41 -23
  49. data/lib/eco/api/session/config/workflow.rb +113 -88
  50. data/lib/eco/api/session/config.rb +6 -0
  51. data/lib/eco/api/session.rb +51 -29
  52. data/lib/eco/api/usecases/base_io.rb +28 -25
  53. data/lib/eco/api/usecases/default/locations/cli/tagtree_extract_cli.rb +7 -2
  54. data/lib/eco/api/usecases/default/locations/cli/tagtree_upload_cli.rb +21 -0
  55. data/lib/eco/api/usecases/default/locations/csv_to_tree_case.rb +3 -3
  56. data/lib/eco/api/usecases/default/locations/tagtree_extract_case.rb +54 -23
  57. data/lib/eco/api/usecases/default/locations/tagtree_upload_case.rb +87 -0
  58. data/lib/eco/api/usecases/default/locations.rb +1 -0
  59. data/lib/eco/api/usecases/default/people/analyse_people_case.rb +60 -56
  60. data/lib/eco/api/usecases/default/people/change_email_case.rb +8 -9
  61. data/lib/eco/api/usecases/default/people/clean_unknown_tags_case.rb +13 -11
  62. data/lib/eco/api/usecases/default/people/clear_abilities_case.rb +2 -2
  63. data/lib/eco/api/usecases/default/people/org_data_convert_case.rb +25 -27
  64. data/lib/eco/api/usecases/default/people/refresh_case.rb +2 -2
  65. data/lib/eco/api/usecases/default/people/reinvite_trans_case.rb +1 -1
  66. data/lib/eco/api/usecases/default/people/reinvite_trans_cli.rb +0 -1
  67. data/lib/eco/api/usecases/default/people/restore_db_case.rb +39 -34
  68. data/lib/eco/api/usecases/default/people/set_default_tag_case.rb +19 -15
  69. data/lib/eco/api/usecases/default/people/supers_cyclic_identify_case.rb +16 -12
  70. data/lib/eco/api/usecases/default_cases/hris_case.rb +17 -15
  71. data/lib/eco/api/usecases/default_cases/samples/sftp_case.rb +30 -16
  72. data/lib/eco/api/usecases/default_cases/to_csv_detailed_case.rb +0 -2
  73. data/lib/eco/api/usecases/graphql/base.rb +5 -3
  74. data/lib/eco/api/usecases/graphql/helpers/base/case_env.rb +4 -1
  75. data/lib/eco/api/usecases/graphql/helpers/base/graphql_env.rb +14 -0
  76. data/lib/eco/api/usecases/graphql/helpers/base.rb +5 -4
  77. data/lib/eco/api/usecases/graphql/helpers/location/base/classifications_parser.rb +60 -0
  78. data/lib/eco/api/usecases/graphql/helpers/location/base/tree_tracking.rb +72 -0
  79. data/lib/eco/api/usecases/graphql/helpers/location/base.rb +25 -59
  80. data/lib/eco/api/usecases/graphql/helpers/location/command/diff/as_update.rb +59 -0
  81. data/lib/eco/api/usecases/graphql/helpers/location/command/diff/compare.rb +49 -0
  82. data/lib/eco/api/usecases/graphql/helpers/location/command/diff.rb +11 -0
  83. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/commandable.rb +46 -0
  84. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_archive.rb +23 -0
  85. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable/for_unarchive.rb +65 -0
  86. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/diff_sortable.rb +49 -0
  87. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable/relation_safe_sort.rb +119 -0
  88. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/sortable.rb +59 -0
  89. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages.rb +82 -0
  90. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs.rb +20 -0
  91. data/lib/eco/api/usecases/graphql/helpers/location/command/optimizations.rb +84 -0
  92. data/lib/eco/api/usecases/graphql/helpers/location/command/result.rb +4 -4
  93. data/lib/eco/api/usecases/graphql/helpers/location/command/results.rb +24 -12
  94. data/lib/eco/api/usecases/graphql/helpers/location/command.rb +21 -24
  95. data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_map.rb +1 -1
  96. data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_set.rb +10 -11
  97. data/lib/eco/api/usecases/graphql/helpers/location/tags_remap.rb +8 -9
  98. data/lib/eco/api/usecases/graphql/samples/location/command/dsl.rb +41 -12
  99. data/lib/eco/api/usecases/graphql/samples/location/command/results.rb +11 -80
  100. data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +89 -0
  101. data/lib/eco/api/usecases/graphql/samples/location/command/service.rb +6 -0
  102. data/lib/eco/api/usecases/graphql/samples/location/command/track_changed_ids.rb +89 -0
  103. data/lib/eco/api/usecases/graphql/samples/location/command.rb +3 -0
  104. data/lib/eco/api/usecases/graphql/samples/location/service/base.rb +9 -0
  105. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/heading.rb +18 -0
  106. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/inputable.rb +53 -0
  107. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing/classifications.rb +34 -0
  108. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing/helpers.rb +28 -0
  109. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing.rb +46 -0
  110. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible.rb +38 -0
  111. data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff.rb +105 -0
  112. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/discarded.rb +16 -0
  113. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/input.rb +15 -0
  114. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/node_attr_maps.rb +22 -0
  115. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter/parser.rb +45 -0
  116. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/converter.rb +36 -0
  117. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list/output.rb +56 -0
  118. data/lib/eco/api/usecases/graphql/samples/location/service/tree_to_list.rb +41 -0
  119. data/lib/eco/api/usecases/graphql/samples/location/service.rb +8 -0
  120. data/lib/eco/api/usecases/graphql/samples/location.rb +1 -0
  121. data/lib/eco/api/usecases/graphql/utils/sftp.rb +96 -36
  122. data/lib/eco/api/usecases/ooze_cases/export_register_case.rb +8 -6
  123. data/lib/eco/api/usecases/ooze_samples/helpers/creatable.rb +4 -3
  124. data/lib/eco/api/usecases/ooze_samples/helpers/exportable_ooze.rb +39 -25
  125. data/lib/eco/api/usecases/ooze_samples/helpers/exportable_register.rb +13 -15
  126. data/lib/eco/api/usecases/ooze_samples/helpers/filters.rb +50 -21
  127. data/lib/eco/api/usecases/ooze_samples/helpers/ooze_handlers.rb +21 -11
  128. data/lib/eco/api/usecases/ooze_samples/helpers/rescuable.rb +2 -0
  129. data/lib/eco/api/usecases/ooze_samples/helpers/shortcuts.rb +49 -43
  130. data/lib/eco/api/usecases/ooze_samples/ooze_base_case.rb +17 -19
  131. data/lib/eco/api/usecases/ooze_samples/register_export_case.rb +48 -43
  132. data/lib/eco/api/usecases/ooze_samples/register_update_case.rb +34 -34
  133. data/lib/eco/api/usecases/ooze_samples/target_oozes_update_case.rb +8 -10
  134. data/lib/eco/api/usecases.rb +0 -1
  135. data/lib/eco/cli/config/use_cases.rb +31 -29
  136. data/lib/eco/cli_default/input_filters.rb +0 -5
  137. data/lib/eco/cli_default/people_filters.rb +4 -4
  138. data/lib/eco/cli_default/workflow.rb +13 -14
  139. data/lib/eco/csv/table.rb +34 -25
  140. data/lib/eco/data/hashes/array_diff.rb +24 -35
  141. data/lib/eco/data/hashes/diff_result/meta.rb +131 -0
  142. data/lib/eco/data/hashes/diff_result.rb +65 -57
  143. data/lib/eco/data/hashes/sanke_camel_indifferent_access.rb +278 -0
  144. data/lib/eco/data/hashes.rb +1 -1
  145. data/lib/eco/data/locations/convert.rb +1 -1
  146. data/lib/eco/data/locations/node_base/csv_convert.rb +19 -9
  147. data/lib/eco/data/locations/node_base/parsing.rb +4 -2
  148. data/lib/eco/data/locations/node_base/treeify.rb +149 -132
  149. data/lib/eco/data/locations/node_base.rb +15 -4
  150. data/lib/eco/data/locations/node_diff/accessors.rb +13 -5
  151. data/lib/eco/data/locations/node_diff/nodes_diff/clustered_treeify.rb +101 -0
  152. data/lib/eco/data/locations/node_diff/nodes_diff/diffs_tree.rb +99 -0
  153. data/lib/eco/data/locations/node_diff/{selectors.rb → nodes_diff/selectors.rb} +1 -1
  154. data/lib/eco/data/locations/node_diff/nodes_diff.rb +50 -35
  155. data/lib/eco/data/locations/node_diff.rb +45 -17
  156. data/lib/eco/data/locations/node_level/parsing.rb +15 -21
  157. data/lib/eco/data/locations/node_level.rb +66 -22
  158. data/lib/eco/data/locations/node_plain/parsing.rb +1 -1
  159. data/lib/eco/data/locations/node_plain.rb +60 -7
  160. data/lib/eco/data/strings/camel_case.rb +35 -0
  161. data/lib/eco/data/strings/snake_case.rb +18 -0
  162. data/lib/eco/data/strings.rb +8 -0
  163. data/lib/eco/data.rb +1 -0
  164. data/lib/eco/language/methods/call_detector.rb +11 -0
  165. data/lib/eco/language/methods/dsl_able.rb +7 -1
  166. data/lib/eco/language/methods.rb +2 -1
  167. data/lib/eco/language/models/collection.rb +23 -25
  168. data/lib/eco/language/models/parser_serializer.rb +24 -5
  169. data/lib/eco/version.rb +1 -1
  170. data/lib/eco-helpers.rb +0 -1
  171. metadata +54 -9
  172. data/lib/eco/data/hashes/diff_meta.rb +0 -52
@@ -0,0 +1,72 @@
1
+ module Eco::API::UseCases::GraphQL::Helpers::Location
2
+ module Base
3
+ module TreeTracking
4
+ include Eco::API::UseCases::GraphQL::Helpers::Base::CaseEnv
5
+
6
+ TAGTREE_BACKUP = 'cache/tagtree.json'.freeze
7
+
8
+ attr_reader :current_tree
9
+ attr_accessor :previous_tree
10
+
11
+ # Allows to override if the @current_tree should be kept
12
+ # to the latest live version
13
+ def track_current_tree?
14
+ true
15
+ end
16
+
17
+ # Back-up the tree everytime that it is retrieved anew.
18
+ # @note that what is costly operational is to parse the tree. As it comes
19
+ # already parsed, we don't lose performance in backing it up.
20
+ def current_tree=(value)
21
+ return if current_tree == value
22
+ return unless value.is_a?(Eco::API::Organization::TagTree)
23
+
24
+ @previous_tree = @current_tree
25
+ @current_tree = value
26
+ backup_tree(current_tree)
27
+ end
28
+
29
+ # At any moment we want to know how the live tree is
30
+ # @note it also does a backup
31
+ # @return [Eco::API::Organization::TagTree] the latest tree (`current_tree`)
32
+ def track_current_tree(tree)
33
+ return if simulate?
34
+ return if tree.nil?
35
+
36
+ latest_tree = tree if tree.is_a?(Eco::API::Organization::TagTree)
37
+ if tree.respond_to?(:treeify) && track_current_tree?
38
+ latest_tree ||= Eco::API::Organization::TagTree.new(tree.treeify, id: tree.id, name: tree.name)
39
+ end
40
+
41
+ latest_tree.tap do
42
+ next unless latest_tree.is_a?(Eco::API::Organization::TagTree)
43
+ next if latest_tree.empty?
44
+
45
+ # a backup happens:
46
+ self.current_tree = latest_tree
47
+ end
48
+ end
49
+
50
+ # @param tree [Eco::API::Organization::TagTree, Hash, Array]
51
+ # @return [Boolean] whether or not the backup was created
52
+ def backup_tree(tree = current_tree || live_tree)
53
+ return false if simulate?
54
+ case tree
55
+ when Eco::API::Organization::TagTree
56
+ tree = tree.source
57
+ when Hash, Array
58
+ # that's alright
59
+ else
60
+ log(:error) {
61
+ "Can't back up tagtree. Expecting TagTree, Hash or Array. Given: #{tree.class}"
62
+ }
63
+ return false
64
+ end
65
+
66
+ file = session.file_manager.save_json(tree, self.class::TAGTREE_BACKUP, :timestamp)
67
+ log(:debug) { "Backed-up tagtree saved locally to #{file}." }
68
+ true
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,75 +1,28 @@
1
+ require_relative 'base/tree_tracking'
2
+ require_relative 'base/classifications_parser'
1
3
  module Eco::API::UseCases::GraphQL::Helpers::Location
2
4
  module Base
3
5
  include Eco::API::UseCases::GraphQL::Helpers::Base
4
-
5
- TAGTREE_BACKUP = 'cache/tagtree.json'.freeze
6
-
7
- attr_reader :current_tree
8
- attr_accessor :previous_tree
9
-
10
- # Back-up the tree everytime that it is retrieved anew.
11
- def current_tree=(value)
12
- return current_tree if current_tree == value
13
- @current_tree = value
14
- backup_tree(current_tree)
15
- value
16
- end
17
-
18
- # At any moment we want to know how the live tree is
19
- # @note it also does a backup
20
- # @return [Eco::API::Organization::TagTree] the latest tree (`current_tree`)
21
- def track_current_tree(tree)
22
- return if simulate?
23
- return unless tree
24
- latest_tree = tree if tree.is_a?(Eco::API::Organization::TagTree)
25
- if tree.respond_to?(:treeify)
26
- latest_tree ||= Eco::API::Organization::TagTree.new(tree.treeify, id: tree.id, name: tree.name)
27
- end
28
- latest_tree.tap do |_tree|
29
- next unless latest_tree
30
- @previous_tree = @current_tree
31
- self.current_tree = latest_tree
32
- end
33
- end
34
-
35
- # @param tree [Eco::API::Organization::TagTree, Hash, Array]
36
- # @return [Boolean] whether or not the backup was created
37
- def backup_tree(tree = current_tree || live_tree)
38
- return false if simulate?
39
- case tree
40
- when Eco::API::Organization::TagTree
41
- tagtree = tree.source
42
- when Hash, Array
43
- # that's allright
44
- else
45
- log(:error) {
46
- "Can't back up tagtree. Expecting TagTree, Hash or Array. Given: #{tree.class}"
47
- }
48
- return false
49
- end
50
- file = session.file_manager.save_json(tree, self.class::TAGTREE_BACKUP, :timestamp)
51
- logger.debug("Backed up tagtree saved locally to #{file}.")
52
- true
53
- end
54
-
55
- def tagtree_id
56
- %i[target_structure_id tagtree_id structure_id].find {|key| options.dig(:source, key)}
57
- end
6
+ include TreeTracking
7
+ include ClassificationsParser
58
8
 
59
9
  # Scopes the target structure `id`.
60
10
  # @note it is basic that the `id` is correctly identified.
61
11
  def target_structure_id
62
12
  @target_structure_id ||= tagtree_id
63
- @target_structure_id ||= self.class.const_get(:TARGET_STRUCTURE_ID) if self.class.const_defined?(:TARGET_STRUCTURE_ID)
64
- @target_structure_id ||= current_tree.id if current_tree.respond_to?(:id)
13
+ @target_structure_id ||= target_structure_id_const
14
+ @target_structure_id ||= current_tree.id if current_tree.respond_to?(:id)
65
15
  return @target_structure_id if @target_structure_id
16
+
66
17
  msg = "Const TARGET_STRUCTURE_ID has not been defined, "
67
18
  msg << "nor options(:source, :structure_id). "
68
19
  msg << "Infering active locations structure."
69
20
  log(:warn) { msg }
70
- if self.current_tree = session_live_tree
71
- @target_structure_id = current_tree.id
72
- end
21
+
22
+ # a backup happens:
23
+ return nil unless (self.current_tree = session_live_tree)
24
+
25
+ @target_structure_id = current_tree.id
73
26
  end
74
27
 
75
28
  # Retrieves the live tree only if `current_tree` hasn't been just retrieved.
@@ -80,6 +33,8 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
80
33
  tree_init = current_tree
81
34
  target_id = target_structure_id
82
35
  return current_tree if current_tree != tree_init
36
+
37
+ # a backup happens:
83
38
  self.current_tree = session_live_tree(id: target_id)
84
39
  end
85
40
 
@@ -88,5 +43,16 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
88
43
  def session_live_tree(id: nil)
89
44
  session.live_tree(id: id, include_archived: true)
90
45
  end
46
+
47
+ def tagtree_id
48
+ %i[target_structure_id tagtree_id structure_id].map do |key|
49
+ options.dig(:source, key)
50
+ end.compact.first
51
+ end
52
+
53
+ def target_structure_id_const
54
+ return nil unless self.class.const_defined?(:TARGET_STRUCTURE_ID)
55
+ self.class.const_get(:TARGET_STRUCTURE_ID)
56
+ end
91
57
  end
92
58
  end
@@ -0,0 +1,59 @@
1
+ class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diff
2
+ module AsUpdate
3
+ # Unique access point to generate an update
4
+ def as_update(operation, after_id_update: false)
5
+ update_hash = diff_hash(after_id_update: after_id_update)
6
+ update_hash.slice(*update_attrs(operation))
7
+ end
8
+
9
+ # Adjustments to be able to generate correct updates
10
+ # @note it transforms the method on a very effective helper
11
+ def diff_hash(after_id_update: true) # rubocop:disable Metrics/AbcSize
12
+ super().tap do |h|
13
+ h.delete('archived')
14
+ h['classificationIds'] = h.delete('classifications') if h.key?('classifications')
15
+
16
+ if archive? || insert?
17
+ # We assume archives do not have `move` nor update `id` or `name`
18
+ h['nodeId'] = node_id || node_id_prev
19
+ h['parentId'] = parent_id if insert?
20
+ elsif update?
21
+ if id? && !after_id_update
22
+ h['nodeId'] = node_id_prev
23
+ h['newId'] = node_id
24
+ elsif id? # after id update
25
+ h['nodeId'] = node_id
26
+ else # no id? change , and before the id_update stage
27
+ h['nodeId'] = node_id_prev
28
+ end
29
+ if move?
30
+ h['parentId'] = after_id_update ? parent_id : parent_id_prev
31
+ end
32
+ else
33
+ # Something is wrong (not expected to be any other category)
34
+ raise "Unexpected condition, please review code base"
35
+ end
36
+ end
37
+ end
38
+
39
+ # List of attributes that can be part of an operation
40
+ def update_attrs(operation)
41
+ ['nodeId'].tap do |attrs|
42
+ plus =
43
+ case operation
44
+ when :id_name, :update
45
+ %w[newId name classificationIds]
46
+ when :id
47
+ %w[newId]
48
+ when :insert
49
+ %w[name parentId classificationIds]
50
+ when :move
51
+ %w[parentId]
52
+ else
53
+ []
54
+ end
55
+ attrs.push(*plus)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,49 @@
1
+ class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diff
2
+ module Compare
3
+ # Sorts from top to bottom (parents go first)
4
+ # @note
5
+ # 1. The analysis below will assume that NO `nodeId` has changed. Which is what
6
+ # this class helper tries to somehow ensure/emmulate.
7
+ # 2. The below has been thought for NodesDiff::STAGES order:
8
+ # [:unarchive, :id_name, :insert, :move, :archive]
9
+ # @note
10
+ # - If there was a change of id involved in child or parent, the method used
11
+ # on each (attr vs prev_attr) could be different. We would need to know at
12
+ # what stage we are in the general update.
13
+ # - If MOVE has yet to come, `attr_prev` is alright, as we are interested
14
+ # in the current parent-to-child order in the locations structure.
15
+ # UNARCHIVE nodes have `attr_prev` and, therefore, this can be used to sort.
16
+ # - If MOVE has happened, then `attr_prev` is missleading (order could be different),
17
+ # so `attr` should be used, which refers to the final parents. However,
18
+ # ARCHIVE nodes only have `attr_prev`, which could refer to the previous parent. And yet
19
+ # as there's no current data, they have NOT been moved, which makes `prev_attr` reliable.
20
+ # @note
21
+ # - One thing is still to be resolved. `unarchive?` could entail a later `move?` (same NodeDiff).
22
+ # Therefore, the STAGE when the comparison is done is relevant.
23
+ # - This has been resolved with `sort_before_move` & `sort_after_move` methods.
24
+ def <=>(other)
25
+ if archive? || unarchive?
26
+ return -1 if node_id_prev == other.parent_id_prev
27
+ return 1 if parent_id_prev == other.node_id_prev
28
+ elsif insert? || move?
29
+ return -1 if node_id == other.parent_id
30
+ return 1 if parent_id == other.node_id
31
+ end
32
+ 0
33
+ end
34
+
35
+ def sort_before_move(other)
36
+ self <=> other
37
+ end
38
+
39
+ def sort_after_move(other)
40
+ if move?
41
+ return -1 if node_id == other.parent_id
42
+ return 1 if parent_id == other.node_id
43
+ 0
44
+ else
45
+ self <=> other
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,11 @@
1
+ module Eco::API::UseCases::GraphQL::Helpers::Location
2
+ # To prepend to class Custom::UseCase::TagtreeDiff::NodeDiff
3
+ # Currently, name and id are the same thing, so we make some adjustments.
4
+ class Command::Diff < Eco::Data::Locations::NodeDiff
5
+ require_relative 'diff/compare'
6
+ require_relative 'diff/as_update'
7
+
8
+ include Compare
9
+ include AsUpdate
10
+ end
11
+ end
@@ -0,0 +1,46 @@
1
+ class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
2
+ module Stages
3
+ module Commandable
4
+ class << self
5
+ def included(base)
6
+ super
7
+ base.extend(ClassMethods)
8
+ base.send(:include, DiffSortable)
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ def stage_command(stage)
14
+ return :update if stage == :id_name
15
+ stage
16
+ end
17
+ end
18
+
19
+ # builds up the particular graphql location commands
20
+ def stage_commands(stage)
21
+ stage_command = self.class.stage_command(stage)
22
+ stage_updates(stage).map do |update|
23
+ {}.tap do |comm|
24
+ comm[stage_command] = symbolize_keys(update)
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def stage_updates(stage)
32
+ aiu = self.class.after_id_update?(stage)
33
+ stage_diffs(stage).map do |dfs|
34
+ dfs.as_update(stage, after_id_update: aiu)
35
+ end
36
+ end
37
+
38
+ def stage_diffs(stage)
39
+ raise "There is NO support for stage '#{stage}'. Please review" unless respond_to?(stage)
40
+ sort_diffs(stage) do
41
+ send(stage)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,23 @@
1
+ class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
2
+ module Stages
3
+ module DiffSortable
4
+ module ForArchive
5
+
6
+ private
7
+
8
+ def only_first_ancestor_in_chains(raw_diffs)
9
+ parents = raw_diffs.each_with_object({}) do |dff, prs|
10
+ (prs[dff.parent_id_prev] ||= []) << dff
11
+ end
12
+
13
+ present_parent_prev_ids = raw_diffs.select do |dff|
14
+ parents.key?(dff.node_id_prev)
15
+ end.map(&:node_id_prev)
16
+
17
+ children_with_parents = parents.values_at(*present_parent_prev_ids).flatten(1).uniq
18
+ raw_diffs - children_with_parents
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,65 @@
1
+ class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
2
+ module Stages
3
+ module DiffSortable
4
+ module ForUnarchive
5
+ # we require the original tree to be able to track what ancestors
6
+ # should be unarchived, with the aim of unarchiving a node that
7
+ # is target of update (i.e. revive and move somewhere else)
8
+ def original_tree
9
+ defined?(super)? super : @original_tree
10
+ end
11
+
12
+ def original_tree_node(id)
13
+ original_tree.node(id)
14
+ end
15
+
16
+ private
17
+
18
+ # Uses method of core class ArrayDiff
19
+ def results_by_node_id_prev
20
+ @results_by_node_id_prev ||= source_results.each_with_object({}) do |diff_result, out|
21
+ out[diff_result.node_id_prev] = diff_result
22
+ end
23
+ end
24
+
25
+ # Generates result diffs where archied ancestors are being unarchived
26
+ # It ensures unique diff results for nodeId where `raw_diffs` takes precedence
27
+ def include_unarchive_of_archived_ancestors(raw_diffs)
28
+ ancestors_diffs = raw_diffs.each_with_object([]) do |diff_result, out|
29
+ ans_diff_results = archived_ancestors_as_unarchive_diffs(diff_result.node_id_prev)
30
+ out.concat(ans_diff_results)
31
+ end
32
+ (raw_diffs + ancestors_diffs).uniq(&:node_id_prev)
33
+ end
34
+
35
+ # Unarchive comes with a bad joke. You need to unarchive archived parents
36
+ # even if they should remain archived.
37
+ def archived_ancestors_as_unarchive_diffs(id)
38
+ archived_tree_node_ancestors(id).map do |node|
39
+ if (dff = results_by_node_id_prev[node.id])
40
+ # generate a node diff with no changes
41
+ dff.dup(src_2: dff.src_1).tap do |nres|
42
+ nres.src_2['archived'] = false
43
+ end
44
+ else
45
+ # Some trouble -> can re-use
46
+ puts "Couldn't find diff_res for archived ancestor '#{node.id}'"
47
+ end
48
+ end.compact
49
+ end
50
+
51
+ def archived_tree_node_ancestors(id)
52
+ original_tree_node_ancestors(id).select(&:archived)
53
+ end
54
+
55
+ def original_tree_node_ancestors(id)
56
+ if (node = original_tree_node(id))
57
+ node.ancestors
58
+ else
59
+ []
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,49 @@
1
+ require_relative 'diff_sortable/for_unarchive'
2
+ require_relative 'diff_sortable/for_archive'
3
+
4
+ class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
5
+ module Stages
6
+ module DiffSortable
7
+ class << self
8
+ def included(base)
9
+ super
10
+ base.send(:include, Sortable)
11
+ end
12
+ end
13
+
14
+ include ForUnarchive
15
+ include ForArchive
16
+
17
+ private
18
+
19
+ # @yield
20
+ # @yieldreturn [Array<Diff>] the raw diffs of `stage`
21
+ def sort_diffs(stage)
22
+ raise ArgumentError, "Expecting block but not given" unless block_given?
23
+ raw_diffs = yield
24
+
25
+ case stage
26
+ when :archive
27
+ only_first_ancestor_in_chains(sort_top_to_bottom(raw_diffs, stage))
28
+ when :unarchive
29
+ raw_diffs = include_unarchive_of_archived_ancestors(raw_diffs)
30
+ sort_top_to_bottom(uniq_archive_token(raw_diffs, stage), stage)
31
+ when :move
32
+ sort_bottom_to_top(raw_diffs, stage)
33
+ else
34
+ sort_top_to_bottom(raw_diffs, stage)
35
+ end
36
+ end
37
+
38
+ # It should also include archived ancestors !!
39
+ # @note this will translate to the unarchive of a wider range of nodes.
40
+ # - Please notice that the design is correct (there's no way around it)
41
+ def uniq_archive_token(raw_diffs, stage)
42
+ raw_diffs.group_by(&:archived_token_prev).each_with_object([]) do |(token, token_diffs), out|
43
+ next out.concat(token_diffs) unless token
44
+ out << sort_top_to_bottom(token_diffs, stage).first
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,119 @@
1
+ class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
2
+ module Stages
3
+ module Sortable
4
+ # Class that provided elements that may have a relationship
5
+ # with a provided `block` that compares two single elements (`<==>`)
6
+ # it returns the sorted list of the elements in a safe way
7
+ # and by keeping the original order when `<==>` returns `0`
8
+ class RelationSafeSort
9
+ attr_reader :list
10
+ attr_reader :cluster_mode
11
+
12
+ # @param cluster_mode [Boolean] whether it should optimize by trying to
13
+ # first treeify and only trying to resolve the by pairings (one to one)
14
+ # the diffs that are hanging from now where.
15
+ # Possible values are `:prev` or `:curr`
16
+ def initialize(list, cluster_mode: nil, &block)
17
+ @list = list
18
+ @cluster_mode = clusterer_class.mode(cluster_mode) if cluster_mode
19
+ @comparer = block
20
+ end
21
+
22
+ def clustered?
23
+ !!@cluster_mode
24
+ end
25
+
26
+ # @yield [a, b] elements to be sorted
27
+ # When the comparer `<=>` returns `0`, it still ensures the order is correct
28
+ # Please notice that native `sort` is not stable when `<=>` can return `0`
29
+ def sort
30
+ return list.dup if list.count <= 1
31
+
32
+ # treeifying we ensure correct order (parent, children)
33
+ pending = parents_hash.dup
34
+ treeify = proc do |item|
35
+ next item unless (children = pending.delete(item))
36
+ {item => children.map {|child| treeify.call(child)}}
37
+ end
38
+
39
+ sorted_tree = top_parents.each_with_object(unpaired) do |item, tree|
40
+ next unless pending.key?(item)
41
+ tree << treeify.call(item)
42
+ end
43
+
44
+ flatten(sorted_tree)
45
+ end
46
+
47
+ private
48
+
49
+ attr_reader :comparer
50
+
51
+ def compare_two(a, b)
52
+ comparer.call(a, b)
53
+ end
54
+
55
+ def unpaired
56
+ list - paired
57
+ end
58
+
59
+ def paired
60
+ parents_hash.keys | all_children
61
+ end
62
+
63
+ def parents_hash
64
+ @parents_hash ||=
65
+ if clustered?
66
+ clusterer.clusters
67
+ else
68
+ paired_related_nodes.group_by(&:shift).transform_values(&:flatten)
69
+ end
70
+ end
71
+
72
+ def clusterer
73
+ @clusterer ||= clusterer_class.new(list, mode: cluster_mode)
74
+ end
75
+
76
+ def top_parents
77
+ @top_parents ||= parents_hash.keys.reject {|item| all_children.include?(item)}
78
+ end
79
+
80
+ def all_children
81
+ @all_children ||= parents_hash.values.flatten(1).uniq
82
+ end
83
+
84
+ # @yield [a, b] elements to be sorted
85
+ # Pairs one to one relationships in order
86
+ # @note on relationship, parent comes first
87
+ def paired_related_nodes
88
+ paired_combinations.each_with_object([]) do |pair, chained|
89
+ next if (result = compare_two(*pair)).zero?
90
+ chained << (result.negative?? pair : pair.reverse)
91
+ end
92
+ end
93
+
94
+ def paired_combinations
95
+ list.combination(2).to_a
96
+ end
97
+
98
+ # Flattens the tree into an Array
99
+ def flatten(value)
100
+ case value
101
+ when Array
102
+ value.each_with_object([]) do |child, out|
103
+ out.concat(flatten(child))
104
+ end.uniq
105
+ when Hash
106
+ item, children = value.first
107
+ [item].concat(flatten(children)).uniq
108
+ else
109
+ [value]
110
+ end
111
+ end
112
+
113
+ def clusterer_class
114
+ Eco::Data::Locations::NodeDiff::NodesDiff::ClusteredTreeify
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,59 @@
1
+ require_relative 'sortable/relation_safe_sort'
2
+
3
+ class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
4
+ module Stages
5
+ module Sortable
6
+ # from what diffs size we should use the cluster mode
7
+ CLUSTER_MODE_FROM = 400
8
+
9
+ class << self
10
+ def included(base)
11
+ super
12
+ base.extend(Stages)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def diffs_sort(list, cluster_mode: nil, &block)
19
+ RelationSafeSort.new(list, cluster_mode: cluster_mode, &block).sort
20
+ end
21
+
22
+ # @note As can't update archived nodes, it happens before :id update (:prev),
23
+ # and therefore it should use `:prev` rather than `:curr`
24
+ # @note you wouldn't be able to unarchive a node that does not exist.
25
+ # Therefore, it does have `:prev`
26
+ # @see spaceship operator `self.class#<=>`
27
+ def cluster_mode(stage)
28
+ case stage
29
+ when :archive, :unarchive
30
+ :prev
31
+ when :insert
32
+ :curr
33
+ else
34
+ return :curr if self.class.after_id_update?(stage)
35
+ :prev
36
+ end
37
+ end
38
+
39
+ def sort_top_to_bottom(raw_diffs, stage)
40
+ clustered = nil
41
+ clustered = cluster_mode(stage) if raw_diffs.count > CLUSTER_MODE_FROM
42
+
43
+ if self.class.during_or_after_move?(stage)
44
+ diffs_sort(raw_diffs, cluster_mode: clustered) do |a, b|
45
+ a.sort_after_move(b)
46
+ end
47
+ else
48
+ diffs_sort(raw_diffs, cluster_mode: clustered) do |a, b|
49
+ a.sort_before_move(b)
50
+ end
51
+ end
52
+ end
53
+
54
+ def sort_bottom_to_top(raw_diffs, stage)
55
+ sort_top_to_bottom(raw_diffs, stage).reverse
56
+ end
57
+ end
58
+ end
59
+ end